Skip to content

Move reflect metadata from the polyfills to the dev environment file. #12314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/schematics
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@

require('../lib/bootstrap-local');
const packages = require('../lib/packages').packages;
require(packages['@angular-devkit/schematics-cli'].bin['schematics']);
require(packages['@angular-devkit/schematics-cli'].bin['schematics']).main({ args: process.argv.slice(2) });
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
"version": "7.0.0-beta.0",
"factory": "./update-7",
"description": "Update an Angular CLI project to version 7."
},
"migration-03": {
"version": "7.0.0-rc.0",
"factory": "./update-7/index#polyfillMetadataRule",
"description": "Update an Angular CLI project to version 7."
}
}
}
}
1 change: 1 addition & 0 deletions packages/schematics/angular/migrations/update-7/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '../../utility/dependencies';
import { latestVersions } from '../../utility/latest-versions';

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

export default function(): Rule {
return (tree, context) => {
Expand Down
136 changes: 136 additions & 0 deletions packages/schematics/angular/migrations/update-7/polyfill-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* @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 { json } from '@angular-devkit/core';
import { Rule, Tree, chain, noop } from '@angular-devkit/schematics';
import * as ts from 'typescript';


/**
* Remove the Reflect import from a polyfill file.
* @param tree The tree to use.
* @param path Path of the polyfill file found.
* @private
*/
function _removeReflectFromPolyfills(tree: Tree, path: string) {
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);
const imports = (
sourceFile.statements
.filter(s => s.kind === ts.SyntaxKind.ImportDeclaration) as ts.ImportDeclaration[]
);

for (const i of imports) {
const module = i.moduleSpecifier.kind == ts.SyntaxKind.StringLiteral
&& (i.moduleSpecifier as ts.StringLiteral).text;

switch (module) {
case 'core-js/es7/reflect':
recorder.remove(i.getStart(sourceFile), i.getWidth(sourceFile));
break;
}
}

tree.commitUpdate(recorder);
}

/**
* Update a project's target, maybe. Only if it's a builder supported and the options look right.
* This is a rule factory so we return the new rule (or noop if we don't support doing the change).
* @param root The root of the project source.
* @param targetObject The target information.
* @private
*/
function _updateProjectTarget(root: string, targetObject: json.JsonObject): Rule {
// Make sure we're using the correct builder.
if (targetObject.builder !== '@angular-devkit/build-angular:browser'
|| !json.isJsonObject(targetObject.options)) {
return noop();
}
const options = targetObject.options;
if (typeof options.polyfills != 'string') {
return noop();
}

const polyfillsToUpdate = [`${root}/${options.polyfills}`];
const configurations = targetObject.configurations;
if (json.isJsonObject(configurations)) {
for (const configName of Object.keys(configurations)) {
const config = configurations[configName];

// Just in case, only do non-AOT configurations.
if (json.isJsonObject(config)
&& typeof config.polyfills == 'string'
&& config.aot !== true) {
polyfillsToUpdate.push(`${root}/${config.polyfills}`);
}
}
}

return chain(
polyfillsToUpdate.map(polyfillPath => {
return (tree: Tree) => _removeReflectFromPolyfills(tree, polyfillPath);
}),
);
}

/**
* 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 polyfillMetadataRule(): 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 = json.parseJson(angularConfigContent.toString(), json.JsonParseMode.Loose);

if (!json.isJsonObject(angularJson) || !json.isJsonObject(angularJson.projects)) {
// If that field isn't there, no use...
return;
}

// For all projects, for all targets, read the polyfill field, and read the environment.
for (const projectName of Object.keys(angularJson.projects)) {
const project = angularJson.projects[projectName];
if (!json.isJsonObject(project)) {
continue;
}
if (typeof project.root != 'string') {
continue;
}

const targets = project.targets || project.architect;
if (!json.isJsonObject(targets)) {
continue;
}

for (const targetName of Object.keys(targets)) {
const target = targets[targetName];
if (json.isJsonObject(target)) {
rules.push(_updateProjectTarget(project.root, target));
}
}
}

// Remove null or undefined rules.
return chain(rules);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* @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 { Action, EmptyTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';

// Randomly import stuff (including es6 and es7 reflects).
const oldPolyfills = `
/** 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';

/** 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';

import 'web-animations-js'; // Run \`npm install --save web-animations-js\`.

(window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
(window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick

/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
`;


describe('polyfillMetadataRule', () => {
const schematicRunner = new SchematicTestRunner(
'migrations',
require.resolve('../migration-collection.json'),
);

let tree: UnitTestTree;

beforeEach(async () => {
tree = new UnitTestTree(new EmptyTree());
tree = await schematicRunner.runExternalSchematicAsync(
require.resolve('../../collection.json'), 'ng-new',
{
name: 'migration-test',
version: '1.2.3',
directory: '.',
},
tree,
).toPromise();
});

it('is noop for new projects', async () => {
const mapToIdem = (x: Action) => {
const content = (x.kind == 'o' || x.kind == 'c') ? x.content.toString() : null;

return { ...x, content, id: -1 };
};

const expected = [...tree.actions.map(mapToIdem)];
const tree2 = await schematicRunner.runSchematicAsync('migration-03', {}, tree.branch())
.toPromise();

expect(tree2.actions.map(mapToIdem)).toEqual(expected);
});

it('should work as expected', async () => {
const polyfillPath = '/src/polyfills.ts';
tree.overwrite(polyfillPath, oldPolyfills);
const tree2 = await schematicRunner.runSchematicAsync('migration-03', {}, tree.branch())
.toPromise();

expect(tree2.readContent(polyfillPath)).not.toMatch(/import .*es7.*reflect.*;/);
});
});