From 5178f9c01749af891fe4a0c1dc150e77051c26f5 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Sat, 8 Jun 2019 17:12:33 +0200 Subject: [PATCH 1/3] fix(@angular-devkit/build-angular): nomodule polyfill for Safari 10.1 and iOS Safari 10.3 The nomodule polyfill needs to be loaded prior to any script and be outside of webpack compilation because otherwise webpack will cause the script to be wrapped in `window["webpackJsonp"]` which causes it to fail. This polyfill will only be injected when the either Safari 10.1 or iOS Safari 10.3 support is required, which is based on the browsers defined in browserslist file. Fixes #14680 --- .../models/safari-nomodule.js | 20 +- .../models/webpack-configs/common.ts | 54 +++-- .../models/webpack-configs/utils.ts | 5 - .../utilities/package-chunk-sort.ts | 1 + .../build_angular/src/browser/index.ts | 12 +- .../src/utils/build-browser-features.ts | 58 ++++++ .../src/utils/build-browser-features_spec.ts | 195 ++++++++++++++++++ .../src/utils/differential-loading.ts | 30 --- .../src/utils/differential-loading_spec.ts | 56 ----- .../build_angular/src/utils/index.ts | 3 +- .../src/utils/webpack-browser-config.ts | 8 +- .../differential_loading_spec_large.ts | 2 - .../e2e/tests/misc/support-safari-10.1.ts | 42 ++++ 13 files changed, 348 insertions(+), 138 deletions(-) create mode 100644 packages/angular_devkit/build_angular/src/utils/build-browser-features.ts create mode 100644 packages/angular_devkit/build_angular/src/utils/build-browser-features_spec.ts delete mode 100644 packages/angular_devkit/build_angular/src/utils/differential-loading.ts delete mode 100644 packages/angular_devkit/build_angular/src/utils/differential-loading_spec.ts create mode 100644 tests/legacy-cli/e2e/tests/misc/support-safari-10.1.ts diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/safari-nomodule.js b/packages/angular_devkit/build_angular/src/angular-cli-files/models/safari-nomodule.js index 89db355822ae..1d2dbd1436ff 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/safari-nomodule.js +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/safari-nomodule.js @@ -1,22 +1,8 @@ -/** - * Safari 10.1 supports modules, but does not support the `nomodule` attribute - it will - * load - * - * Again: this will **not** prevent inline script, e.g.: - * . - * - * This workaround is possible because Safari supports the non-standard 'beforeload' event. - * This allows us to trap the module and nomodule load. - * - * Note also that `nomodule` is supported in later versions of Safari - it's just 10.1 that - * omits this attribute. - */ -(function() { +(function () { var check = document.createElement('script'); if (!('noModule' in check) && 'onbeforeload' in check) { var support = false; - document.addEventListener('beforeload', function(e) { + document.addEventListener('beforeload', function (e) { if (e.target === check) { support = true; } else if (!e.target.hasAttribute('nomodule') || !support) { @@ -30,4 +16,4 @@ document.head.appendChild(check); check.remove(); } -}()); \ No newline at end of file +}()); 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 7e9402759b0c..ec8318348e78 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 @@ -18,8 +18,8 @@ import { debug, } from 'webpack'; import { RawSource } from 'webpack-sources'; -import { AssetPatternClass } from '../../../browser/schema'; -import { isEs5SupportNeeded } from '../../../utils/differential-loading'; +import { AssetPatternClass, ExtraEntryPoint } from '../../../browser/schema'; +import { BuildBrowserFeatures } from '../../../utils/build-browser-features'; import { BundleBudgetPlugin } from '../../plugins/bundle-budget'; import { CleanCssWebpackPlugin } from '../../plugins/cleancss-webpack-plugin'; import { NamedLazyChunksPlugin } from '../../plugins/named-chunks-plugin'; @@ -40,7 +40,7 @@ export const buildOptimizerLoader: string = g['_DevKitIsLocal'] // tslint:disable-next-line:no-big-function export function getCommonConfig(wco: WebpackConfigOptions): Configuration { - const { root, projectRoot, buildOptions } = wco; + const { root, projectRoot, buildOptions, tsConfig } = wco; const { styles: stylesOptimization, scripts: scriptsOptimization } = buildOptions.optimization; const { styles: stylesSourceMap, @@ -67,25 +67,39 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { } if (wco.buildOptions.platform !== 'server') { - const es5Polyfills = path.join(__dirname, '..', 'es5-polyfills.js'); - const es5JitPolyfills = path.join(__dirname, '..', 'es5-jit-polyfills.js'); - if (targetInFileName) { - // For differential loading we don't need to have 2 polyfill bundles - if (buildOptions.scriptTargetOverride === ScriptTarget.ES2015) { - entryPoints['polyfills'] = [path.join(__dirname, '..', 'safari-nomodule.js')]; - } else { - entryPoints['polyfills'] = [es5Polyfills]; - if (!buildOptions.aot) { - entryPoints['polyfills'].push(es5JitPolyfills); + const buildBrowserFeatures = new BuildBrowserFeatures( + projectRoot, + tsConfig.options.target || ScriptTarget.ES5, + ); + if ((buildOptions.scriptTargetOverride || tsConfig.options.target) === ScriptTarget.ES5) { + if (buildOptions.es5BrowserSupport || + ( + buildOptions.es5BrowserSupport === undefined && + buildBrowserFeatures.isEs5SupportNeeded() + ) + ) { + // The nomodule polyfill needs to be inject prior to any script and be + // outside of webpack compilation because otherwise webpack will cause the + // script to be wrapped in window["webpackJsonp"] which causes this to fail. + if (buildBrowserFeatures.isNoModulePolyfillNeeded()) { + const noModuleScript: ExtraEntryPoint = { + bundleName: 'polyfills-nomodule-es5', + input: path.join(__dirname, '..', 'safari-nomodule.js'), + }; + buildOptions.scripts = buildOptions.scripts + ? [...buildOptions.scripts, noModuleScript] + : [noModuleScript]; } - } - } else { - // For NON differential loading we want to have 2 polyfill bundles - if (buildOptions.es5BrowserSupport - || (buildOptions.es5BrowserSupport === undefined && isEs5SupportNeeded(projectRoot))) { - entryPoints['polyfills-es5'] = [es5Polyfills]; + + // For differential loading we don't need to generate a seperate polyfill file + // because they will be loaded exclusivly based on module and nomodule + const polyfillsChunkName = buildBrowserFeatures.isDifferentialLoadingNeeded() + ? 'polyfills' + : 'polyfills-es5'; + + entryPoints[polyfillsChunkName] = [path.join(__dirname, '..', 'es5-polyfills.js')]; if (!buildOptions.aot) { - entryPoints['polyfills-es5'].push(es5JitPolyfills); + entryPoints[polyfillsChunkName].push(path.join(__dirname, '..', 'es5-jit-polyfills.js')); } } } diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts index 2262bde39378..e0afd2ad53ef 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts @@ -8,16 +8,11 @@ // tslint:disable // TODO: cleanup this file, it's copied as is from Angular CLI. -import * as path from 'path'; import { basename, normalize } from '@angular-devkit/core'; import { ExtraEntryPoint, ExtraEntryPointClass } from '../../../browser/schema'; import { SourceMapDevToolPlugin } from 'webpack'; import { ScriptTarget } from 'typescript'; -export const ngAppResolve = (resolvePath: string): string => { - return path.resolve(process.cwd(), resolvePath); -}; - export interface HashFormat { chunk: string; extract: string; 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 5f45c58c7af5..3b2262575d70 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 @@ -26,6 +26,7 @@ export function generateEntryPoints( }; const entryPoints = [ + 'polyfills-nomodule-es5', 'polyfills-es5', 'polyfills', 'sw-register', diff --git a/packages/angular_devkit/build_angular/src/browser/index.ts b/packages/angular_devkit/build_angular/src/browser/index.ts index b5d75281ee68..76bb388e6295 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -57,7 +57,7 @@ import { statsWarningsToString, } from '../angular-cli-files/utilities/stats'; import { ExecutionTransformer } from '../transforms'; -import { deleteOutputDir, isEs5SupportNeeded } from '../utils'; +import { BuildBrowserFeatures, deleteOutputDir } from '../utils'; import { Version } from '../utils/version'; import { generateBrowserWebpackConfigFromContext } from '../utils/webpack-browser-config'; import { Schema as BrowserBuilderSchema } from './schema'; @@ -202,9 +202,13 @@ export function buildWebpackBrowser( const tsConfigPath = path.resolve(getSystemPath(workspace.root), options.tsConfig); const tsConfig = readTsconfig(tsConfigPath); - if (isEs5SupportNeeded(projectRoot) && - tsConfig.options.target !== ScriptTarget.ES5 && - tsConfig.options.target !== ScriptTarget.ES2015) { + const target = tsConfig.options.target || ScriptTarget.ES5; + const buildBrowserFeatures = new BuildBrowserFeatures( + getSystemPath(projectRoot), + target, + ); + + if (target > ScriptTarget.ES2015 && buildBrowserFeatures.isDifferentialLoadingNeeded()) { context.logger.warn(tags.stripIndent` WARNING: Using differential loading with targets ES5 and ES2016 or higher may cause problems. Browsers with support for ES2015 will load the ES2016+ scripts diff --git a/packages/angular_devkit/build_angular/src/utils/build-browser-features.ts b/packages/angular_devkit/build_angular/src/utils/build-browser-features.ts new file mode 100644 index 000000000000..b330dad94a60 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/build-browser-features.ts @@ -0,0 +1,58 @@ +/** + * @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 * as browserslist from 'browserslist'; +import { feature, features } from 'caniuse-lite'; +import * as ts from 'typescript'; + +export class BuildBrowserFeatures { + private readonly _supportedBrowsers: string[]; + private readonly _es6TargetOrLater: boolean; + + constructor( + private projectRoot: string, + private scriptTarget: ts.ScriptTarget, + ) { + this._supportedBrowsers = browserslist(undefined, { path: this.projectRoot }); + this._es6TargetOrLater = this.scriptTarget > ts.ScriptTarget.ES5; + } + + /** + * True, when one or more browsers requires ES5 + * support and the scirpt target is ES2015 or greater. + */ + isDifferentialLoadingNeeded(): boolean { + return this._es6TargetOrLater && this.isEs5SupportNeeded(); + } + + /** + * True, when one or more browsers requires ES5 support + */ + isEs5SupportNeeded(): boolean { + return !caniuse.isSupported('es6-module', this._supportedBrowsers.join(', ')); + } + + /** + * Safari 10.1 and iOS Safari 10.3 supports modules, + * but does not support the `nomodule` attribute. + * While return `true`, when support for Safari 10.1 and iOS Safari 10.3 + * is required and in differential loading is enabled. + */ + isNoModulePolyfillNeeded(): boolean { + if (!this.isDifferentialLoadingNeeded()) { + return false; + } + + const safariBrowsers = [ + 'safari 10.1', + 'ios_saf 10.3', + ]; + + return this._supportedBrowsers.some(browser => safariBrowsers.includes(browser)); + } +} diff --git a/packages/angular_devkit/build_angular/src/utils/build-browser-features_spec.ts b/packages/angular_devkit/build_angular/src/utils/build-browser-features_spec.ts new file mode 100644 index 000000000000..0f111b563e1d --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/build-browser-features_spec.ts @@ -0,0 +1,195 @@ +/** + * @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 { TestProjectHost } from '@angular-devkit/architect/testing'; +import { getSystemPath, join } from '@angular-devkit/core'; +import { ScriptTarget } from 'typescript'; +import { BuildBrowserFeatures } from './build-browser-features'; + +const devkitRoot = (global as any)._DevKitRoot; // tslint:disable-line:no-any +const workspaceRoot = join( + devkitRoot, + 'tests/angular_devkit/build_angular/hello-world-app/'); + +const host = new TestProjectHost(workspaceRoot); + +describe('BuildBrowserFeatures', () => { + let workspaceRootSysPath = ''; + beforeEach(async () => { + await host.initialize().toPromise(); + workspaceRootSysPath = getSystemPath(host.root()); + }); + + afterEach(async () => host.restore().toPromise()); + + describe('isDifferentialLoadingNeeded', () => { + it('should be true for for IE 9-11 and ES2015', () => { + host.writeMultipleFiles({ + 'browserslist': 'IE 9-11', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES2015, + ); + expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(true); + }); + + it('should be false for Chrome and ES2015', () => { + host.writeMultipleFiles({ + 'browserslist': 'last 1 chrome version', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES2015, + ); + expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false); + }); + + it('detects no need for differential loading for target is ES5', () => { + host.writeMultipleFiles({ + 'browserslist': 'last 1 chrome version', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES5, + ); + expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false); + }); + + it('should be false for Safari 10.1 when target is ES2015', () => { + host.writeMultipleFiles({ + 'browserslist': 'Safari 10.1', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES2015, + ); + expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false); + }); + }); + + describe('isFeatureSupported', () => { + it('should be true for es6-module and Safari 10.1', () => { + host.writeMultipleFiles({ + 'browserslist': 'Safari 10.1', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES2015, + ); + expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true); + }); + + it('should be false for es6-module and IE9', () => { + host.writeMultipleFiles({ + 'browserslist': 'IE 9', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES2015, + ); + expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(false); + }); + + it('should be true for es6-module and last 1 chrome version', () => { + host.writeMultipleFiles({ + 'browserslist': 'last 1 chrome version', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES2015, + ); + expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true); + }); + + it('should be true for es6-module and Edge 18', () => { + host.writeMultipleFiles({ + 'browserslist': 'Edge 18', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES2015, + ); + expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true); + }); + }); + + describe('isNoModulePolyfillNeeded', () => { + it('should be false for Safari 10.1 when target is ES5', () => { + host.writeMultipleFiles({ + 'browserslist': 'Safari 10.1', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES5, + ); + expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false); + }); + + it('should be false for Safari 10.1 when target is ES2015', () => { + host.writeMultipleFiles({ + 'browserslist': 'Safari 10.1', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES2015, + ); + expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false); + }); + + it('should be true for Safari 9+ when target is ES2015', () => { + host.writeMultipleFiles({ + 'browserslist': 'Safari >= 9', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES2015, + ); + expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(true); + }); + + it('should be false for Safari 9+ when target is ES5', () => { + host.writeMultipleFiles({ + 'browserslist': 'Safari >= 9', + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES5, + ); + expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false); + }); + + it('should be false when not supporting Safari 10.1 target is ES2015', () => { + host.writeMultipleFiles({ + 'browserslist': ` + Edge 18 + IE 9 + `, + }); + + const buildBrowserFeatures = new BuildBrowserFeatures( + workspaceRootSysPath, + ScriptTarget.ES2015, + ); + expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/utils/differential-loading.ts b/packages/angular_devkit/build_angular/src/utils/differential-loading.ts deleted file mode 100644 index 456007ebff5b..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/differential-loading.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @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 * as browserslist from 'browserslist'; -import * as caniuse from 'caniuse-api'; -import { ScriptTarget } from 'typescript'; - - -export function isDifferentialLoadingNeeded( - projectRoot: string, - target: ScriptTarget = ScriptTarget.ES5): boolean { - - const supportES2015 = target !== ScriptTarget.ES3 && target !== ScriptTarget.ES5; - - return supportES2015 && isEs5SupportNeeded(projectRoot); -} - -export function isEs5SupportNeeded(projectRoot: string): boolean { - const browsersList: string[] = browserslist( - undefined, { - path: projectRoot, - }); - - return !caniuse.isSupported('es6-module', browsersList.join(', ')); -} diff --git a/packages/angular_devkit/build_angular/src/utils/differential-loading_spec.ts b/packages/angular_devkit/build_angular/src/utils/differential-loading_spec.ts deleted file mode 100644 index 1307fd9f67cd..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/differential-loading_spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @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 { TestProjectHost } from '@angular-devkit/architect/testing'; -import { getSystemPath, join } from '@angular-devkit/core'; -import { ScriptTarget } from 'typescript'; -import { isDifferentialLoadingNeeded } from './differential-loading'; - -const devkitRoot = (global as any)._DevKitRoot; // tslint:disable-line:no-any -const workspaceRoot = join( - devkitRoot, - 'tests/angular_devkit/build_angular/hello-world-app/'); - -const host = new TestProjectHost(workspaceRoot); - - -describe('differential loading', () => { - - beforeEach(async () => host.initialize().toPromise()); - afterEach(async () => host.restore().toPromise()); - - it('detects the need for differential loading for IE 9-11 and ES2015', () => { - host.writeMultipleFiles({ - 'browserslist': 'IE 9-11', - }); - - const needed = isDifferentialLoadingNeeded(getSystemPath(host.root()), ScriptTarget.ES2015); - expect(needed).toBe(true); - }); - - it('detects no need for differential loading for Chrome and ES2015', () => { - host.writeMultipleFiles({ - 'browserslist': 'last 1 chrome version', - }); - - const needed = isDifferentialLoadingNeeded(getSystemPath(host.root()), ScriptTarget.ES2015); - - expect(needed).toBe(false); - }); - - it('detects no need for differential loading for target is ES5', () => { - host.writeMultipleFiles({ - 'browserslist': 'last 1 chrome version', - }); - - const needed = isDifferentialLoadingNeeded(getSystemPath(host.root()), ScriptTarget.ES5); - - expect(needed).toBe(false); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/utils/index.ts b/packages/angular_devkit/build_angular/src/utils/index.ts index 0f297fff122b..8cd7670f0a9b 100644 --- a/packages/angular_devkit/build_angular/src/utils/index.ts +++ b/packages/angular_devkit/build_angular/src/utils/index.ts @@ -5,9 +5,10 @@ * 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 */ + +export * from './build-browser-features'; export * from './default-progress'; export * from './delete-output-dir'; -export * from './differential-loading'; export * from './run-module-as-observable-fork'; export * from './normalize-file-replacements'; export * from './normalize-asset-patterns'; diff --git a/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts b/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts index 94966ee738bd..df478dbfde32 100644 --- a/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts +++ b/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts @@ -24,7 +24,7 @@ import { getEsVersionForFileName } from '../angular-cli-files/models/webpack-con import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig'; import { Schema as BrowserBuilderSchema } from '../browser/schema'; import { NormalizedBrowserBuilderSchema, defaultProgress, normalizeBrowserSchema } from '../utils'; -import { isDifferentialLoadingNeeded } from './differential-loading'; +import { BuildBrowserFeatures } from './build-browser-features'; const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const webpackMerge = require('webpack-merge'); @@ -53,9 +53,11 @@ export async function generateWebpackConfig( // At the moment, only the browser builder supports differential loading // However this config generation is used by multiple builders such as dev-server - const scriptTarget = tsConfig.options.target; + const scriptTarget = tsConfig.options.target || ts.ScriptTarget.ES5; + const buildBrowserFeatures = new BuildBrowserFeatures(projectRoot, scriptTarget); const differentialLoading = context.builder.builderName === 'browser' - && isDifferentialLoadingNeeded(projectRoot, scriptTarget) && !options.watch; + && !options.watch + && buildBrowserFeatures.isDifferentialLoadingNeeded(); const scriptTargets = [scriptTarget]; diff --git a/packages/angular_devkit/build_angular/test/browser/differential_loading_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/differential_loading_spec_large.ts index 62c80dafa543..16530d00e934 100644 --- a/packages/angular_devkit/build_angular/test/browser/differential_loading_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/browser/differential_loading_spec_large.ts @@ -73,8 +73,6 @@ describe('Browser Builder with differential loading', () => { 'main.js', 'main.js.map', - 'polyfills-es5.js', - 'polyfills-es5.js.map', 'polyfills.js', 'polyfills.js.map', diff --git a/tests/legacy-cli/e2e/tests/misc/support-safari-10.1.ts b/tests/legacy-cli/e2e/tests/misc/support-safari-10.1.ts new file mode 100644 index 000000000000..f78cb217bdcb --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/support-safari-10.1.ts @@ -0,0 +1,42 @@ +import { oneLineTrim } from 'common-tags'; +import { + expectFileNotToExist, + expectFileToExist, + expectFileToMatch, + writeFile, +} from '../../utils/fs'; +import { ng } from '../../utils/process'; + +export default async function () { + await writeFile('browserslist', 'Safari 10.1'); + await ng('build'); + await expectFileNotToExist('dist/test-project/polyfills-es5.js'); + await expectFileNotToExist('dist/test-project/polyfills-nomodule-es5.js'); + await expectFileToMatch('dist/test-project/index.html', oneLineTrim` + + + + + + `); + + await writeFile('browserslist', ` + IE 9 + Safari 10.1 + `); + await ng('build'); + await expectFileToExist('dist/test-project/polyfills-nomodule-es5.js'); + await expectFileToMatch('dist/test-project/index.html', oneLineTrim` + + + + + + + + + + + + `); +} From 89e806d92623db8b8f75524adadd074ca00c9d0f Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Tue, 11 Jun 2019 07:51:40 +0200 Subject: [PATCH 2/3] test: change polyfills in hello-world-app which causes `rebuilds TS worker` to fail --- .../hello-world-app/package.json | 1 - .../hello-world-app/src/polyfills.ts | 57 +++++++++---------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/tests/angular_devkit/build_angular/hello-world-app/package.json b/tests/angular_devkit/build_angular/hello-world-app/package.json index 97164be01a98..5aecb7dd20b6 100644 --- a/tests/angular_devkit/build_angular/hello-world-app/package.json +++ b/tests/angular_devkit/build_angular/hello-world-app/package.json @@ -23,7 +23,6 @@ "@angular/platform-browser": "^6.0.0-rc.0", "@angular/platform-browser-dynamic": "^6.0.0-rc.0", "@angular/router": "^6.0.0-rc.0", - "core-js": "^2.4.1", "rxjs": "~6.2.0", "zone.js": "^0.8.19" }, diff --git a/tests/angular_devkit/build_angular/hello-world-app/src/polyfills.ts b/tests/angular_devkit/build_angular/hello-world-app/src/polyfills.ts index d7fd9a7ba789..bd13d5722895 100644 --- a/tests/angular_devkit/build_angular/hello-world-app/src/polyfills.ts +++ b/tests/angular_devkit/build_angular/hello-world-app/src/polyfills.ts @@ -25,41 +25,39 @@ * BROWSER POLYFILLS */ -/** IE9, IE10 and IE11 requires all of the following polyfills. **/ -// 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'; - - -/** Evergreen browsers require these. **/ -// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. -import 'core-js/es7/reflect'; - - /** - * Required to support Web Animations `@angular/platform-browser/animations`. - * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation - **/ + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. - +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags.ts'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ /*************************************************************************************************** * Zone JS is required by default for Angular itself. @@ -67,7 +65,6 @@ import 'core-js/es7/reflect'; import 'zone.js/dist/zone'; // Included with Angular CLI. - /*************************************************************************************************** * APPLICATION IMPORTS */ From f3f9a69f165e04117733dea1a406abbed922000f Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Tue, 11 Jun 2019 07:56:05 +0200 Subject: [PATCH 3/3] fix(@angular-devkit/build-angular): browsers that partially support ES6 modules are being marked as not supported `caniuse-api` only returns `true` for a feature when it's fully supported. This might causes redundant ES5 builds if users opt to support only browsers which do support ES6 modules but either require polyfills such as Safari 10.1 or when both bundles will be downloaded such as `Edge 18` See: https://github.com/Nyalab/caniuse-api/blob/1b74c10257e36877709da4f6630513db10203a78/src/index.js#L49 and https://github.com/Nyalab/caniuse-api/issues/82 Fixes #14580 --- package.json | 2 +- .../angular_devkit/build_angular/package.json | 2 +- .../src/utils/build-browser-features.ts | 31 +++++++++++++++- yarn.lock | 36 +++++-------------- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 751520b3b9f9..d75a1a20d005 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "@bazel/typescript": "~0.26.0", "@ngtools/json-schema": "^1.1.0", "@types/browserslist": "^4.4.0", - "@types/caniuse-api": "^3.0.0", + "@types/caniuse-lite": "^1.0.0", "@types/copy-webpack-plugin": "^4.4.1", "@types/express": "^4.16.0", "@types/glob": "^7.0.0", diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json index 1803c7f40286..3b7cb6710da9 100644 --- a/packages/angular_devkit/build_angular/package.json +++ b/packages/angular_devkit/build_angular/package.json @@ -15,7 +15,7 @@ "ajv": "6.10.0", "autoprefixer": "9.6.0", "browserslist": "4.6.2", - "caniuse-api": "3.0.0", + "caniuse-lite": "1.0.30000974", "circular-dependency-plugin": "5.0.2", "clean-css": "4.2.1", "copy-webpack-plugin": "5.0.3", diff --git a/packages/angular_devkit/build_angular/src/utils/build-browser-features.ts b/packages/angular_devkit/build_angular/src/utils/build-browser-features.ts index b330dad94a60..cfd3e58dda4c 100644 --- a/packages/angular_devkit/build_angular/src/utils/build-browser-features.ts +++ b/packages/angular_devkit/build_angular/src/utils/build-browser-features.ts @@ -34,7 +34,7 @@ export class BuildBrowserFeatures { * True, when one or more browsers requires ES5 support */ isEs5SupportNeeded(): boolean { - return !caniuse.isSupported('es6-module', this._supportedBrowsers.join(', ')); + return !this.isFeatureSupported('es6-module'); } /** @@ -55,4 +55,33 @@ export class BuildBrowserFeatures { return this._supportedBrowsers.some(browser => safariBrowsers.includes(browser)); } + + /** + * True, when a browser feature is supported partially or fully. + */ + isFeatureSupported(featureId: string): boolean { + // y: feature is fully available + // n: feature is unavailable + // a: feature is partially supported + // x: feature is prefixed + const criteria = [ + 'y', + 'a', + ]; + + const data = feature(features[featureId]); + + return !this._supportedBrowsers + .some(browser => { + const [agentId, version] = browser.split(' '); + + const browserData = data.stats[agentId]; + const featureStatus = (browserData && browserData[version]) as string | undefined; + + // We are only interested in the first character + // Ex: when 'a #4 #5', we only need to check for 'a' + // as for such cases we should polyfill these features as needed + return !featureStatus || !criteria.includes(featureStatus.charAt(0)); + }); + } } diff --git a/yarn.lock b/yarn.lock index 72b867017af8..c98c5eef4ef0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -300,10 +300,10 @@ resolved "https://registry.yarnpkg.com/@types/browserslist/-/browserslist-4.4.0.tgz#e2a5f7f8c7e97afb39f50812a77e5230d3ca2353" integrity sha512-hrIjWSu7Hh96/rKlpChe58qHEwIZ0+F5Zf4QNdvSVP5LUXbaJM04g9tBjo702VTNqPZr5znEJeqNR3nAV3vJPg== -"@types/caniuse-api@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/caniuse-api/-/caniuse-api-3.0.0.tgz#af31cc52062be0ab24583be072fd49b634dcc2fe" - integrity sha512-wT1VfnScjAftZsvLYaefu/UuwYJdYBwD2JDL2OQd01plGmuAoir5V6HnVHgrfh7zEwcasoiyO2wQ+W58sNh2sw== +"@types/caniuse-lite@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/caniuse-lite/-/caniuse-lite-1.0.0.tgz#6506ed4b3f8d19def130d19419062960e86cc3bc" + integrity sha512-g28510gzJpFL0xqvuGAlI+dfIr3qvUcZQVFc7f7u2VlWVqI1oBkWhGLYh3fXfoflR7HRnU4w0NRux0pPJQ7VVg== "@types/caseless@*": version "0.12.1" @@ -1871,20 +1871,10 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-api@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0: - version "1.0.30000955" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000955.tgz#360fdb9a1e41d6dd996130411334e44a39e4446d" - integrity sha512-6AwmIKgqCYfDWWadRkAuZSHMQP4Mmy96xAXEdRBlN/luQhlRYOKgwOlZ9plpCOsVbBuqbTmGqDK3JUM/nlr8CA== +caniuse-lite@1.0.30000974: + version "1.0.30000974" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000974.tgz#b7afe14ee004e97ce6dc73e3f878290a12928ad8" + integrity sha512-xc3rkNS/Zc3CmpMKuczWEdY2sZgx09BkAxfvkxlAEBTqcMHeL8QnPqhKse+5sRTi3nrw2pJwToD2WvKn1Uhvww== caniuse-lite@^1.0.30000898: version "1.0.30000903" @@ -5870,11 +5860,6 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" @@ -5895,11 +5880,6 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "~3.0.0" -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - lodash@^4.16.6, lodash@^4.17.11, lodash@^4.17.5: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"