Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): add type=module to all scripts…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
alan-agius4 committed Aug 5, 2021
1 parent 48d4925 commit f53bf9d
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 256 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,12 @@ export function buildWebpackBrowser(

return {
...(await initialize(options, context, transforms.webpackConfiguration)),
buildBrowserFeatures,
target,
};
}),
switchMap(
// eslint-disable-next-line max-lines-per-function
({ config, projectRoot, projectSourceRoot, i18n, buildBrowserFeatures, target }) => {
({ config, projectRoot, projectSourceRoot, i18n, target }) => {
const normalizedOptimization = normalizeOptimization(options.optimization);

return runWebpack(config, context, {
Expand All @@ -191,7 +190,6 @@ export function buildWebpackBrowser(
}
}),
}).pipe(
// eslint-disable-next-line max-lines-per-function
concatMap(async (buildEvent) => {
const spinner = new Spinner();
spinner.enabled = options.progress !== false;
Expand Down Expand Up @@ -227,8 +225,6 @@ export function buildWebpackBrowser(
} else {
outputPaths = ensureOutputPaths(baseOutputPath, i18n);

let moduleFiles: EmittedFiles[] | undefined;

const scriptsEntryPointName = normalizeExtraEntryPoints(
options.scripts || [],
'scripts',
Expand Down Expand Up @@ -320,8 +316,6 @@ export function buildWebpackBrowser(
lang: locale || undefined,
outputPath,
files: mapEmittedFilesToFileInfo(emittedFiles),
noModuleFiles: [],
moduleFiles: mapEmittedFilesToFileInfo(moduleFiles),
});

if (warnings.length || errors.length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ describe('Browser Builder crossOrigin', () => {
expect(content).toBe(
`<html><head><base href="/"><link rel="stylesheet" href="styles.css" crossorigin="use-credentials"></head>` +
`<body><app-root></app-root>` +
`<script src="runtime.js" crossorigin="use-credentials" defer></script>` +
`<script src="polyfills.js" crossorigin="use-credentials" defer></script>` +
`<script src="vendor.js" crossorigin="use-credentials" defer></script>` +
`<script src="main.js" crossorigin="use-credentials" defer></script></body></html>`,
`<script src="runtime.js" type="module" crossorigin="use-credentials"></script>` +
`<script src="polyfills.js" type="module" crossorigin="use-credentials"></script>` +
`<script src="vendor.js" type="module" crossorigin="use-credentials"></script>` +
`<script src="main.js" type="module" crossorigin="use-credentials"></script></body></html>`,
);
await run.stop();
});
Expand All @@ -58,10 +58,10 @@ describe('Browser Builder crossOrigin', () => {
`<html><head><base href="/">` +
`<link rel="stylesheet" href="styles.css" crossorigin="anonymous"></head>` +
`<body><app-root></app-root>` +
`<script src="runtime.js" crossorigin="anonymous" defer></script>` +
`<script src="polyfills.js" crossorigin="anonymous" defer></script>` +
`<script src="vendor.js" crossorigin="anonymous" defer></script>` +
`<script src="main.js" crossorigin="anonymous" defer></script></body></html>`,
`<script src="runtime.js" type="module" crossorigin="anonymous"></script>` +
`<script src="polyfills.js" type="module" crossorigin="anonymous"></script>` +
`<script src="vendor.js" type="module" crossorigin="anonymous"></script>` +
`<script src="main.js" type="module" crossorigin="anonymous"></script></body></html>`,
);
await run.stop();
});
Expand All @@ -77,10 +77,10 @@ describe('Browser Builder crossOrigin', () => {
`<html><head><base href="/">` +
`<link rel="stylesheet" href="styles.css"></head>` +
`<body><app-root></app-root>` +
`<script src="runtime.js" defer></script>` +
`<script src="polyfills.js" defer></script>` +
`<script src="vendor.js" defer></script>` +
`<script src="main.js" defer></script></body></html>`,
`<script src="runtime.js" type="module"></script>` +
`<script src="polyfills.js" type="module"></script>` +
`<script src="vendor.js" type="module"></script>` +
`<script src="main.js" type="module"></script></body></html>`,
);
await run.stop();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ describe('Browser Builder index HTML processing', () => {
const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise());
expect(content).toBe(
`<html><head><base href="/"><link rel="stylesheet" href="styles.css"></head>` +
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
`<script src="polyfills.js" defer></script>` +
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
`<script src="polyfills.js" type="module"></script>` +
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
);
await run.stop();
});
Expand All @@ -60,9 +60,9 @@ describe('Browser Builder index HTML processing', () => {
expect(content).toBe(
`<html><head><base href="/"><link rel="stylesheet" href="styles.css"></head>` +
`<body><app-root></app-root>` +
`<script src="runtime.js" defer></script><script src="polyfills.js" defer></script>` +
`<script src="vendor.js" defer></script>` +
`<script src="main.js" defer></script></body></html>`,
`<script src="runtime.js" type="module"></script><script src="polyfills.js" type="module"></script>` +
`<script src="vendor.js" type="module"></script>` +
`<script src="main.js" type="module"></script></body></html>`,
);
await run.stop();
});
Expand All @@ -82,9 +82,9 @@ describe('Browser Builder index HTML processing', () => {
const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise());
expect(content).toBe(
`<html><head><title>&iacute;</title><base href="/"><link rel="stylesheet" href="styles.css"></head> ` +
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
`<script src="polyfills.js" defer></script>` +
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
`<script src="polyfills.js" type="module"></script>` +
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
);
await run.stop();
});
Expand All @@ -104,9 +104,9 @@ describe('Browser Builder index HTML processing', () => {
const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise());
expect(content).toBe(
`<html><head><base href="/"><%= csrf_meta_tags %><link rel="stylesheet" href="styles.css"></head> ` +
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
`<script src="polyfills.js" defer></script>` +
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
`<script src="polyfills.js" type="module"></script>` +
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
);
await run.stop();
});
Expand Down Expand Up @@ -152,9 +152,9 @@ describe('Browser Builder index HTML processing', () => {
const content = await host.read(normalize(outputIndexPath)).toPromise();
expect(virtualFs.fileBufferToString(content)).toBe(
`<html><head><base href="/"><%= csrf_meta_tags %><link rel="stylesheet" href="styles.css"></head> ` +
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
`<script src="polyfills.js" defer></script>` +
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
`<script src="polyfills.js" type="module"></script>` +
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
);
});

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

Expand Down Expand Up @@ -244,9 +244,9 @@ describe('Browser Builder index HTML processing', () => {
const content = await host.read(normalize(outputIndexPath)).toPromise();
expect(virtualFs.fileBufferToString(content)).toBe(
`<html><head><base href="/"><link rel="stylesheet" href="styles.css"></head> ` +
`<body><app-root></app-root><script src="runtime.js" defer></script>` +
`<script src="polyfills.js" defer></script>` +
`<script src="vendor.js" defer></script><script src="main.js" defer></script></body></html>`,
`<body><app-root></app-root><script src="runtime.js" type="module"></script>` +
`<script src="polyfills.js" type="module"></script>` +
`<script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>`,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ describe('Browser Builder scripts array', () => {
'renamed-lazy-script.js': 'pre-rename-lazy-script',
'main.js': 'input-script',
'index.html':
'<script src="runtime.js" defer></script>' +
'<script src="polyfills.js" defer></script>' +
'<script src="runtime.js" type="module"></script>' +
'<script src="polyfills.js" type="module"></script>' +
'<script src="scripts.js" defer></script>' +
'<script src="renamed-script.js" defer></script>' +
'<script src="vendor.js" defer></script>' +
'<script src="main.js" defer></script>',
'<script src="vendor.js" type="module"></script>' +
'<script src="main.js" type="module"></script>',
};

host.writeMultipleFiles(scripts);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe('Browser Builder service worker', () => {
hashTable: {
'/favicon.ico': '84161b857f5c547e3699ddfbffc6d8d737542e01',
'/assets/folder-asset.txt': '617f202968a6a81050aa617c2e28e1dca11ce8d4',
'/index.html': 'f0bea8ced1dfbeeb771a5f48651fbcff52a625eb',
'/index.html': '8964a35a8b850942f8d18ba919f248762ff3154d',
'/spectrum.png': '8d048ece46c0f3af4b598a95fd8e4709b631c3c0',
},
}),
Expand Down Expand Up @@ -222,7 +222,7 @@ describe('Browser Builder service worker', () => {
hashTable: {
'/foo/bar/favicon.ico': '84161b857f5c547e3699ddfbffc6d8d737542e01',
'/foo/bar/assets/folder-asset.txt': '617f202968a6a81050aa617c2e28e1dca11ce8d4',
'/foo/bar/index.html': 'f6650ac91428c6933dfe4c24079b3b15400da1ba',
'/foo/bar/index.html': '5c99755c1e7cfd1c8aba34ad1155afc72a288fec',
},
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,17 +305,22 @@ export function serveWebpackBrowser(
switchMap(({ browserOptions, webpackConfig, locale }) => {
if (browserOptions.index) {
const { scripts = [], styles = [], baseHref } = browserOptions;
const entrypoints = generateEntryPoints({ scripts, styles });

webpackConfig.plugins = [...(webpackConfig.plugins || [])];
const entrypoints = generateEntryPoints({
scripts,
styles,
// The below is needed as otherwise HMR for CSS will break.
// styles.js and runtime.js needs to be loaded as a non-module scripts as otherwise `document.currentScript` will be null.
// https://github.com/webpack-contrib/mini-css-extract-plugin/blob/90445dd1d81da0c10b9b0e8a17b417d0651816b8/src/hmr/hotModuleReplacement.js#L39
isHMREnabled: webpackConfig.devServer?.hot,
});

webpackConfig.plugins ??= [];
webpackConfig.plugins.push(
new IndexHtmlWebpackPlugin({
indexPath: path.resolve(workspaceRoot, getIndexInputFile(browserOptions.index)),
outputPath: getIndexOutputFile(browserOptions.index),
baseHref,
entrypoints,
moduleEntrypoints: [],
noModuleEntrypoints: [],
deployUrl: browserOptions.deployUrl,
sri: browserOptions.subresourceIntegrity,
postTransform: transforms.indexHtml,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export type LoadOutputFileFunctionType = (file: string) => Promise<string>;

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

export type Entrypoint = [name: string, isModule: boolean];

export interface AugmentIndexHtmlOptions {
/* Input contents */
html: string;
Expand All @@ -23,21 +25,16 @@ export interface AugmentIndexHtmlOptions {
crossOrigin?: CrossOriginValue;
/*
* Files emitted by the build.
* Js files will be added without 'nomodule' nor 'module'.
*/
files: FileInfo[];
/** Files that should be added using 'nomodule'. */
noModuleFiles?: FileInfo[];
/** Files that should be added using 'module'. */
moduleFiles?: FileInfo[];
/*
* Function that loads a file used.
* This allows us to use different routines within the IndexHtmlWebpackPlugin and
* when used without this plugin.
*/
loadOutputFile: LoadOutputFileFunctionType;
/** Used to sort the inseration of files in the HTML file */
entrypoints: string[];
entrypoints: Entrypoint[];
/** Used to set the document default locale */
lang?: string;
}
Expand All @@ -47,46 +44,34 @@ export interface FileInfo {
name: string;
extension: string;
}

/*
* Helper function used by the IndexHtmlWebpackPlugin.
* Can also be directly used by builder, e. g. in order to generate an index.html
* after processing several configurations in order to build different sets of
* bundles for differential serving.
*/
export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise<string> {
const {
loadOutputFile,
files,
noModuleFiles = [],
moduleFiles = [],
entrypoints,
sri,
deployUrl = '',
lang,
baseHref,
html,
} = params;
const { loadOutputFile, files, entrypoints, sri, deployUrl = '', lang, baseHref, html } = params;

let { crossOrigin = 'none' } = params;
if (sri && crossOrigin === 'none') {
crossOrigin = 'anonymous';
}

const stylesheets = new Set<string>();
const scripts = new Set<string>();
const scripts = new Map</** file name */ string, /** isModule */ boolean>();

// Sort files in the order we want to insert them by entrypoint and dedupes duplicates
const mergedFiles = [...moduleFiles, ...noModuleFiles, ...files];
for (const entrypoint of entrypoints) {
for (const { extension, file, name } of mergedFiles) {
if (name !== entrypoint) {
// Sort files in the order we want to insert them by entrypoint
for (const [entrypoint, isModule] of entrypoints) {
for (const { extension, file, name } of files) {
if (name !== entrypoint || scripts.has(file) || stylesheets.has(file)) {
continue;
}

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

let scriptTags: string[] = [];
for (const script of scripts) {
const attrs = [`src="${deployUrl}${script}"`];

if (crossOrigin !== 'none') {
attrs.push(`crossorigin="${crossOrigin}"`);
}
for (const [src, isModule] of scripts) {
const attrs = [`src="${deployUrl}${src}"`];

// We want to include nomodule or module when a file is not common amongs all
// such as runtime.js
const scriptPredictor = ({ file }: FileInfo): boolean => file === script;
if (!files.some(scriptPredictor)) {
// in some cases for differential loading file with the same name is available in both
// nomodule and module such as scripts.js
// we shall not add these attributes if that's the case
const isNoModuleType = noModuleFiles.some(scriptPredictor);
const isModuleType = moduleFiles.some(scriptPredictor);

if (isNoModuleType && !isModuleType) {
attrs.push('nomodule', 'defer');
} else if (isModuleType && !isNoModuleType) {
attrs.push('type="module"');
} else {
attrs.push('defer');
}
// This is also need for non entry-points as they may contain problematic code.
if (isModule) {
attrs.push('type="module"');
} else {
attrs.push('defer');
}

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

if (sri) {
const content = await loadOutputFile(script);
const content = await loadOutputFile(src);
attrs.push(generateSriAttributes(content));
}

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

let linkTags: string[] = [];
for (const stylesheet of stylesheets) {
const attrs = [`rel="stylesheet"`, `href="${deployUrl}${stylesheet}"`];
for (const src of stylesheets) {
const attrs = [`rel="stylesheet"`, `href="${deployUrl}${src}"`];

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

if (sri) {
const content = await loadOutputFile(stylesheet);
const content = await loadOutputFile(src);
attrs.push(generateSriAttributes(content));
}

Expand Down
Loading

0 comments on commit f53bf9d

Please sign in to comment.