diff --git a/packages/schematics/angular/migrations/update-8/drop-es6-polyfills.ts b/packages/schematics/angular/migrations/update-8/drop-es6-polyfills.ts index 7ae0c08595fa..8d286d45e7c8 100644 --- a/packages/schematics/angular/migrations/update-8/drop-es6-polyfills.ts +++ b/packages/schematics/angular/migrations/update-8/drop-es6-polyfills.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import { Rule, Tree } from '@angular-devkit/schematics'; +import { JsonParseMode, isJsonObject, join, normalize, parseJson } from '@angular-devkit/core'; +import { Rule, Tree, chain, noop } from '@angular-devkit/schematics'; import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript'; const toDrop: {[importName: string]: true} = { @@ -26,35 +27,52 @@ const toDrop: {[importName: string]: true} = { }; const header = `/** -*/ + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ /*************************************************************************************************** * BROWSER POLYFILLS */ /** IE9, IE10 and IE11 requires all of the following polyfills. **/ -// import 'core-js/es6/weak-map';`; +// import 'core-js/es6/weak-map'; +`; const applicationPolyfillsHeader = 'APPLICATION IMPORTS'; -export const dropES2015Polyfills = (): Rule => { +function dropES2015PolyfillsFromFile(polyfillPath: string): Rule { return (tree: Tree) => { - const path = '/polyfills.ts'; - const source = tree.read(path); + const source = tree.read(polyfillPath); if (!source) { - return; + return noop(); } // Start the update of the file. - const recorder = tree.beginUpdate(path); + const recorder = tree.beginUpdate(polyfillPath); - const sourceFile = ts.createSourceFile(path, source.toString(), ts.ScriptTarget.Latest, true); + const sourceFile = ts.createSourceFile(polyfillPath, + source.toString(), + ts.ScriptTarget.Latest, + true, + ); const imports = sourceFile.statements - .filter(s => s.kind === ts.SyntaxKind.ImportDeclaration) as ts.ImportDeclaration[]; + .filter(s => s.kind === ts.SyntaxKind.ImportDeclaration) as ts.ImportDeclaration[]; const applicationPolyfillsStart = sourceFile.getText().indexOf(applicationPolyfillsHeader); - if (imports.length === 0) { return; } + if (imports.length === 0) { return noop(); } for (const i of imports) { const module = ts.isStringLiteral(i.moduleSpecifier) && i.moduleSpecifier.text; @@ -73,4 +91,43 @@ export const dropES2015Polyfills = (): Rule => { tree.commitUpdate(recorder); }; -}; +} + +/** + * Move the import reflect metadata polyfill from the polyfill file to the dev environment. This is + * not guaranteed to work, but if it doesn't it will result in no changes made. + */ +export function dropES2015Polyfills(): Rule { + return (tree) => { + // Simple. Take the ast of polyfills (if it exists) and find the import metadata. Remove it. + const angularConfigContent = tree.read('angular.json') || tree.read('.angular.json'); + const rules: Rule[] = []; + + if (!angularConfigContent) { + // Is this even an angular project? + return; + } + + const angularJson = parseJson(angularConfigContent.toString(), JsonParseMode.Loose); + + if (!isJsonObject(angularJson) || !isJsonObject(angularJson.projects)) { + // If that field isn't there, no use... + return; + } + + // For all projects + for (const projectName of Object.keys(angularJson.projects)) { + const project = angularJson.projects[projectName]; + if (!isJsonObject(project)) { + continue; + } + if (typeof project.sourceRoot != 'string' || project.projectType !== 'application') { + continue; + } + + rules.push(dropES2015PolyfillsFromFile(join(normalize(project.sourceRoot), '/polyfills.ts'))); + } + + return chain(rules); + }; +} 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 index 320847061ae1..a3ee61950904 100644 --- a/packages/schematics/angular/migrations/update-8/drop-es6-polyfills_spec.ts +++ b/packages/schematics/angular/migrations/update-8/drop-es6-polyfills_spec.ts @@ -16,7 +16,7 @@ describe('Migration to version 8', () => { ); let tree: UnitTestTree; - const polyfillsPath = '/polyfills.ts'; + const polyfillsPath = '/src/polyfills.ts'; const defaultOptions = {}; const polyfills = `/** */ @@ -61,18 +61,19 @@ import 'zone.js/dist/zone'; // Included with Angular CLI. */ `; - const packageJson = { - devDependencies: { - codelyzer: '^4.5.0', - }, - }; - const packageJsonPath = '/package.json'; - describe('Migration to differential polyfill loading', () => { - beforeEach(() => { + beforeEach(async () => { tree = new UnitTestTree(new EmptyTree()); - tree.create(polyfillsPath, polyfills); - tree.create(packageJsonPath, JSON.stringify(packageJson, null, 2)); + tree = await schematicRunner.runExternalSchematicAsync( + require.resolve('../../collection.json'), 'ng-new', + { + name: 'migration-test', + version: '1.2.3', + directory: '.', + }, + tree, + ).toPromise(); + tree.overwrite(polyfillsPath, polyfills); }); it('should drop the es6 polyfills', () => { @@ -89,5 +90,25 @@ import 'zone.js/dist/zone'; // Included with Angular CLI. expect(polyfills).toContain('BROWSER POLYFILLS'); expect(polyfills).toContain('core-js/es6/weak-map'); }); + + it('should work as expected for a project with a root', async () => { + const originalContent = JSON.parse(tree.readContent('angular.json')); + originalContent.projects['migration-test'].sourceRoot = 'foo/src'; + tree.overwrite('angular.json', JSON.stringify(originalContent)); + const polyfillPath = '/foo/src/polyfills.ts'; + tree.create(polyfillPath, polyfills); + tree = schematicRunner.runSchematic('migration-07', defaultOptions, tree); + const newPolyfills = tree.readContent(polyfillPath); + expect(newPolyfills).not.toContain('core-js/es6/symbol'); + expect(newPolyfills).not.toContain('core-js/es6/set'); + expect(newPolyfills).toContain('zone.js'); + expect(newPolyfills).toContain('Zone'); + + // We don't want to drop this commented import comments + expect(newPolyfills).toContain('core-js/es6/reflect'); + expect(newPolyfills).toContain('core-js/es7/reflect'); + expect(newPolyfills).toContain('BROWSER POLYFILLS'); + expect(newPolyfills).toContain('core-js/es6/weak-map'); + }); }); });