From e9b30d6b7d38c336df99160384329a57d636f70f 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 201c30cc7772..ef9a0ce364d0 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 @@ -16,8 +16,8 @@ import { Output, debug, } from 'webpack'; -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'; @@ -41,7 +41,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, @@ -68,25 +68,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 ff95bac5b101..c399c5c4be7f 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -49,7 +49,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'; @@ -193,9 +193,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 e9be1252836b..a6649e8703c4 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 207913420306b616da815e4da1de3c02789f3dc0 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 5990c104eea6306854f8ab12dc977bda04f765c9 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 Nyalab/caniuse-api#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 e9bdcc0f7cf3..608c00c55c6a 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 915edde68377..a02b7da67ec7 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.5.1", "browserslist": "4.5.5", - "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.2", 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 0af4249d9d16..acabb0d3d81c 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" @@ -1823,20 +1823,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" @@ -5700,11 +5690,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" @@ -5725,11 +5710,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"