Skip to content

Commit ab8bac4

Browse files
committed
feat(@schematics/angular): replace assets with public directory
The `assets` directory is confusing for the users and commonly users place "assets" which are not meant to be copied but instead processed by the build system. This causes some files both bundled and copied. With this change we rename the `assets` directory to `public` and also move the `favicon.ico` inside this newly created directory.
1 parent 0f84ae9 commit ab8bac4

File tree

24 files changed

+64
-68
lines changed

24 files changed

+64
-68
lines changed

goldens/public-api/angular_devkit/build_angular/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export interface AssetPatternObject {
8080
glob: string;
8181
ignore?: string[];
8282
input: string;
83-
output: string;
83+
output?: string;
8484
}
8585

8686
// @public

packages/angular/pwa/pwa/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,15 @@ export default function (options: PwaOptions): Rule {
146146
const { title, ...swOptions } = options;
147147

148148
await writeWorkspace(host, workspace);
149+
let assetsDir = posix.join(sourcePath, 'assets');
150+
if (!host.exists(assetsDir)) {
151+
assetsDir = posix.join(sourcePath, 'public');
152+
}
149153

150154
return chain([
151155
externalSchematic('@schematics/angular', 'service-worker', swOptions),
152156
mergeWith(apply(url('./files/root'), [template({ ...options }), move(sourcePath)])),
153-
mergeWith(apply(url('./files/assets'), [move(posix.join(sourcePath, 'assets'))])),
157+
mergeWith(apply(url('./files/assets'), [move(assetsDir)])),
154158
...[...indexFiles].map((path) => updateIndexFile(path)),
155159
]);
156160
};

packages/angular/pwa/pwa/index_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('PWA Schematic', () => {
5454

5555
it('should create icon files', async () => {
5656
const dimensions = [72, 96, 128, 144, 152, 192, 384, 512];
57-
const iconPath = '/projects/bar/src/assets/icons/icon-';
57+
const iconPath = '/projects/bar/src/public/icons/icon-';
5858
const tree = await schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
5959

6060
dimensions.forEach((d) => {

packages/angular_devkit/build_angular/src/builders/application/schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,11 +556,12 @@
556556
},
557557
"output": {
558558
"type": "string",
559+
"default": "",
559560
"description": "Absolute path within the output."
560561
}
561562
},
562563
"additionalProperties": false,
563-
"required": ["glob", "input", "output"]
564+
"required": ["glob", "input"]
564565
},
565566
{
566567
"type": "string"

packages/angular_devkit/build_angular/src/builders/browser-esbuild/schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,11 +467,12 @@
467467
},
468468
"output": {
469469
"type": "string",
470+
"default": "",
470471
"description": "Absolute path within the output."
471472
}
472473
},
473474
"additionalProperties": false,
474-
"required": ["glob", "input", "output"]
475+
"required": ["glob", "input"]
475476
},
476477
{
477478
"type": "string"

packages/angular_devkit/build_angular/src/builders/browser/schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,11 +455,12 @@
455455
},
456456
"output": {
457457
"type": "string",
458+
"default": "",
458459
"description": "Absolute path within the output."
459460
}
460461
},
461462
"additionalProperties": false,
462-
"required": ["glob", "input", "output"]
463+
"required": ["glob", "input"]
463464
},
464465
{
465466
"type": "string"

packages/angular_devkit/build_angular/src/builders/karma/schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@
290290
},
291291
"output": {
292292
"type": "string",
293+
"default": "",
293294
"description": "Absolute path within the output."
294295
},
295296
"ignore": {
@@ -301,7 +302,7 @@
301302
}
302303
},
303304
"additionalProperties": false,
304-
"required": ["glob", "input", "output"]
305+
"required": ["glob", "input"]
305306
},
306307
{
307308
"type": "string"

packages/angular_devkit/build_angular/src/builders/server/schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,12 @@
252252
},
253253
"output": {
254254
"type": "string",
255+
"default": "",
255256
"description": "Absolute path within the output."
256257
}
257258
},
258259
"additionalProperties": false,
259-
"required": ["glob", "input", "output"]
260+
"required": ["glob", "input"]
260261
},
261262
{
262263
"type": "string"

packages/angular_devkit/build_angular/src/builders/web-test-runner/schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
},
270270
"output": {
271271
"type": "string",
272+
"default": "",
272273
"description": "Absolute path within the output."
273274
},
274275
"ignore": {
@@ -280,7 +281,7 @@
280281
}
281282
},
282283
"additionalProperties": false,
283-
"required": ["glob", "input", "output"]
284+
"required": ["glob", "input"]
284285
},
285286
{
286287
"type": "string"

packages/angular_devkit/build_angular/src/tools/webpack/utils/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export function assetPatterns(root: string, assets: AssetPatternClass[]) {
231231
return assets.map((asset: AssetPatternClass, index: number): ObjectPattern => {
232232
// Resolve input paths relative to workspace root and add slash at the end.
233233
// eslint-disable-next-line prefer-const
234-
let { input, output, ignore = [], glob } = asset;
234+
let { input, output = '', ignore = [], glob } = asset;
235235
input = path.resolve(root, input).replace(/\\/g, '/');
236236
input = input.endsWith('/') ? input : input + '/';
237237
output = output.endsWith('/') ? output : output + '/';

packages/angular_devkit/build_angular/src/utils/normalize-asset-patterns.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import { BaseException } from '@angular-devkit/core';
1010
import { statSync } from 'fs';
11+
import assert from 'node:assert';
1112
import * as path from 'path';
1213
import { AssetPattern, AssetPatternClass } from '../builders/browser/schema';
1314

@@ -22,7 +23,7 @@ export function normalizeAssetPatterns(
2223
workspaceRoot: string,
2324
projectRoot: string,
2425
projectSourceRoot: string | undefined,
25-
): AssetPatternClass[] {
26+
): (AssetPatternClass & { output: string })[] {
2627
if (assetPatterns.length === 0) {
2728
return [];
2829
}
@@ -68,13 +69,15 @@ export function normalizeAssetPatterns(
6869

6970
assetPattern = { glob, input, output };
7071
} else {
71-
assetPattern.output = path.join('.', assetPattern.output);
72+
assetPattern.output = path.join('.', assetPattern.output ?? '');
7273
}
7374

75+
assert(assetPattern.output !== undefined);
76+
7477
if (assetPattern.output.startsWith('..')) {
7578
throw new Error('An asset cannot be written to a location outside of the output path.');
7679
}
7780

78-
return assetPattern;
81+
return assetPattern as AssetPatternClass & { output: string };
7982
});
8083
}

packages/schematics/angular/application/files/common-files/src/assets/.gitkeep.template

Whitespace-only changes.

packages/schematics/angular/application/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ function addAppToWorkspaceFile(
242242
polyfills: ['zone.js'],
243243
tsConfig: `${projectRoot}tsconfig.app.json`,
244244
inlineStyleLanguage,
245-
assets: [`${sourceRoot}/favicon.ico`, `${sourceRoot}/assets`],
245+
assets: [{ 'glob': '**/*', 'input': `${sourceRoot}/public` }],
246246
styles: [`${sourceRoot}/styles.${options.style}`],
247247
scripts: [],
248248
},

packages/schematics/angular/application/index_spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ describe('Application Schematic', () => {
5050
jasmine.arrayContaining([
5151
'/projects/foo/tsconfig.app.json',
5252
'/projects/foo/tsconfig.spec.json',
53-
'/projects/foo/src/favicon.ico',
53+
'/projects/foo/src/public/favicon.ico',
5454
'/projects/foo/src/index.html',
5555
'/projects/foo/src/main.ts',
5656
'/projects/foo/src/styles.css',
@@ -263,7 +263,7 @@ describe('Application Schematic', () => {
263263
jasmine.arrayContaining([
264264
'/tsconfig.app.json',
265265
'/tsconfig.spec.json',
266-
'/src/favicon.ico',
266+
'/src/public/favicon.ico',
267267
'/src/index.html',
268268
'/src/main.ts',
269269
'/src/styles.css',
@@ -448,7 +448,7 @@ describe('Application Schematic', () => {
448448
expect(files).toEqual(
449449
jasmine.arrayContaining([
450450
'/projects/foo/tsconfig.app.json',
451-
'/projects/foo/src/favicon.ico',
451+
'/projects/foo/src/public/favicon.ico',
452452
'/projects/foo/src/index.html',
453453
'/projects/foo/src/main.ts',
454454
'/projects/foo/src/styles.css',
@@ -473,7 +473,7 @@ describe('Application Schematic', () => {
473473
expect(files).toEqual(
474474
jasmine.arrayContaining([
475475
'/projects/foo/tsconfig.app.json',
476-
'/projects/foo/src/favicon.ico',
476+
'/projects/foo/src/public/favicon.ico',
477477
'/projects/foo/src/index.html',
478478
'/projects/foo/src/main.ts',
479479
'/projects/foo/src/styles.css',
@@ -499,7 +499,7 @@ describe('Application Schematic', () => {
499499
expect(files).toEqual(
500500
jasmine.arrayContaining([
501501
'/projects/foo/tsconfig.app.json',
502-
'/projects/foo/src/favicon.ico',
502+
'/projects/foo/src/public/favicon.ico',
503503
'/projects/foo/src/index.html',
504504
'/projects/foo/src/main.ts',
505505
'/projects/foo/src/styles.css',
@@ -519,7 +519,7 @@ describe('Application Schematic', () => {
519519
jasmine.arrayContaining([
520520
'/projects/foo/tsconfig.app.json',
521521
'/projects/foo/tsconfig.spec.json',
522-
'/projects/foo/src/favicon.ico',
522+
'/projects/foo/src/public/favicon.ico',
523523
'/projects/foo/src/index.html',
524524
'/projects/foo/src/main.ts',
525525
'/projects/foo/src/styles.css',

tests/legacy-cli/e2e/tests/build/assets.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@ import { updateJsonFile } from '../../utils/project';
55
import { expectToFail } from '../../utils/utils';
66

77
export default async function () {
8-
await writeFile('src/assets/.file', '');
9-
await writeFile('src/assets/test.abc', 'hello world');
8+
await writeFile('src/public/.file', '');
9+
await writeFile('src/public/test.abc', 'hello world');
1010

1111
await ng('build', '--configuration=development');
1212

1313
await expectFileToExist('dist/test-project/browser/favicon.ico');
14-
await expectFileToExist('dist/test-project/browser/assets/.file');
15-
await expectFileToMatch('dist/test-project/browser/assets/test.abc', 'hello world');
16-
await expectToFail(() => expectFileToExist('dist/test-project/browser/assets/.gitkeep'));
14+
await expectFileToExist('dist/test-project/browser/.file');
15+
await expectFileToMatch('dist/test-project/browser/test.abc', 'hello world');
16+
await expectToFail(() => expectFileToExist('dist/test-project/browser/.gitkeep'));
1717

1818
// Ensure `followSymlinks` option follows symlinks
1919
await updateJsonFile('angular.json', (workspaceJson) => {
2020
const appArchitect = workspaceJson.projects['test-project'].architect;
2121
appArchitect['build'].options.assets = [
22-
{ glob: '**/*', input: 'src/assets', output: 'assets', followSymlinks: true },
22+
{ glob: '**/*', input: 'src/public', followSymlinks: true },
2323
];
2424
});
2525
fs.mkdirSync('dirToSymlink/subdir1', { recursive: true });
@@ -28,12 +28,12 @@ export default async function () {
2828
fs.writeFileSync('dirToSymlink/subdir1/b.txt', '');
2929
fs.writeFileSync('dirToSymlink/subdir2/c.txt', '');
3030
fs.writeFileSync('dirToSymlink/subdir2/subsubdir1/d.txt', '');
31-
fs.symlinkSync(process.cwd() + '/dirToSymlink', 'src/assets/symlinkDir');
31+
fs.symlinkSync(process.cwd() + '/dirToSymlink', 'src/public/symlinkDir');
3232

3333
await ng('build', '--configuration=development');
3434

35-
await expectFileToExist('dist/test-project/browser/assets/symlinkDir/a.txt');
36-
await expectFileToExist('dist/test-project/browser/assets/symlinkDir/subdir1/b.txt');
37-
await expectFileToExist('dist/test-project/browser/assets/symlinkDir/subdir2/c.txt');
38-
await expectFileToExist('dist/test-project/browser/assets/symlinkDir/subdir2/subsubdir1/d.txt');
35+
await expectFileToExist('dist/test-project/browser/symlinkDir/a.txt');
36+
await expectFileToExist('dist/test-project/browser/symlinkDir/subdir1/b.txt');
37+
await expectFileToExist('dist/test-project/browser/symlinkDir/subdir2/c.txt');
38+
await expectFileToExist('dist/test-project/browser/symlinkDir/subdir2/subsubdir1/d.txt');
3939
}

tests/legacy-cli/e2e/tests/build/css-urls.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { copyProjectAsset } from '../../utils/assets';
99
import { expectToFail } from '../../utils/utils';
1010
import { getGlobalVariable } from '../../utils/env';
11+
import { mkdir } from 'node:fs/promises';
1112

1213
const imgSvg = `
1314
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
@@ -22,6 +23,8 @@ export default async function () {
2223
? './dist/test-project/browser'
2324
: './dist/test-project/browser/media';
2425

26+
await mkdir('src/public/assets/', { recursive: true });
27+
2528
await Promise.resolve()
2629
// Verify absolute/relative paths in global/component css.
2730
.then(() =>
@@ -34,8 +37,8 @@ export default async function () {
3437
h3 { background: url('/assets/component-img-absolute.svg'); }
3538
h4 { background: url('../assets/component-img-relative.png'); }
3639
`,
37-
'src/assets/global-img-absolute.svg': imgSvg,
38-
'src/assets/component-img-absolute.svg': imgSvg,
40+
'src/public/assets/global-img-absolute.svg': imgSvg,
41+
'src/public/assets/component-img-absolute.svg': imgSvg,
3942
}),
4043
)
4144
.then(() => copyProjectAsset('images/spectrum.png', './src/assets/global-img-relative.png'))

tests/legacy-cli/e2e/tests/build/multiple-configs.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ export default async function () {
2424
sourceMap: true,
2525
outputHashing: 'none',
2626
vendorChunk: true,
27-
assets: ['src/favicon.ico', 'src/assets'],
2827
styles: ['src/styles.css'],
2928
scripts: [],
3029
budgets: [],

tests/legacy-cli/e2e/tests/build/prerender/http-requests-assets.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default async function () {
3939
};
4040
`,
4141
// Add asset
42-
'src/assets/media.json': JSON.stringify({ dataFromAssets: true }),
42+
'src/public/media.json': JSON.stringify({ dataFromAssets: true }),
4343
// Update component to do an HTTP call to asset.
4444
'src/app/app.component.ts': `
4545
import { Component, inject } from '@angular/core';
@@ -60,7 +60,7 @@ export default async function () {
6060
data: any;
6161
constructor() {
6262
const http = inject(HttpClient);
63-
http.get('/assets/media.json').subscribe((d) => {
63+
http.get('/media.json').subscribe((d) => {
6464
this.data = d;
6565
});
6666
}

tests/legacy-cli/e2e/tests/commands/add/add-pwa.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,4 @@ export default async function () {
4848

4949
await ng('build');
5050
await expectFileToExist(ngswPath);
51-
52-
// It should correctly generate assetGroups and include at least one URL in each group.
53-
const ngswJson = JSON.parse(await readFile(ngswPath));
54-
// @ts-ignore
55-
const assetGroups: any[] = ngswJson.assetGroups.map(({ name, urls }) => ({
56-
name,
57-
urlCount: urls.length,
58-
}));
59-
const emptyAssetGroups = assetGroups.filter(({ urlCount }) => urlCount === 0);
60-
61-
if (assetGroups.length === 0) {
62-
throw new Error("Expected 'ngsw.json' to contain at least one asset-group.");
63-
}
64-
if (emptyAssetGroups.length > 0) {
65-
throw new Error(
66-
'Expected all asset-groups to contain at least one URL, but the following groups are empty: ' +
67-
emptyAssetGroups.map(({ name }) => name).join(', '),
68-
);
69-
}
7051
}

tests/legacy-cli/e2e/tests/commands/config/config-get.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@ export default async function () {
2323
'config',
2424
`projects.test-project.architect.build.options.assets[0]`,
2525
);
26-
if (!stdout2.includes('src/favicon.ico')) {
27-
throw new Error(`Expected "src/favicon.ico", received "${JSON.stringify(stdout)}".`);
26+
if (!stdout2.includes('"input": "src/public"')) {
27+
throw new Error(`Expected "input": "src/public", received "${JSON.stringify(stdout)}".`);
2828
}
2929

3030
const { stdout: stdout3 } = await ng(
3131
'config',
3232
`projects["test-project"].architect.build.options.assets[0]`,
3333
);
3434

35-
if (!stdout3.includes('src/favicon.ico')) {
36-
throw new Error(`Expected "src/favicon.ico", received "${JSON.stringify(stdout)}".`);
35+
if (!stdout3.includes('"input": "src/public"')) {
36+
throw new Error(`Expected "input": "src/public", received "${JSON.stringify(stdout)}".`);
3737
}
3838

3939
// should print all config when no positional args are provided.

tests/legacy-cli/e2e/tests/commands/serve/assets.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ export default async function () {
88
const outsideDirectoryName = `../outside-${randomUUID()}`;
99

1010
await updateJsonFile('angular.json', (json) => {
11-
json.projects['test-project'].architect.build.options.assets = [
12-
'src/favicon.ico',
13-
'src/assets',
14-
// Ensure assets located outside the workspace root work with the dev server
15-
{ 'input': outsideDirectoryName, 'glob': '**/*', 'output': './outside' },
16-
];
11+
// Ensure assets located outside the workspace root work with the dev server
12+
json.projects['test-project'].architect.build.options.assets.push({
13+
'input': outsideDirectoryName,
14+
'glob': '**/*',
15+
'output': './outside',
16+
});
1717
});
1818

1919
await mkdir(outsideDirectoryName);

0 commit comments

Comments
 (0)