diff --git a/packages/angular/cli/lib/config/schema.json b/packages/angular/cli/lib/config/schema.json index 097e26f38bbb..ff5a58aa9b49 100644 --- a/packages/angular/cli/lib/config/schema.json +++ b/packages/angular/cli/lib/config/schema.json @@ -950,11 +950,6 @@ }, "default": [] }, - "es5BrowserSupport": { - "description": "Enables conditionally loaded ES2015 polyfills.", - "type": "boolean", - "default": false - }, "rebaseRootRelativeCssUrls": { "description": "Change root relative URLs in stylesheets to include base HREF and deploy URL. Use only for compatibility and transition. The behavior of this option is non-standard and will be removed in the next major release.", "type": "boolean", 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 996a922a6094..8551dba66232 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 @@ -68,8 +68,6 @@ export interface BuildOptions { statsJson: boolean; forkTypeChecker: boolean; profile?: boolean; - /** @deprecated since version 8 **/ - es5BrowserSupport?: boolean; main: string; polyfills?: string; 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 e5d79cfa34d7..d4816b1b204e 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 @@ -130,10 +130,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { tsConfig.options.target || ScriptTarget.ES5, ); - if ( - buildOptions.es5BrowserSupport || - (buildOptions.es5BrowserSupport === undefined && buildBrowserFeatures.isEs5SupportNeeded()) - ) { + if (buildBrowserFeatures.isEs5SupportNeeded()) { const polyfillsChunkName = 'polyfills-es5'; entryPoints[polyfillsChunkName] = [path.join(__dirname, '..', 'es5-polyfills.js')]; if (differentialLoadingMode) { diff --git a/packages/angular_devkit/build_angular/src/browser/schema.json b/packages/angular_devkit/build_angular/src/browser/schema.json index 347c2ed04886..68ded06cffa5 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/browser/schema.json @@ -350,11 +350,6 @@ "default": false, "x-deprecated": "Use \"NG_BUILD_PROFILING\" environment variable instead." }, - "es5BrowserSupport": { - "description": "Enables conditionally loaded ES2015 polyfills.", - "type": "boolean", - "x-deprecated": "This will be determined from the list of supported browsers specified in the 'browserslist' file." - }, "rebaseRootRelativeCssUrls": { "description": "Change root relative URLs in stylesheets to include base HREF and deploy URL. Use only for compatibility and transition. The behavior of this option is non-standard and will be removed in the next major release.", "type": "boolean", 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 d493b6b546cf..387a245e34d0 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 @@ -82,7 +82,6 @@ export async function generateWebpackConfig( // Under downlevel differential loading we copy the assets outside of webpack. assets: [], esVersionInFileName: true, - es5BrowserSupport: undefined, }; } diff --git a/packages/schematics/angular/BUILD.bazel b/packages/schematics/angular/BUILD.bazel index 443a612c4668..43f0df631369 100644 --- a/packages/schematics/angular/BUILD.bazel +++ b/packages/schematics/angular/BUILD.bazel @@ -64,6 +64,8 @@ ts_library( "//packages/angular_devkit/schematics", "//packages/angular_devkit/schematics/tasks", "//packages/schematics/angular/third_party/github.com/Microsoft/TypeScript", + "@npm//@types/browserslist", + "@npm//@types/caniuse-lite", "@npm//@types/node", "@npm//rxjs", "@npm//tslint", @@ -98,6 +100,8 @@ ts_library( "//packages/angular_devkit/schematics", "//packages/angular_devkit/schematics/testing", "//packages/schematics/angular/third_party/github.com/Microsoft/TypeScript", + "@npm//@types/browserslist", + "@npm//@types/caniuse-lite", "@npm//@types/jasmine", "@npm//@types/node", "@npm//rxjs", diff --git a/packages/schematics/angular/migrations/migration-collection.json b/packages/schematics/angular/migrations/migration-collection.json index 31a3e8494c34..2051977e4bda 100644 --- a/packages/schematics/angular/migrations/migration-collection.json +++ b/packages/schematics/angular/migrations/migration-collection.json @@ -69,6 +69,11 @@ "version": "10.0.0-beta.1", "factory": "./update-10/update-dependencies", "description": "Workspace dependencies updates." + }, + "remove-es5-browser-support-option": { + "version": "10.0.0-beta.2", + "factory": "./update-10/remove-es5-browser-support", + "description": "Remove deprecated 'es5BrowserSupport' browser builder option. The inclusion for ES5 polyfills will be determined from the browsers listed in the browserslist configuration." } } } diff --git a/packages/schematics/angular/migrations/update-10/remove-es5-browser-support.ts b/packages/schematics/angular/migrations/update-10/remove-es5-browser-support.ts new file mode 100644 index 000000000000..e3c594137657 --- /dev/null +++ b/packages/schematics/angular/migrations/update-10/remove-es5-browser-support.ts @@ -0,0 +1,137 @@ +/** + * @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 { Path, getSystemPath, logging, normalize, resolve, workspaces } from '@angular-devkit/core'; +import { Rule } from '@angular-devkit/schematics'; +import { getWorkspace, updateWorkspace } from '../../utility/workspace'; +import { Builders, ProjectType } from '../../utility/workspace-models'; + +export default function (): Rule { + return async (host, context) => { + const workspace = await getWorkspace(host); + + for (const [projectName, project] of workspace.projects) { + if (project.extensions.projectType !== ProjectType.Application) { + // Only interested in application projects + continue; + } + + for (const [, target] of project.targets) { + // Only interested in Angular Devkit Browser builder + if (target?.builder !== Builders.Browser) { + continue; + } + + const isES5Needed = await isES5SupportNeeded( + resolve( + normalize(host.root.path), + normalize(project.root), + ), + ); + + // Check options + if (target.options) { + target.options = removeE5BrowserSupportOption(projectName, target.options, isES5Needed, context.logger); + } + + // Go through each configuration entry + if (!target.configurations) { + continue; + } + + for (const [configurationName, options] of Object.entries(target.configurations)) { + target.configurations[configurationName] = removeE5BrowserSupportOption( + projectName, + options, + isES5Needed, + context.logger, + configurationName, + ); + } + } + } + + return updateWorkspace(workspace); + }; +} + +type TargetOptions = workspaces.TargetDefinition['options']; + +function removeE5BrowserSupportOption( + projectName: string, + options: TargetOptions, + isES5Needed: boolean | undefined, + logger: logging.LoggerApi, + configurationName = '', +): TargetOptions { + if (typeof options?.es5BrowserSupport !== 'boolean') { + return options; + } + + const configurationPath = configurationName ? `configurations.${configurationName}.` : ''; + + if (options.es5BrowserSupport && isES5Needed === false) { + logger.warn( + `Project '${projectName}' doesn't require ES5 support, but '${configurationPath}es5BrowserSupport' was set to 'true'.\n` + + `ES5 polyfills will no longer be added when building this project${configurationName ? ` with '${configurationName}' configuration.` : '.'}\n` + + `If ES5 polyfills are needed, add the supported ES5 browsers in the browserslist configuration.`, + ); + } else if (!options.es5BrowserSupport && isES5Needed === true) { + logger.warn( + `Project '${projectName}' requires ES5 support, but '${configurationPath}es5BrowserSupport' was set to 'false'.\n` + + `ES5 polyfills will be added when building this project${configurationName ? ` with '${configurationName}' configuration.` : '.'}\n` + + `If ES5 polyfills are not needed, remove the unsupported ES5 browsers from the browserslist configuration.`, + ); + } + + return { + ...options, + es5BrowserSupport: undefined, + }; +} + +/** + * True, when one or more browsers requires ES5 support + */ +async function isES5SupportNeeded(projectRoot: Path): Promise { + // y: feature is fully available + // n: feature is unavailable + // a: feature is partially supported + // x: feature is prefixed + const criteria = [ + 'y', + 'a', + ]; + + try { + // tslint:disable-next-line:no-implicit-dependencies + const browserslist = await import('browserslist'); + const supportedBrowsers = browserslist(undefined, { + path: getSystemPath(projectRoot), + }); + + // tslint:disable-next-line:no-implicit-dependencies + const { feature, features } = await import('caniuse-lite'); + const data = feature(features['es6-module']); + + return 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)); + }); + } catch { + return undefined; + } +} diff --git a/packages/schematics/angular/migrations/update-10/remove-es5-browser-support_spec.ts b/packages/schematics/angular/migrations/update-10/remove-es5-browser-support_spec.ts new file mode 100644 index 000000000000..5989b07140d5 --- /dev/null +++ b/packages/schematics/angular/migrations/update-10/remove-es5-browser-support_spec.ts @@ -0,0 +1,94 @@ +/** + * @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 { JsonObject } from '@angular-devkit/core'; +import { EmptyTree } from '@angular-devkit/schematics'; +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; +import { BuilderTarget, Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models'; + +function getBuildTarget(tree: UnitTestTree): BuilderTarget { + return JSON.parse(tree.readContent('/angular.json')).projects.app.architect.build; +} + +function createWorkSpaceConfig(tree: UnitTestTree, es5BrowserSupport: boolean | undefined) { + const angularConfig: WorkspaceSchema = { + version: 1, + projects: { + app: { + root: '', + sourceRoot: 'src', + projectType: ProjectType.Application, + prefix: 'app', + architect: { + build: { + builder: Builders.Browser, + options: { + es5BrowserSupport, + sourceMaps: true, + buildOptimizer: false, + // tslint:disable-next-line:no-any + } as any, + configurations: { + one: { + es5BrowserSupport, + vendorChunk: false, + buildOptimizer: true, + }, + two: { + es5BrowserSupport, + vendorChunk: false, + buildOptimizer: true, + sourceMaps: false, + }, + // tslint:disable-next-line:no-any + } as any, + }, + }, + }, + }, + }; + + tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2)); +} + +describe(`Migration to remove deprecated 'es5BrowserSupport' option`, () => { + const schematicName = 'remove-es5-browser-support-option'; + + const schematicRunner = new SchematicTestRunner( + 'migrations', + require.resolve('../migration-collection.json'), + ); + + let tree: UnitTestTree; + beforeEach(() => { + tree = new UnitTestTree(new EmptyTree()); + }); + + it(`should remove option when set to 'false'`, async () => { + createWorkSpaceConfig(tree, false); + + const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); + const { options, configurations } = getBuildTarget(newTree); + + expect(options.es5BrowserSupport).toBeUndefined(); + expect(configurations).toBeDefined(); + expect(configurations?.one.es5BrowserSupport).toBeUndefined(); + expect(configurations?.two.es5BrowserSupport).toBeUndefined(); + }); + + it(`should remove option and when set to 'true'`, async () => { + createWorkSpaceConfig(tree, true); + + const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); + const { options, configurations } = getBuildTarget(newTree); + + expect(options.es5BrowserSupport).toBeUndefined(); + expect(configurations).toBeDefined(); + expect(configurations?.one.es5BrowserSupport).toBeUndefined(); + expect(configurations?.two.es5BrowserSupport).toBeUndefined(); + }); +}); diff --git a/packages/schematics/angular/utility/workspace-models.ts b/packages/schematics/angular/utility/workspace-models.ts index 232141360e6f..d4392362abb5 100644 --- a/packages/schematics/angular/utility/workspace-models.ts +++ b/packages/schematics/angular/utility/workspace-models.ts @@ -62,7 +62,6 @@ export interface BrowserBuilderOptions extends BrowserBuilderBaseOptions { maximumWarning?: string; maximumError?: string; }[]; - es5BrowserSupport?: boolean; webWorkerTsConfig?: string; } diff --git a/tests/legacy-cli/e2e/tests/misc/support-ie.ts b/tests/legacy-cli/e2e/tests/misc/es5-polyfills.ts similarity index 60% rename from tests/legacy-cli/e2e/tests/misc/support-ie.ts rename to tests/legacy-cli/e2e/tests/misc/es5-polyfills.ts index 042ff1f15190..3a7e6ccb90a8 100644 --- a/tests/legacy-cli/e2e/tests/misc/support-ie.ts +++ b/tests/legacy-cli/e2e/tests/misc/es5-polyfills.ts @@ -9,11 +9,6 @@ export default async function () { compilerOptions['target'] = 'es5'; }); - await updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.es5BrowserSupport = false; - }); - await writeFile('.browserslistrc', 'last 2 Chrome versions'); await ng('build'); await expectFileNotToExist('dist/test-project/polyfills-es5.js'); @@ -25,21 +20,6 @@ export default async function () { `); - await ng('build', `--es5BrowserSupport`); - await expectFileToMatch('dist/test-project/polyfills-es5.js', 'core-js'); - await expectFileToMatch('dist/test-project/index.html', oneLineTrim` - - - - - - - `); - - await updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.es5BrowserSupport = undefined; - }); await writeFile('.browserslistrc', 'IE 10'); await ng('build'); await expectFileToMatch('dist/test-project/polyfills-es5.js', 'core-js'); diff --git a/tests/legacy-cli/e2e/tests/update/update-7.0.ts b/tests/legacy-cli/e2e/tests/update/update-7.0.ts index a7cd7e169dd2..6f24b6fd61f1 100644 --- a/tests/legacy-cli/e2e/tests/update/update-7.0.ts +++ b/tests/legacy-cli/e2e/tests/update/update-7.0.ts @@ -11,6 +11,9 @@ export default async function() { // Create new project from previous version files. // We must use the original NPM packages to force a real update. await createProjectFromAsset('7.0-project', true); + // Update to version 8, to use the self update CLI feature. + await ng('update', '@angular/cli@8'); + fs.writeFileSync('.npmrc', 'registry = http://localhost:4873', 'utf8'); // Update the CLI.