Skip to content

Commit f53bf9d

Browse files
committed
feat(@angular-devkit/build-angular): add type=module to all scripts tags
With this change we add `type=module` to all script tags. This is now possible since IE is no longer supported. More information about modules can be found here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
1 parent 48d4925 commit f53bf9d

File tree

15 files changed

+132
-256
lines changed

15 files changed

+132
-256
lines changed

packages/angular_devkit/build_angular/src/builders/browser/index.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,12 @@ export function buildWebpackBrowser(
172172

173173
return {
174174
...(await initialize(options, context, transforms.webpackConfiguration)),
175-
buildBrowserFeatures,
176175
target,
177176
};
178177
}),
179178
switchMap(
180179
// eslint-disable-next-line max-lines-per-function
181-
({ config, projectRoot, projectSourceRoot, i18n, buildBrowserFeatures, target }) => {
180+
({ config, projectRoot, projectSourceRoot, i18n, target }) => {
182181
const normalizedOptimization = normalizeOptimization(options.optimization);
183182

184183
return runWebpack(config, context, {
@@ -191,7 +190,6 @@ export function buildWebpackBrowser(
191190
}
192191
}),
193192
}).pipe(
194-
// eslint-disable-next-line max-lines-per-function
195193
concatMap(async (buildEvent) => {
196194
const spinner = new Spinner();
197195
spinner.enabled = options.progress !== false;
@@ -227,8 +225,6 @@ export function buildWebpackBrowser(
227225
} else {
228226
outputPaths = ensureOutputPaths(baseOutputPath, i18n);
229227

230-
let moduleFiles: EmittedFiles[] | undefined;
231-
232228
const scriptsEntryPointName = normalizeExtraEntryPoints(
233229
options.scripts || [],
234230
'scripts',
@@ -320,8 +316,6 @@ export function buildWebpackBrowser(
320316
lang: locale || undefined,
321317
outputPath,
322318
files: mapEmittedFilesToFileInfo(emittedFiles),
323-
noModuleFiles: [],
324-
moduleFiles: mapEmittedFilesToFileInfo(moduleFiles),
325319
});
326320

327321
if (warnings.length || errors.length) {

packages/angular_devkit/build_angular/src/builders/browser/specs/cross-origin_spec.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ describe('Browser Builder crossOrigin', () => {
3939
expect(content).toBe(
4040
`<html><head><base href="/"><link rel="stylesheet" href="styles.css" crossorigin="use-credentials"></head>` +
4141
`<body><app-root></app-root>` +
42-
`<script src="runtime.js" crossorigin="use-credentials" defer></script>` +
43-
`<script src="polyfills.js" crossorigin="use-credentials" defer></script>` +
44-
`<script src="vendor.js" crossorigin="use-credentials" defer></script>` +
45-
`<script src="main.js" crossorigin="use-credentials" defer></script></body></html>`,
42+
`<script src="runtime.js" type="module" crossorigin="use-credentials"></script>` +
43+
`<script src="polyfills.js" type="module" crossorigin="use-credentials"></script>` +
44+
`<script src="vendor.js" type="module" crossorigin="use-credentials"></script>` +
45+
`<script src="main.js" type="module" crossorigin="use-credentials"></script></body></html>`,
4646
);
4747
await run.stop();
4848
});
@@ -58,10 +58,10 @@ describe('Browser Builder crossOrigin', () => {
5858
`<html><head><base href="/">` +
5959
`<link rel="stylesheet" href="styles.css" crossorigin="anonymous"></head>` +
6060
`<body><app-root></app-root>` +
61-
`<script src="runtime.js" crossorigin="anonymous" defer></script>` +
62-
`<script src="polyfills.js" crossorigin="anonymous" defer></script>` +
63-
`<script src="vendor.js" crossorigin="anonymous" defer></script>` +
64-
`<script src="main.js" crossorigin="anonymous" defer></script></body></html>`,
61+
`<script src="runtime.js" type="module" crossorigin="anonymous"></script>` +
62+
`<script src="polyfills.js" type="module" crossorigin="anonymous"></script>` +
63+
`<script src="vendor.js" type="module" crossorigin="anonymous"></script>` +
64+
`<script src="main.js" type="module" crossorigin="anonymous"></script></body></html>`,
6565
);
6666
await run.stop();
6767
});
@@ -77,10 +77,10 @@ describe('Browser Builder crossOrigin', () => {
7777
`<html><head><base href="/">` +
7878
`<link rel="stylesheet" href="styles.css"></head>` +
7979
`<body><app-root></app-root>` +
80-
`<script src="runtime.js" defer></script>` +
81-
`<script src="polyfills.js" defer></script>` +
82-
`<script src="vendor.js" defer></script>` +
83-
`<script src="main.js" defer></script></body></html>`,
80+
`<script src="runtime.js" type="module"></script>` +
81+
`<script src="polyfills.js" type="module"></script>` +
82+
`<script src="vendor.js" type="module"></script>` +
83+
`<script src="main.js" type="module"></script></body></html>`,
8484
);
8585
await run.stop();
8686
});

packages/angular_devkit/build_angular/src/builders/browser/specs/index_spec.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ describe('Browser Builder index HTML processing', () => {
3636
const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise());
3737
expect(content).toBe(
3838
`<html><head><base href="/"><link rel="stylesheet" href="styles.css"></head>` +
39-
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
40-
`<script src="polyfills.js" defer></script>` +
41-
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
39+
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
40+
`<script src="polyfills.js" type="module"></script>` +
41+
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
4242
);
4343
await run.stop();
4444
});
@@ -60,9 +60,9 @@ describe('Browser Builder index HTML processing', () => {
6060
expect(content).toBe(
6161
`<html><head><base href="/"><link rel="stylesheet" href="styles.css"></head>` +
6262
`<body><app-root></app-root>` +
63-
`<script src="runtime.js" defer></script><script src="polyfills.js" defer></script>` +
64-
`<script src="vendor.js" defer></script>` +
65-
`<script src="main.js" defer></script></body></html>`,
63+
`<script src="runtime.js" type="module"></script><script src="polyfills.js" type="module"></script>` +
64+
`<script src="vendor.js" type="module"></script>` +
65+
`<script src="main.js" type="module"></script></body></html>`,
6666
);
6767
await run.stop();
6868
});
@@ -82,9 +82,9 @@ describe('Browser Builder index HTML processing', () => {
8282
const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise());
8383
expect(content).toBe(
8484
`<html><head><title>&iacute;</title><base href="/"><link rel="stylesheet" href="styles.css"></head> ` +
85-
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
86-
`<script src="polyfills.js" defer></script>` +
87-
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
85+
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
86+
`<script src="polyfills.js" type="module"></script>` +
87+
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
8888
);
8989
await run.stop();
9090
});
@@ -104,9 +104,9 @@ describe('Browser Builder index HTML processing', () => {
104104
const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise());
105105
expect(content).toBe(
106106
`<html><head><base href="/"><%= csrf_meta_tags %><link rel="stylesheet" href="styles.css"></head> ` +
107-
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
108-
`<script src="polyfills.js" defer></script>` +
109-
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
107+
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
108+
`<script src="polyfills.js" type="module"></script>` +
109+
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
110110
);
111111
await run.stop();
112112
});
@@ -152,9 +152,9 @@ describe('Browser Builder index HTML processing', () => {
152152
const content = await host.read(normalize(outputIndexPath)).toPromise();
153153
expect(virtualFs.fileBufferToString(content)).toBe(
154154
`<html><head><base href="/"><%= csrf_meta_tags %><link rel="stylesheet" href="styles.css"></head> ` +
155-
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
156-
`<script src="polyfills.js" defer></script>` +
157-
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
155+
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
156+
`<script src="polyfills.js" type="module"></script>` +
157+
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
158158
);
159159
});
160160

@@ -198,9 +198,9 @@ describe('Browser Builder index HTML processing', () => {
198198
const content = await host.read(normalize(outputIndexPath)).toPromise();
199199
expect(virtualFs.fileBufferToString(content)).toBe(
200200
`<html><head><base href="/"><link rel="stylesheet" href="styles.css"></head> ` +
201-
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
202-
`<script src="polyfills.js" defer></script>` +
203-
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
201+
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
202+
`<script src="polyfills.js" type="module"></script>` +
203+
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
204204
);
205205
});
206206

@@ -244,9 +244,9 @@ describe('Browser Builder index HTML processing', () => {
244244
const content = await host.read(normalize(outputIndexPath)).toPromise();
245245
expect(virtualFs.fileBufferToString(content)).toBe(
246246
`<html><head><base href="/"><link rel="stylesheet" href="styles.css"></head> ` +
247-
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
248-
`<script src="polyfills.js" defer></script>` +
249-
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
247+
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
248+
`<script src="polyfills.js" type="module"></script>` +
249+
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
250250
);
251251
});
252252
});

packages/angular_devkit/build_angular/src/builders/browser/specs/scripts-array_spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ describe('Browser Builder scripts array', () => {
5353
'renamed-lazy-script.js': 'pre-rename-lazy-script',
5454
'main.js': 'input-script',
5555
'index.html':
56-
'<script src="runtime.js" defer></script>' +
57-
'<script src="polyfills.js" defer></script>' +
56+
'<script src="runtime.js" type="module"></script>' +
57+
'<script src="polyfills.js" type="module"></script>' +
5858
'<script src="scripts.js" defer></script>' +
5959
'<script src="renamed-script.js" defer></script>' +
60-
'<script src="vendor.js" defer></script>' +
61-
'<script src="main.js" defer></script>',
60+
'<script src="vendor.js" type="module"></script>' +
61+
'<script src="main.js" type="module"></script>',
6262
};
6363

6464
host.writeMultipleFiles(scripts);

packages/angular_devkit/build_angular/src/builders/browser/specs/service-worker_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ describe('Browser Builder service worker', () => {
105105
hashTable: {
106106
'/favicon.ico': '84161b857f5c547e3699ddfbffc6d8d737542e01',
107107
'/assets/folder-asset.txt': '617f202968a6a81050aa617c2e28e1dca11ce8d4',
108-
'/index.html': 'f0bea8ced1dfbeeb771a5f48651fbcff52a625eb',
108+
'/index.html': '8964a35a8b850942f8d18ba919f248762ff3154d',
109109
'/spectrum.png': '8d048ece46c0f3af4b598a95fd8e4709b631c3c0',
110110
},
111111
}),
@@ -222,7 +222,7 @@ describe('Browser Builder service worker', () => {
222222
hashTable: {
223223
'/foo/bar/favicon.ico': '84161b857f5c547e3699ddfbffc6d8d737542e01',
224224
'/foo/bar/assets/folder-asset.txt': '617f202968a6a81050aa617c2e28e1dca11ce8d4',
225-
'/foo/bar/index.html': 'f6650ac91428c6933dfe4c24079b3b15400da1ba',
225+
'/foo/bar/index.html': '5c99755c1e7cfd1c8aba34ad1155afc72a288fec',
226226
},
227227
}),
228228
);

packages/angular_devkit/build_angular/src/builders/dev-server/index.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,17 +305,22 @@ export function serveWebpackBrowser(
305305
switchMap(({ browserOptions, webpackConfig, locale }) => {
306306
if (browserOptions.index) {
307307
const { scripts = [], styles = [], baseHref } = browserOptions;
308-
const entrypoints = generateEntryPoints({ scripts, styles });
309-
310-
webpackConfig.plugins = [...(webpackConfig.plugins || [])];
308+
const entrypoints = generateEntryPoints({
309+
scripts,
310+
styles,
311+
// The below is needed as otherwise HMR for CSS will break.
312+
// styles.js and runtime.js needs to be loaded as a non-module scripts as otherwise `document.currentScript` will be null.
313+
// https://github.com/webpack-contrib/mini-css-extract-plugin/blob/90445dd1d81da0c10b9b0e8a17b417d0651816b8/src/hmr/hotModuleReplacement.js#L39
314+
isHMREnabled: webpackConfig.devServer?.hot,
315+
});
316+
317+
webpackConfig.plugins ??= [];
311318
webpackConfig.plugins.push(
312319
new IndexHtmlWebpackPlugin({
313320
indexPath: path.resolve(workspaceRoot, getIndexInputFile(browserOptions.index)),
314321
outputPath: getIndexOutputFile(browserOptions.index),
315322
baseHref,
316323
entrypoints,
317-
moduleEntrypoints: [],
318-
noModuleEntrypoints: [],
319324
deployUrl: browserOptions.deployUrl,
320325
sri: browserOptions.subresourceIntegrity,
321326
postTransform: transforms.indexHtml,

packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html.ts

Lines changed: 24 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export type LoadOutputFileFunctionType = (file: string) => Promise<string>;
1313

1414
export type CrossOriginValue = 'none' | 'anonymous' | 'use-credentials';
1515

16+
export type Entrypoint = [name: string, isModule: boolean];
17+
1618
export interface AugmentIndexHtmlOptions {
1719
/* Input contents */
1820
html: string;
@@ -23,21 +25,16 @@ export interface AugmentIndexHtmlOptions {
2325
crossOrigin?: CrossOriginValue;
2426
/*
2527
* Files emitted by the build.
26-
* Js files will be added without 'nomodule' nor 'module'.
2728
*/
2829
files: FileInfo[];
29-
/** Files that should be added using 'nomodule'. */
30-
noModuleFiles?: FileInfo[];
31-
/** Files that should be added using 'module'. */
32-
moduleFiles?: FileInfo[];
3330
/*
3431
* Function that loads a file used.
3532
* This allows us to use different routines within the IndexHtmlWebpackPlugin and
3633
* when used without this plugin.
3734
*/
3835
loadOutputFile: LoadOutputFileFunctionType;
3936
/** Used to sort the inseration of files in the HTML file */
40-
entrypoints: string[];
37+
entrypoints: Entrypoint[];
4138
/** Used to set the document default locale */
4239
lang?: string;
4340
}
@@ -47,46 +44,34 @@ export interface FileInfo {
4744
name: string;
4845
extension: string;
4946
}
50-
5147
/*
5248
* Helper function used by the IndexHtmlWebpackPlugin.
5349
* Can also be directly used by builder, e. g. in order to generate an index.html
5450
* after processing several configurations in order to build different sets of
5551
* bundles for differential serving.
5652
*/
5753
export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise<string> {
58-
const {
59-
loadOutputFile,
60-
files,
61-
noModuleFiles = [],
62-
moduleFiles = [],
63-
entrypoints,
64-
sri,
65-
deployUrl = '',
66-
lang,
67-
baseHref,
68-
html,
69-
} = params;
54+
const { loadOutputFile, files, entrypoints, sri, deployUrl = '', lang, baseHref, html } = params;
7055

7156
let { crossOrigin = 'none' } = params;
7257
if (sri && crossOrigin === 'none') {
7358
crossOrigin = 'anonymous';
7459
}
7560

7661
const stylesheets = new Set<string>();
77-
const scripts = new Set<string>();
62+
const scripts = new Map</** file name */ string, /** isModule */ boolean>();
7863

79-
// Sort files in the order we want to insert them by entrypoint and dedupes duplicates
80-
const mergedFiles = [...moduleFiles, ...noModuleFiles, ...files];
81-
for (const entrypoint of entrypoints) {
82-
for (const { extension, file, name } of mergedFiles) {
83-
if (name !== entrypoint) {
64+
// Sort files in the order we want to insert them by entrypoint
65+
for (const [entrypoint, isModule] of entrypoints) {
66+
for (const { extension, file, name } of files) {
67+
if (name !== entrypoint || scripts.has(file) || stylesheets.has(file)) {
8468
continue;
8569
}
8670

8771
switch (extension) {
8872
case '.js':
89-
scripts.add(file);
73+
// Also, non entrypoints need to be loaded as no module as they can contain problematic code.
74+
scripts.set(file, isModule);
9075
break;
9176
case '.css':
9277
stylesheets.add(file);
@@ -96,52 +81,38 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
9681
}
9782

9883
let scriptTags: string[] = [];
99-
for (const script of scripts) {
100-
const attrs = [`src="${deployUrl}${script}"`];
101-
102-
if (crossOrigin !== 'none') {
103-
attrs.push(`crossorigin="${crossOrigin}"`);
104-
}
84+
for (const [src, isModule] of scripts) {
85+
const attrs = [`src="${deployUrl}${src}"`];
10586

106-
// We want to include nomodule or module when a file is not common amongs all
107-
// such as runtime.js
108-
const scriptPredictor = ({ file }: FileInfo): boolean => file === script;
109-
if (!files.some(scriptPredictor)) {
110-
// in some cases for differential loading file with the same name is available in both
111-
// nomodule and module such as scripts.js
112-
// we shall not add these attributes if that's the case
113-
const isNoModuleType = noModuleFiles.some(scriptPredictor);
114-
const isModuleType = moduleFiles.some(scriptPredictor);
115-
116-
if (isNoModuleType && !isModuleType) {
117-
attrs.push('nomodule', 'defer');
118-
} else if (isModuleType && !isNoModuleType) {
119-
attrs.push('type="module"');
120-
} else {
121-
attrs.push('defer');
122-
}
87+
// This is also need for non entry-points as they may contain problematic code.
88+
if (isModule) {
89+
attrs.push('type="module"');
12390
} else {
12491
attrs.push('defer');
12592
}
12693

94+
if (crossOrigin !== 'none') {
95+
attrs.push(`crossorigin="${crossOrigin}"`);
96+
}
97+
12798
if (sri) {
128-
const content = await loadOutputFile(script);
99+
const content = await loadOutputFile(src);
129100
attrs.push(generateSriAttributes(content));
130101
}
131102

132103
scriptTags.push(`<script ${attrs.join(' ')}></script>`);
133104
}
134105

135106
let linkTags: string[] = [];
136-
for (const stylesheet of stylesheets) {
137-
const attrs = [`rel="stylesheet"`, `href="${deployUrl}${stylesheet}"`];
107+
for (const src of stylesheets) {
108+
const attrs = [`rel="stylesheet"`, `href="${deployUrl}${src}"`];
138109

139110
if (crossOrigin !== 'none') {
140111
attrs.push(`crossorigin="${crossOrigin}"`);
141112
}
142113

143114
if (sri) {
144-
const content = await loadOutputFile(stylesheet);
115+
const content = await loadOutputFile(src);
145116
attrs.push(generateSriAttributes(content));
146117
}
147118

0 commit comments

Comments
 (0)