Skip to content

Commit ef32681

Browse files
committed
feat(@schematics/angular): new 7.0.0 migration to remove polyfills
See PR angular#12346 for more information. This PR migrates current CLI projects to the new polyfills introduced in that PR.
1 parent a0a333b commit ef32681

File tree

4 files changed

+232
-1
lines changed

4 files changed

+232
-1
lines changed

packages/schematics/angular/migrations/migration-collection.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
"version": "7.0.0-beta.0",
1010
"factory": "./update-7",
1111
"description": "Update an Angular CLI project to version 7."
12+
},
13+
"migration-03": {
14+
"version": "7.0.0-rc.0",
15+
"factory": "./update-7/index#polyfillMetadataRule",
16+
"description": "Update an Angular CLI project to version 7."
1217
}
1318
}
14-
}
19+
}

packages/schematics/angular/migrations/update-7/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from '../../utility/dependencies';
1414
import { latestVersions } from '../../utility/latest-versions';
1515

16+
export { polyfillMetadataRule } from './polyfill-metadata';
1617

1718
export default function(): Rule {
1819
return (tree, context) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { json } from '@angular-devkit/core';
9+
import { Rule, Tree, chain, noop } from '@angular-devkit/schematics';
10+
import * as ts from 'typescript';
11+
12+
13+
/**
14+
* Remove the Reflect import from a polyfill file.
15+
* @param tree The tree to use.
16+
* @param path Path of the polyfill file found.
17+
* @private
18+
*/
19+
function _removeReflectFromPolyfills(tree: Tree, path: string) {
20+
const source = tree.read(path);
21+
if (!source) {
22+
return;
23+
}
24+
25+
// Start the update of the file.
26+
const recorder = tree.beginUpdate(path);
27+
28+
const sourceFile = ts.createSourceFile(path, source.toString(), ts.ScriptTarget.Latest);
29+
const imports = (
30+
sourceFile.statements
31+
.filter(s => s.kind === ts.SyntaxKind.ImportDeclaration) as ts.ImportDeclaration[]
32+
);
33+
34+
for (const i of imports) {
35+
const module = i.moduleSpecifier.kind == ts.SyntaxKind.StringLiteral
36+
&& (i.moduleSpecifier as ts.StringLiteral).text;
37+
38+
switch (module) {
39+
case 'core-js/es7/reflect':
40+
recorder.remove(i.getStart(sourceFile), i.getWidth(sourceFile));
41+
break;
42+
}
43+
}
44+
45+
tree.commitUpdate(recorder);
46+
}
47+
48+
/**
49+
* Update a project's target, maybe. Only if it's a builder supported and the options look right.
50+
* This is a rule factory so we return the new rule (or noop if we don't support doing the change).
51+
* @param root The root of the project source.
52+
* @param targetObject The target information.
53+
* @private
54+
*/
55+
function _updateProjectTarget(root: string, targetObject: json.JsonObject): Rule {
56+
// Make sure we're using the correct builder.
57+
if (targetObject.builder !== '@angular-devkit/build-angular:browser'
58+
|| !json.isJsonObject(targetObject.options)) {
59+
return noop();
60+
}
61+
const options = targetObject.options;
62+
if (typeof options.polyfills != 'string') {
63+
return noop();
64+
}
65+
66+
const polyfillsToUpdate = [`${root}/${options.polyfills}`];
67+
const configurations = targetObject.configurations;
68+
if (json.isJsonObject(configurations)) {
69+
for (const configName of Object.keys(configurations)) {
70+
const config = configurations[configName];
71+
72+
// Just in case, only do non-AOT configurations.
73+
if (json.isJsonObject(config)
74+
&& typeof config.polyfills == 'string'
75+
&& config.aot !== true) {
76+
polyfillsToUpdate.push(`${root}/${config.polyfills}`);
77+
}
78+
}
79+
}
80+
81+
return chain(
82+
polyfillsToUpdate.map(polyfillPath => {
83+
return (tree: Tree) => _removeReflectFromPolyfills(tree, polyfillPath);
84+
}),
85+
);
86+
}
87+
88+
/**
89+
* Move the import reflect metadata polyfill from the polyfill file to the dev environment. This is
90+
* not guaranteed to work, but if it doesn't it will result in no changes made.
91+
*/
92+
export function polyfillMetadataRule(): Rule {
93+
return (tree) => {
94+
// Simple. Take the ast of polyfills (if it exists) and find the import metadata. Remove it.
95+
const angularConfigContent = tree.read('angular.json') || tree.read('.angular.json');
96+
const rules: Rule[] = [];
97+
98+
if (!angularConfigContent) {
99+
// Is this even an angular project?
100+
return;
101+
}
102+
103+
const angularJson = json.parseJson(angularConfigContent.toString(), json.JsonParseMode.Loose);
104+
105+
if (!json.isJsonObject(angularJson) || !json.isJsonObject(angularJson.projects)) {
106+
// If that field isn't there, no use...
107+
return;
108+
}
109+
110+
// For all projects, for all targets, read the polyfill field, and read the environment.
111+
for (const projectName of Object.keys(angularJson.projects)) {
112+
const project = angularJson.projects[projectName];
113+
if (!json.isJsonObject(project)) {
114+
continue;
115+
}
116+
if (typeof project.root != 'string') {
117+
continue;
118+
}
119+
120+
const targets = project.targets || project.architect;
121+
if (!json.isJsonObject(targets)) {
122+
continue;
123+
}
124+
125+
for (const targetName of Object.keys(targets)) {
126+
const target = targets[targetName];
127+
if (json.isJsonObject(target)) {
128+
rules.push(_updateProjectTarget(project.root, target));
129+
}
130+
}
131+
}
132+
133+
// Remove null or undefined rules.
134+
return chain(rules);
135+
};
136+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { Action, EmptyTree } from '@angular-devkit/schematics';
9+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
10+
11+
// Randomly import stuff (including es6 and es7 reflects).
12+
const oldPolyfills = `
13+
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
14+
// import 'core-js/es6/symbol';
15+
// import 'core-js/es6/object';
16+
import 'core-js/es6/function';
17+
import 'core-js/es6/parse-int';
18+
// import 'core-js/es6/parse-float';
19+
import 'core-js/es6/number';
20+
// import 'core-js/es6/math';
21+
// import 'core-js/es6/string';
22+
import 'core-js/es6/date';
23+
// import 'core-js/es6/array';
24+
// import 'core-js/es6/regexp';
25+
26+
/** IE10 and IE11 requires the following for the Reflect API. */
27+
import 'core-js/es6/reflect';
28+
29+
30+
/** Evergreen browsers require these. **/
31+
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
32+
import 'core-js/es7/reflect';
33+
34+
import 'web-animations-js'; // Run \`npm install --save web-animations-js\`.
35+
36+
(window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
37+
(window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
38+
39+
/***************************************************************************************************
40+
* Zone JS is required by default for Angular itself.
41+
*/
42+
import 'zone.js/dist/zone'; // Included with Angular CLI.
43+
`;
44+
45+
46+
describe('polyfillMetadataRule', () => {
47+
const schematicRunner = new SchematicTestRunner(
48+
'migrations',
49+
require.resolve('../migration-collection.json'),
50+
);
51+
52+
let tree: UnitTestTree;
53+
54+
beforeEach(async () => {
55+
tree = new UnitTestTree(new EmptyTree());
56+
tree = await schematicRunner.runExternalSchematicAsync(
57+
require.resolve('../../collection.json'), 'ng-new',
58+
{
59+
name: 'migration-test',
60+
version: '1.2.3',
61+
directory: '.',
62+
},
63+
tree,
64+
).toPromise();
65+
});
66+
67+
it('is noop for new projects', async () => {
68+
const mapToIdem = (x: Action) => {
69+
const content = (x.kind == 'o' || x.kind == 'c') ? x.content.toString() : null;
70+
71+
return { ...x, content, id: -1 };
72+
};
73+
74+
const expected = [...tree.actions.map(mapToIdem)];
75+
const tree2 = await schematicRunner.runSchematicAsync('migration-03', {}, tree.branch())
76+
.toPromise();
77+
78+
expect(tree2.actions.map(mapToIdem)).toEqual(expected);
79+
});
80+
81+
it('should work as expected', async () => {
82+
const polyfillPath = '/src/polyfills.ts';
83+
tree.overwrite(polyfillPath, oldPolyfills);
84+
const tree2 = await schematicRunner.runSchematicAsync('migration-03', {}, tree.branch())
85+
.toPromise();
86+
87+
expect(tree2.readContent(polyfillPath)).not.toMatch(/import .*es7.*reflect.*;/);
88+
});
89+
});

0 commit comments

Comments
 (0)