From 397fe6820f560141d442501f3a6bf273aec24923 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 20 Sep 2018 21:43:18 -0400 Subject: [PATCH 1/5] feat(@angular-devkit/build-angular): conditional ES2015 polyfill loading --- packages/angular/cli/lib/config/schema.json | 5 +++ .../angular-cli-files/models/build-options.ts | 1 + .../models/es2015-jit-polyfills.js | 8 +++++ .../models/es2015-polyfills.js | 21 ++++++++++++ .../models/webpack-configs/browser.ts | 3 +- .../models/webpack-configs/common.ts | 11 ++++++ .../plugins/index-html-webpack-plugin.ts | 34 +++++++++++++++---- .../utilities/package-chunk-sort.ts | 2 +- .../build_angular/src/browser/schema.d.ts | 5 +++ .../build_angular/src/browser/schema.json | 5 +++ 10 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 packages/angular_devkit/build_angular/src/angular-cli-files/models/es2015-jit-polyfills.js create mode 100644 packages/angular_devkit/build_angular/src/angular-cli-files/models/es2015-polyfills.js diff --git a/packages/angular/cli/lib/config/schema.json b/packages/angular/cli/lib/config/schema.json index 7006c36b84ca..bc31fb6c324c 100644 --- a/packages/angular/cli/lib/config/schema.json +++ b/packages/angular/cli/lib/config/schema.json @@ -863,6 +863,11 @@ "$ref": "#/definitions/targetOptions/definitions/browser/definitions/budget" }, "default": [] + }, + "es5BrowserSupport": { + "description": "Enables conditionally loaded ES2015 polyfills.", + "type": "boolean", + "default": false } }, "additionalProperties": false, diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts index 80a9a17a515b..eaabcc68ae91 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts @@ -58,6 +58,7 @@ export interface BuildOptions { statsJson: boolean; forkTypeChecker: boolean; profile?: boolean; + es5BrowserSupport?: boolean; main: string; index: string; diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/es2015-jit-polyfills.js b/packages/angular_devkit/build_angular/src/angular-cli-files/models/es2015-jit-polyfills.js new file mode 100644 index 000000000000..0e68685db11f --- /dev/null +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/es2015-jit-polyfills.js @@ -0,0 +1,8 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import 'core-js/es6/reflect'; diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/es2015-polyfills.js b/packages/angular_devkit/build_angular/src/angular-cli-files/models/es2015-polyfills.js new file mode 100644 index 000000000000..08e69267c5e8 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/es2015-polyfills.js @@ -0,0 +1,21 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/weak-map'; +import 'core-js/es6/set'; diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/browser.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/browser.ts index 7f1153dbd010..8bfc0679ef51 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/browser.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/browser.ts @@ -44,6 +44,7 @@ export function getBrowserConfig(wco: WebpackConfigOptions) { entrypoints: generateEntryPoints(buildOptions), deployUrl: buildOptions.deployUrl, sri: buildOptions.subresourceIntegrity, + noModuleEntrypoints: ['es2015-polyfills'], })); } @@ -112,7 +113,7 @@ export function getBrowserConfig(wco: WebpackConfigOptions) { const moduleName = module.nameForCondition ? module.nameForCondition() : ''; return /[\\/]node_modules[\\/]/.test(moduleName) - && !chunks.some(({ name }) => name === 'polyfills' + && !chunks.some(({ name }) => name === 'polyfills' || name === 'es2015-polyfills' || globalStylesBundleNames.includes(name)); }, }, diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts index bab2266a41f7..7225fcb0381a 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts @@ -53,6 +53,10 @@ export function getCommonConfig(wco: WebpackConfigOptions) { entryPoints['main'] = [path.resolve(root, buildOptions.main)]; } + if (buildOptions.es5BrowserSupport) { + entryPoints['es2015-polyfills'] = [path.join(__dirname, '..', 'es2015-polyfills.js')]; + } + if (buildOptions.polyfills) { entryPoints['polyfills'] = [path.resolve(root, buildOptions.polyfills)]; } @@ -62,6 +66,13 @@ export function getCommonConfig(wco: WebpackConfigOptions) { ...(entryPoints['polyfills'] || []), path.join(__dirname, '..', 'jit-polyfills.js'), ]; + + if (buildOptions.es5BrowserSupport) { + entryPoints['es2015-polyfills'] = [ + ...entryPoints['es2015-polyfills'], + path.join(__dirname, '..', 'es2015-jit-polyfills.js'), + ]; + } } if (buildOptions.profile) { diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts index be4c1497cd73..a06ed3964bd5 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts @@ -18,6 +18,7 @@ export interface IndexHtmlWebpackPluginOptions { entrypoints: string[]; deployUrl?: string; sri: boolean; + noModuleEntrypoints: string[]; } function readFile(filename: string, compilation: compilation.Compilation): Promise { @@ -53,6 +54,7 @@ export class IndexHtmlWebpackPlugin { input: 'index.html', output: 'index.html', entrypoints: ['polyfills', 'main'], + noModuleEntrypoints: [], sri: false, ...options, }; @@ -67,14 +69,26 @@ export class IndexHtmlWebpackPlugin { // Get all files for selected entrypoints - let unfilteredSortedFiles: string[] = []; + const unfilteredSortedFiles: string[] = []; + const noModuleFiles = new Set(); + const otherFiles = new Set(); for (const entryName of this._options.entrypoints) { const entrypoint = compilation.entrypoints.get(entryName); if (entrypoint && entrypoint.getFiles) { - unfilteredSortedFiles = unfilteredSortedFiles.concat(entrypoint.getFiles() || []); + const files: string[] = entrypoint.getFiles() || []; + unfilteredSortedFiles.push(...files); + + if (this._options.noModuleEntrypoints.includes(entryName)) { + files.forEach(file => noModuleFiles.add(file)); + } else { + files.forEach(file => otherFiles.add(file)); + } } } + // Clean out files that are used in all types of entrypoints + otherFiles.forEach(file => noModuleFiles.delete(file)); + // Filter files const existingFiles = new Set(); const stylesheets: string[] = []; @@ -137,25 +151,31 @@ export class IndexHtmlWebpackPlugin { // Inject into the html const indexSource = new ReplaceSource(new RawSource(inputContent), this._options.input); - const scriptElements = treeAdapter.createDocumentFragment(); + let scriptElements = ''; for (const script of scripts) { - const attrs = [ + const attrs: { name: string, value: string | null }[] = [ { name: 'type', value: 'text/javascript' }, { name: 'src', value: (this._options.deployUrl || '') + script }, ]; + if (noModuleFiles.has(script)) { + attrs.push({ name: 'nomodule', value: null }); + } + if (this._options.sri) { const content = compilation.assets[script].source(); attrs.push(...this._generateSriAttributes(content)); } - const element = treeAdapter.createElement('script', undefined, attrs); - treeAdapter.appendChild(scriptElements, element); + const attributes = attrs + .map(attr => attr.value === null ? attr.name : `${attr.name}="${attr.value}"`) + .join(' '); + scriptElements += ``; } indexSource.insert( scriptInsertionPoint, - parse5.serialize(scriptElements, { treeAdapter }), + scriptElements, ); // Adjust base href if specified diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/package-chunk-sort.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/package-chunk-sort.ts index 2e443db69a22..d6ccf6e569ac 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/package-chunk-sort.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/package-chunk-sort.ts @@ -11,7 +11,7 @@ import { normalizeExtraEntryPoints } from '../models/webpack-configs/utils'; export function generateEntryPoints( appConfig: { styles: ExtraEntryPoint[], scripts: ExtraEntryPoint[] }, ) { - const entryPoints = ['polyfills', 'sw-register']; + const entryPoints = ['es2015-polyfills', 'polyfills', 'sw-register']; // Add all styles/scripts, except lazy-loaded ones. [ diff --git a/packages/angular_devkit/build_angular/src/browser/schema.d.ts b/packages/angular_devkit/build_angular/src/browser/schema.d.ts index 1a2bd717ea52..de31a4eef072 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.d.ts +++ b/packages/angular_devkit/build_angular/src/browser/schema.d.ts @@ -236,6 +236,11 @@ export interface BrowserBuilderSchema { * Output profile events for Chrome profiler. */ profile: boolean; + + /** + * Enables conditionally loaded IE9-11 polyfills. + */ + es5BrowserSupport: boolean; } export type OptimizationOptions = boolean | OptimizationObject; diff --git a/packages/angular_devkit/build_angular/src/browser/schema.json b/packages/angular_devkit/build_angular/src/browser/schema.json index 817379b9fb42..9fff2eea5f6d 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/browser/schema.json @@ -299,6 +299,11 @@ "type": "boolean", "description": "Output profile events for Chrome profiler.", "default": false + }, + "es5BrowserSupport": { + "description": "Enables conditionally loaded ES2015 polyfills.", + "type": "boolean", + "default": false } }, "additionalProperties": false, From a667407fdcf32c08e26d021bae1a9d0c8183436f Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 20 Sep 2018 21:43:50 -0400 Subject: [PATCH 2/5] fix(@schematics/angular): remove redundant ES2015 polyfills --- .../application/files/src/polyfills.ts | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/packages/schematics/angular/application/files/src/polyfills.ts b/packages/schematics/angular/application/files/src/polyfills.ts index a6d34ea67a3b..75d639398924 100644 --- a/packages/schematics/angular/application/files/src/polyfills.ts +++ b/packages/schematics/angular/application/files/src/polyfills.ts @@ -18,31 +18,9 @@ * BROWSER POLYFILLS */ -/** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills. - * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot - */ - -// import 'core-js/es6/symbol'; -// import 'core-js/es6/object'; -// import 'core-js/es6/function'; -// import 'core-js/es6/parse-int'; -// import 'core-js/es6/parse-float'; -// import 'core-js/es6/number'; -// import 'core-js/es6/math'; -// import 'core-js/es6/string'; -// import 'core-js/es6/date'; -// import 'core-js/es6/array'; -// import 'core-js/es6/regexp'; -// import 'core-js/es6/map'; -// import 'core-js/es6/weak-map'; -// import 'core-js/es6/set'; - /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. -/** IE10 and IE11 requires the following for the Reflect API. */ -// import 'core-js/es6/reflect'; - /** * Web Animations `@angular/platform-browser/animations` * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. From 2955b7b09faaadd9fdd85c9807cf1b5a59358ae0 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 20 Sep 2018 22:03:13 -0400 Subject: [PATCH 3/5] feat(@schematics/angular): enabled conditional ES2015 polyfills for new apps --- packages/schematics/angular/application/index.ts | 1 + packages/schematics/angular/utility/workspace-models.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts index cc2af68a0ff7..483a44902698 100644 --- a/packages/schematics/angular/application/index.ts +++ b/packages/schematics/angular/application/index.ts @@ -207,6 +207,7 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace `${projectRoot}src/styles.${options.style}`, ], scripts: [], + es5BrowserSupport: true, }, configurations: { production: { diff --git a/packages/schematics/angular/utility/workspace-models.ts b/packages/schematics/angular/utility/workspace-models.ts index 051354129d01..d697a4bb121d 100644 --- a/packages/schematics/angular/utility/workspace-models.ts +++ b/packages/schematics/angular/utility/workspace-models.ts @@ -60,6 +60,7 @@ export interface BrowserBuilderOptions extends BrowserBuilderBaseOptions { maximumWarning?: string; maximumError?: string; }[]; + es5BrowserSupport?: boolean; } export interface ServeBuilderOptions { From f704ecb8e432aff238a874ecfaf8c5881e8153d3 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Fri, 21 Sep 2018 11:10:19 -0400 Subject: [PATCH 4/5] test: update CLI E2E with additional script element --- tests/legacy-cli/e2e/tests/basic/scripts-array.ts | 1 + tests/legacy-cli/e2e/tests/basic/styles-array.ts | 1 + tests/legacy-cli/e2e/tests/build/polyfills.ts | 1 + tests/legacy-cli/e2e/tests/build/styles/extract-css.ts | 2 ++ tests/legacy-cli/e2e/tests/third-party/bootstrap.ts | 2 ++ 5 files changed, 7 insertions(+) diff --git a/tests/legacy-cli/e2e/tests/basic/scripts-array.ts b/tests/legacy-cli/e2e/tests/basic/scripts-array.ts index 2bea2dabfcd2..44efea1fd6f8 100644 --- a/tests/legacy-cli/e2e/tests/basic/scripts-array.ts +++ b/tests/legacy-cli/e2e/tests/basic/scripts-array.ts @@ -54,6 +54,7 @@ export default function () { // index.html lists the right bundles .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` + diff --git a/tests/legacy-cli/e2e/tests/basic/styles-array.ts b/tests/legacy-cli/e2e/tests/basic/styles-array.ts index 4815c503d12b..4b6457b220ed 100644 --- a/tests/legacy-cli/e2e/tests/basic/styles-array.ts +++ b/tests/legacy-cli/e2e/tests/basic/styles-array.ts @@ -43,6 +43,7 @@ export default function () { `)) .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` + diff --git a/tests/legacy-cli/e2e/tests/build/polyfills.ts b/tests/legacy-cli/e2e/tests/build/polyfills.ts index cc0e147afd4e..d760f4634047 100644 --- a/tests/legacy-cli/e2e/tests/build/polyfills.ts +++ b/tests/legacy-cli/e2e/tests/build/polyfills.ts @@ -27,6 +27,7 @@ export default async function () { await expectFileToMatch('dist/test-project/polyfills.js', 'zone.js'); expectFileToMatch('dist/test-project/index.html', oneLineTrim` + `); } diff --git a/tests/legacy-cli/e2e/tests/build/styles/extract-css.ts b/tests/legacy-cli/e2e/tests/build/styles/extract-css.ts index 35c993cf332c..590f45657c37 100644 --- a/tests/legacy-cli/e2e/tests/build/styles/extract-css.ts +++ b/tests/legacy-cli/e2e/tests/build/styles/extract-css.ts @@ -48,6 +48,7 @@ export default function () { `))) .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` + @@ -63,6 +64,7 @@ export default function () { // index.html lists the right bundles .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` + diff --git a/tests/legacy-cli/e2e/tests/third-party/bootstrap.ts b/tests/legacy-cli/e2e/tests/third-party/bootstrap.ts index 847bce3bba42..871b3c428189 100644 --- a/tests/legacy-cli/e2e/tests/third-party/bootstrap.ts +++ b/tests/legacy-cli/e2e/tests/third-party/bootstrap.ts @@ -23,6 +23,7 @@ export default function() { .then(() => expectFileToMatch('dist/test-project/styles.css', '* Bootstrap')) .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` + @@ -39,6 +40,7 @@ export default function() { .then(() => expectFileToMatch('dist/test-project/styles.css', '* Bootstrap')) .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` + From dd4c381498344cf9de8ca4d2fc95237931934d0e Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Fri, 21 Sep 2018 13:46:44 -0400 Subject: [PATCH 5/5] test: additional tests for conditional ES2015 polyfills --- tests/legacy-cli/e2e/tests/build/polyfills.ts | 3 +- tests/legacy-cli/e2e/tests/misc/support-ie.ts | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/legacy-cli/e2e/tests/misc/support-ie.ts diff --git a/tests/legacy-cli/e2e/tests/build/polyfills.ts b/tests/legacy-cli/e2e/tests/build/polyfills.ts index d760f4634047..29b1930b199a 100644 --- a/tests/legacy-cli/e2e/tests/build/polyfills.ts +++ b/tests/legacy-cli/e2e/tests/build/polyfills.ts @@ -15,6 +15,7 @@ export default async function () { await expectFileToMatch('dist/test-project/polyfills.js', 'zone.js'); expectFileToMatch('dist/test-project/index.html', oneLineTrim` + `); const jitPolyfillSize = await getFileSize('dist/test-project/polyfills.js'); @@ -27,7 +28,7 @@ export default async function () { await expectFileToMatch('dist/test-project/polyfills.js', 'zone.js'); expectFileToMatch('dist/test-project/index.html', oneLineTrim` - + `); } diff --git a/tests/legacy-cli/e2e/tests/misc/support-ie.ts b/tests/legacy-cli/e2e/tests/misc/support-ie.ts new file mode 100644 index 000000000000..2bbe31b19337 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/support-ie.ts @@ -0,0 +1,32 @@ +import { oneLineTrim } from 'common-tags'; +import { expectFileNotToExist, expectFileToMatch } from '../../utils/fs'; +import { ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; + +export default async function () { + await updateJsonFile('angular.json', workspaceJson => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.es5BrowserSupport = false; + }); + + await ng('build'); + await expectFileNotToExist('dist/test-project/es2015-polyfills.js'); + await expectFileToMatch('dist/test-project/index.html', oneLineTrim` + + + + + + `); + + await ng('build', `--es5BrowserSupport`); + await expectFileToMatch('dist/test-project/es2015-polyfills.js', 'core-js'); + await expectFileToMatch('dist/test-project/index.html', oneLineTrim` + + + + + + + `); +}