Skip to content

Conditional ES5 Browser Polyfill Loading #13403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/angular/cli/lib/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface BuildOptions {
statsJson: boolean;
forkTypeChecker: boolean;
profile?: boolean;
es5BrowserSupport?: boolean;

main: string;
index: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be mistaken, but it's my understanding that polyfills are moved into an internal file. I often find that I also need to import some of the es7 modules (for example to support Object.entries) into polifyls. Having them in an internal file won't allow me adding additional polifylls only required for legacy browsers. It'd be my preference if there was a new file generated by CLI and included in the app: polifyls.legacy.ts or something like that.

Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function getBrowserConfig(wco: WebpackConfigOptions) {
entrypoints: generateEntryPoints(buildOptions),
deployUrl: buildOptions.deployUrl,
sri: buildOptions.subresourceIntegrity,
noModuleEntrypoints: ['es2015-polyfills'],
}));
}

Expand Down Expand Up @@ -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));
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)];
}
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface IndexHtmlWebpackPluginOptions {
entrypoints: string[];
deployUrl?: string;
sri: boolean;
noModuleEntrypoints: string[];
}

function readFile(filename: string, compilation: compilation.Compilation): Promise<string> {
Expand Down Expand Up @@ -53,6 +54,7 @@ export class IndexHtmlWebpackPlugin {
input: 'index.html',
output: 'index.html',
entrypoints: ['polyfills', 'main'],
noModuleEntrypoints: [],
sri: false,
...options,
};
Expand All @@ -67,14 +69,26 @@ export class IndexHtmlWebpackPlugin {


// Get all files for selected entrypoints
let unfilteredSortedFiles: string[] = [];
const unfilteredSortedFiles: string[] = [];
const noModuleFiles = new Set<string>();
const otherFiles = new Set<string>();
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<string>();
const stylesheets: string[] = [];
Expand Down Expand Up @@ -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 += `<script ${attributes}></script>`;
}

indexSource.insert(
scriptInsertionPoint,
parse5.serialize(scriptElements, { treeAdapter }),
scriptElements,
);

// Adjust base href if specified
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
[
Expand Down
5 changes: 5 additions & 0 deletions packages/angular_devkit/build_angular/src/browser/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions packages/angular_devkit/build_angular/src/browser/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
22 changes: 0 additions & 22 deletions packages/schematics/angular/application/files/src/polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions packages/schematics/angular/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace
`${projectRoot}src/styles.${options.style}`,
],
scripts: [],
es5BrowserSupport: true,
},
configurations: {
production: {
Expand Down
1 change: 1 addition & 0 deletions packages/schematics/angular/utility/workspace-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface BrowserBuilderOptions extends BrowserBuilderBaseOptions {
maximumWarning?: string;
maximumError?: string;
}[];
es5BrowserSupport?: boolean;
}

export interface ServeBuilderOptions {
Expand Down
1 change: 1 addition & 0 deletions tests/legacy-cli/e2e/tests/basic/scripts-array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default function () {
// index.html lists the right bundles
.then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim`
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="es2015-polyfills.js" nomodule></script>
<script type="text/javascript" src="polyfills.js"></script>
<script type="text/javascript" src="scripts.js"></script>
<script type="text/javascript" src="renamed-script.js"></script>
Expand Down
1 change: 1 addition & 0 deletions tests/legacy-cli/e2e/tests/basic/styles-array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default function () {
`))
.then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim`
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="es2015-polyfills.js" nomodule></script>
<script type="text/javascript" src="polyfills.js"></script>
<script type="text/javascript" src="vendor.js"></script>
<script type="text/javascript" src="main.js"></script>
Expand Down
2 changes: 2 additions & 0 deletions tests/legacy-cli/e2e/tests/build/polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default async function () {
await expectFileToMatch('dist/test-project/polyfills.js', 'zone.js');
expectFileToMatch('dist/test-project/index.html', oneLineTrim`
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="es2015-polyfills.js" nomodule></script>
<script type="text/javascript" src="polyfills.js"></script>
`);
const jitPolyfillSize = await getFileSize('dist/test-project/polyfills.js');
Expand All @@ -27,6 +28,7 @@ export default async function () {
await expectFileToMatch('dist/test-project/polyfills.js', 'zone.js');
expectFileToMatch('dist/test-project/index.html', oneLineTrim`
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="es2015-polyfills.js" nomodule></script>
<script type="text/javascript" src="polyfills.js"></script>
`);
}
2 changes: 2 additions & 0 deletions tests/legacy-cli/e2e/tests/build/styles/extract-css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default function () {
`)))
.then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim`
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="es2015-polyfills.js" nomodule></script>
<script type="text/javascript" src="polyfills.js"></script>
<script type="text/javascript" src="vendor.js"></script>
<script type="text/javascript" src="main.js"></script>
Expand All @@ -63,6 +64,7 @@ export default function () {
// index.html lists the right bundles
.then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim`
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="es2015-polyfills.js" nomodule></script>
<script type="text/javascript" src="polyfills.js"></script>
<script type="text/javascript" src="styles.js"></script>
<script type="text/javascript" src="renamed-style.js"></script>
Expand Down
32 changes: 32 additions & 0 deletions tests/legacy-cli/e2e/tests/misc/support-ie.ts
Original file line number Diff line number Diff line change
@@ -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`
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="polyfills.js"></script>
<script type="text/javascript" src="styles.js"></script>
<script type="text/javascript" src="vendor.js"></script>
<script type="text/javascript" src="main.js"></script>
`);

await ng('build', `--es5BrowserSupport`);
await expectFileToMatch('dist/test-project/es2015-polyfills.js', 'core-js');
await expectFileToMatch('dist/test-project/index.html', oneLineTrim`
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="es2015-polyfills.js" nomodule></script>
<script type="text/javascript" src="polyfills.js"></script>
<script type="text/javascript" src="styles.js"></script>
<script type="text/javascript" src="vendor.js"></script>
<script type="text/javascript" src="main.js"></script>
`);
}
2 changes: 2 additions & 0 deletions tests/legacy-cli/e2e/tests/third-party/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function() {
.then(() => expectFileToMatch('dist/test-project/styles.css', '* Bootstrap'))
.then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim`
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="es2015-polyfills.js" nomodule></script>
<script type="text/javascript" src="polyfills.js"></script>
<script type="text/javascript" src="scripts.js"></script>
<script type="text/javascript" src="vendor.js"></script>
Expand All @@ -39,6 +40,7 @@ export default function() {
.then(() => expectFileToMatch('dist/test-project/styles.css', '* Bootstrap'))
.then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim`
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="es2015-polyfills.js" nomodule></script>
<script type="text/javascript" src="polyfills.js"></script>
<script type="text/javascript" src="scripts.js"></script>
<script type="text/javascript" src="main.js"></script>
Expand Down