From a355e7d693a8a74b2010e18f67cfd86207a9eac7 Mon Sep 17 00:00:00 2001 From: Minko Gechev Date: Fri, 1 Mar 2019 13:56:34 -0800 Subject: [PATCH] feat(@schematics/angular): drop es6 from modern polyfills 1. Remove imports of es6 polyfills introduced by the CLI. 2. Refactor the migrations for version 8 by moving the codelyzer and polyfill transforms into different files. The PR drops all `core-js/es6` polyfills that we've introduced with the CLI, except the commented ones. We do not remove commented imports, since they are not part of the internal es6 polyfills. The migration automatically drops the associated comments with the removed imports since they are part of the node - under its `jsDoc` property. --- .../angular_devkit/schematics/src/index.ts | 2 +- .../migrations/update-8/codelyzer-5.ts | 94 +++++++++++++++++++ .../{index_spec.ts => codelyzer-5_spec.ts} | 0 .../migrations/update-8/drop-es6-polyfills.ts | 77 +++++++++++++++ .../update-8/drop-es6-polyfills_spec.ts | 93 ++++++++++++++++++ .../angular/migrations/update-8/index.ts | 87 +---------------- 6 files changed, 269 insertions(+), 84 deletions(-) create mode 100644 packages/schematics/angular/migrations/update-8/codelyzer-5.ts rename packages/schematics/angular/migrations/update-8/{index_spec.ts => codelyzer-5_spec.ts} (100%) create mode 100644 packages/schematics/angular/migrations/update-8/drop-es6-polyfills.ts create mode 100644 packages/schematics/angular/migrations/update-8/drop-es6-polyfills_spec.ts diff --git a/packages/angular_devkit/schematics/src/index.ts b/packages/angular_devkit/schematics/src/index.ts index 186d53b511..68390c0f40 100644 --- a/packages/angular_devkit/schematics/src/index.ts +++ b/packages/angular_devkit/schematics/src/index.ts @@ -25,7 +25,7 @@ export * from './rules/url'; export * from './tree/delegate'; export * from './tree/empty'; export * from './tree/host-tree'; -export {UpdateRecorder} from './tree/interface'; +export { UpdateRecorder } from './tree/interface'; export * from './engine/schematic'; export * from './sink/dryrun'; export * from './sink/filesystem'; diff --git a/packages/schematics/angular/migrations/update-8/codelyzer-5.ts b/packages/schematics/angular/migrations/update-8/codelyzer-5.ts new file mode 100644 index 0000000000..49909a5976 --- /dev/null +++ b/packages/schematics/angular/migrations/update-8/codelyzer-5.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 { JsonParseMode, parseJsonAst } from '@angular-devkit/core'; +import { + Rule, + SchematicContext, + Tree, +} from '@angular-devkit/schematics'; +import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; +import { + NodeDependency, + NodeDependencyType, + addPackageJsonDependency, +} from '../../utility/dependencies'; +import { findPropertyInAstObject } from '../../utility/json-utils'; + +const ruleMapping: {[key: string]: string} = { + 'contextual-life-cycle': 'contextual-lifecycle', + 'no-conflicting-life-cycle-hooks': 'no-conflicting-lifecycle', + 'no-life-cycle-call': 'no-lifecycle-call', + 'use-life-cycle-interface': 'use-lifecycle-interface', + 'decorator-not-allowed': 'contextual-decorator', + 'enforce-component-selector': 'use-component-selector', + 'no-output-named-after-standard-event': 'no-output-native', + 'use-host-property-decorator': 'no-host-metadata-property', + 'use-input-property-decorator': 'no-inputs-metadata-property', + 'use-output-property-decorator': 'no-outputs-metadata-property', + 'no-queries-parameter': 'no-queries-metadata-property', + 'pipe-impure': 'no-pipe-impure', + 'use-view-encapsulation': 'use-component-view-encapsulation', + i18n: 'template-i18n', + 'banana-in-box': 'template-banana-in-box', + 'no-template-call-expression': 'template-no-call-expression', + 'templates-no-negated-async': 'template-no-negated-async', + 'trackBy-function': 'template-use-track-by-function', + 'no-attribute-parameter-decorator': 'no-attribute-decorator', + 'max-inline-declarations': 'component-max-inline-declarations', +}; + +export const updateTsLintConfig = (): Rule => { + return (host: Tree) => { + const tsLintPath = '/tslint.json'; + const buffer = host.read(tsLintPath); + if (!buffer) { + return host; + } + const tsCfgAst = parseJsonAst(buffer.toString(), JsonParseMode.Loose); + + if (tsCfgAst.kind != 'object') { + return host; + } + + const rulesNode = findPropertyInAstObject(tsCfgAst, 'rules'); + if (!rulesNode || rulesNode.kind != 'object') { + return host; + } + + const recorder = host.beginUpdate(tsLintPath); + + rulesNode.properties.forEach(prop => { + const mapping = ruleMapping[prop.key.value]; + if (mapping) { + recorder.remove(prop.key.start.offset + 1, prop.key.value.length); + recorder.insertLeft(prop.key.start.offset + 1, mapping); + } + }); + + host.commitUpdate(recorder); + + return host; + }; +}; + +export const updatePackageJson = () => { + return (host: Tree, context: SchematicContext) => { + const dependency: NodeDependency = { + type: NodeDependencyType.Dev, + name: 'codelyzer', + version: '^5.0.0', + overwrite: true, + }; + + addPackageJsonDependency(host, dependency); + context.addTask(new NodePackageInstallTask()); + + return host; + }; +}; diff --git a/packages/schematics/angular/migrations/update-8/index_spec.ts b/packages/schematics/angular/migrations/update-8/codelyzer-5_spec.ts similarity index 100% rename from packages/schematics/angular/migrations/update-8/index_spec.ts rename to packages/schematics/angular/migrations/update-8/codelyzer-5_spec.ts diff --git a/packages/schematics/angular/migrations/update-8/drop-es6-polyfills.ts b/packages/schematics/angular/migrations/update-8/drop-es6-polyfills.ts new file mode 100644 index 0000000000..7290c1a27c --- /dev/null +++ b/packages/schematics/angular/migrations/update-8/drop-es6-polyfills.ts @@ -0,0 +1,77 @@ +/** + * @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 { Rule, Tree } from '@angular-devkit/schematics'; +import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript'; + +const toDrop: {[importName: string]: true} = { + 'core-js/es6/symbol': true, + 'core-js/es6/object': true, + 'core-js/es6/function': true, + 'core-js/es6/parse-int': true, + 'core-js/es6/parse-float': true, + 'core-js/es6/number': true, + 'core-js/es6/math': true, + 'core-js/es6/string': true, + 'core-js/es6/date': true, + 'core-js/es6/array': true, + 'core-js/es6/regexp': true, + 'core-js/es6/map': true, + 'zone.js/dist/zone': true, + 'core-js/es6/set': true, +}; + +const header = `/** +*/ + +/*************************************************************************************************** +* BROWSER POLYFILLS +*/ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/weak-map';`; + +const applicationPolyfillsHeader = 'APPLICATION IMPORTS'; + +export const dropES2015Polyfills = (): Rule => { + return (tree: Tree) => { + const path = '/polyfills.ts'; + const source = tree.read(path); + if (!source) { + return; + } + + // Start the update of the file. + const recorder = tree.beginUpdate(path); + + const sourceFile = ts.createSourceFile(path, source.toString(), ts.ScriptTarget.Latest, true); + const imports = sourceFile.statements + .filter(s => s.kind === ts.SyntaxKind.ImportDeclaration) as ts.ImportDeclaration[]; + + const applicationPolyfillsStart = sourceFile.getText().indexOf(applicationPolyfillsHeader); + + if (imports.length === 0) { return; } + + for (const i of imports) { + const module = ts.isStringLiteral(i.moduleSpecifier) && i.moduleSpecifier.text; + // We do not want to remove imports which are after the "APPLICATION IMPORTS" header. + if (module && toDrop[module] && applicationPolyfillsStart > i.getFullStart()) { + recorder.remove(i.getFullStart(), i.getFullWidth()); + } + } + + // We've removed the header since it's part of the JSDoc of the nodes we dropped + // As part of the header, we also add the comment for importing WeakMap since + // it's not part of the internal ES2015 polyfills we provide. + if (sourceFile.getText().indexOf(header) < 0) { + recorder.insertLeft(0, header); + } + + tree.commitUpdate(recorder); + }; +}; diff --git a/packages/schematics/angular/migrations/update-8/drop-es6-polyfills_spec.ts b/packages/schematics/angular/migrations/update-8/drop-es6-polyfills_spec.ts new file mode 100644 index 0000000000..48734de700 --- /dev/null +++ b/packages/schematics/angular/migrations/update-8/drop-es6-polyfills_spec.ts @@ -0,0 +1,93 @@ +/** + * @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 + */ +// tslint:disable:no-big-function +import { EmptyTree } from '@angular-devkit/schematics'; +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; + +describe('Migration to version 8', () => { + const schematicRunner = new SchematicTestRunner( + 'migrations', + require.resolve('../migration-collection.json'), + ); + + let tree: UnitTestTree; + const polyfillsPath = '/polyfills.ts'; + const defaultOptions = {}; + const polyfills = `/** + */ + +/*************************************************************************************************** + * 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'; + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ +`; + + const packageJson = { + devDependencies: { + codelyzer: '^4.5.0', + }, + }; + const packageJsonPath = '/package.json'; + + describe('Migration to differential polyfill loading', () => { + beforeEach(() => { + tree = new UnitTestTree(new EmptyTree()); + tree.create(polyfillsPath, polyfills); + tree.create(packageJsonPath, JSON.stringify(packageJson, null, 2)); + }); + + it('should drop the es6 polyfills', () => { + tree = schematicRunner.runSchematic('migration-07', defaultOptions, tree); + const polyfills = tree.readContent(polyfillsPath); + expect(polyfills).not.toContain('core-js/es6/symbol'); + expect(polyfills).not.toContain('core-js/es6/set'); + expect(polyfills).not.toContain('zone.js'); + expect(polyfills).not.toContain('Zone'); + + // We don't want to drop this commented import comments + expect(polyfills).toContain('core-js/es6/reflect'); + expect(polyfills).toContain('core-js/es7/reflect'); + expect(polyfills).toContain('BROWSER POLYFILLS'); + expect(polyfills).toContain('core-js/es6/weak-map'); + }); + }); +}); diff --git a/packages/schematics/angular/migrations/update-8/index.ts b/packages/schematics/angular/migrations/update-8/index.ts index 5bd28c6bdd..ea3eed359d 100644 --- a/packages/schematics/angular/migrations/update-8/index.ts +++ b/packages/schematics/angular/migrations/update-8/index.ts @@ -5,99 +5,20 @@ * 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 { JsonParseMode, parseJsonAst } from '@angular-devkit/core'; + import { Rule, - SchematicContext, - Tree, chain, } from '@angular-devkit/schematics'; -import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; -import { - NodeDependency, - NodeDependencyType, - addPackageJsonDependency, -} from '../../utility/dependencies'; -import { findPropertyInAstObject } from '../../utility/json-utils'; - -const ruleMapping: {[key: string]: string} = { - 'contextual-life-cycle': 'contextual-lifecycle', - 'no-conflicting-life-cycle-hooks': 'no-conflicting-lifecycle', - 'no-life-cycle-call': 'no-lifecycle-call', - 'use-life-cycle-interface': 'use-lifecycle-interface', - 'decorator-not-allowed': 'contextual-decorator', - 'enforce-component-selector': 'use-component-selector', - 'no-output-named-after-standard-event': 'no-output-native', - 'use-host-property-decorator': 'no-host-metadata-property', - 'use-input-property-decorator': 'no-inputs-metadata-property', - 'use-output-property-decorator': 'no-outputs-metadata-property', - 'no-queries-parameter': 'no-queries-metadata-property', - 'pipe-impure': 'no-pipe-impure', - 'use-view-encapsulation': 'use-component-view-encapsulation', - i18n: 'template-i18n', - 'banana-in-box': 'template-banana-in-box', - 'no-template-call-expression': 'template-no-call-expression', - 'templates-no-negated-async': 'template-no-negated-async', - 'trackBy-function': 'template-use-track-by-function', - 'no-attribute-parameter-decorator': 'no-attribute-decorator', - 'max-inline-declarations': 'component-max-inline-declarations', -}; - -function updateTsLintConfig(): Rule { - return (host: Tree) => { - const tsLintPath = '/tslint.json'; - const buffer = host.read(tsLintPath); - if (!buffer) { - return host; - } - const tsCfgAst = parseJsonAst(buffer.toString(), JsonParseMode.Loose); - - if (tsCfgAst.kind != 'object') { - return host; - } - - const rulesNode = findPropertyInAstObject(tsCfgAst, 'rules'); - if (!rulesNode || rulesNode.kind != 'object') { - return host; - } - - const recorder = host.beginUpdate(tsLintPath); - - rulesNode.properties.forEach(prop => { - const mapping = ruleMapping[prop.key.value]; - if (mapping) { - recorder.remove(prop.key.start.offset + 1, prop.key.value.length); - recorder.insertLeft(prop.key.start.offset + 1, mapping); - } - }); - - host.commitUpdate(recorder); - - return host; - }; -} - -function updatePackageJson() { - return (host: Tree, context: SchematicContext) => { - const dependency: NodeDependency = { - type: NodeDependencyType.Dev, - name: 'codelyzer', - version: '^5.0.0', - overwrite: true, - }; - - addPackageJsonDependency(host, dependency); - context.addTask(new NodePackageInstallTask()); - - return host; - }; -} +import { updatePackageJson, updateTsLintConfig } from './codelyzer-5'; +import { dropES2015Polyfills } from './drop-es6-polyfills'; export default function(): Rule { return () => { return chain([ updateTsLintConfig(), updatePackageJson(), + dropES2015Polyfills(), ]); }; }