feat(@schematics/angular): add migration to enable AOT by default

With this change we enable the AOT option for the browser builder when an application will use Ivy as rendering engine.
This commit is contained in:
Alan 2019-08-23 13:52:10 +02:00 committed by vikerman
parent 87b01ffd6a
commit f4691a545a
4 changed files with 261 additions and 8 deletions

View File

@ -15,7 +15,7 @@ import {
removePropertyInAstObject,
} from '../../utility/json-utils';
import { Builders } from '../../utility/workspace-models';
import { getAllOptions, getTargets, getWorkspace } from './utils';
import { getAllOptions, getTargets, getWorkspace, isIvyEnabled } from './utils';
export const ANY_COMPONENT_STYLE_BUDGET = {
type: 'anyComponentStyle',
@ -32,6 +32,7 @@ export function UpdateWorkspaceConfig(): Rule {
updateStyleOrScriptOption('styles', recorder, target);
updateStyleOrScriptOption('scripts', recorder, target);
addAnyComponentStyleBudget(recorder, target);
updateAotOption(tree, recorder, target);
}
for (const { target } of getTargets(workspace, 'test', Builders.Karma)) {
@ -45,6 +46,41 @@ export function UpdateWorkspaceConfig(): Rule {
};
}
function updateAotOption(tree: Tree, recorder: UpdateRecorder, builderConfig: JsonAstObject) {
const options = findPropertyInAstObject(builderConfig, 'options');
if (!options || options.kind !== 'object') {
return;
}
const tsConfig = findPropertyInAstObject(options, 'tsConfig');
// Do not add aot option if the users already opted out from Ivy.
if (tsConfig && tsConfig.kind === 'string' && !isIvyEnabled(tree, tsConfig.value)) {
return;
}
// Add aot to options.
const aotOption = findPropertyInAstObject(options, 'aot');
if (!aotOption) {
insertPropertyInAstObjectInOrder(recorder, options, 'aot', true, 12);
return;
}
if (aotOption.kind !== 'true') {
const { start, end } = aotOption;
recorder.remove(start.offset, end.offset - start.offset);
recorder.insertLeft(start.offset, 'true');
}
// Remove aot properties from other configurations as they are no redundant
const configOptions = getAllOptions(builderConfig, true);
for (const options of configOptions) {
removePropertyInAstObject(recorder, options, 'aot');
}
}
function updateStyleOrScriptOption(property: 'scripts' | 'styles', recorder: UpdateRecorder, builderConfig: JsonAstObject) {
const options = getAllOptions(builderConfig);
@ -75,12 +111,6 @@ function addAnyComponentStyleBudget(recorder: UpdateRecorder, builderConfig: Jso
const options = getAllOptions(builderConfig, true);
for (const option of options) {
const aotOption = findPropertyInAstObject(option, 'aot');
if (!aotOption || aotOption.kind !== 'true') {
// AnyComponentStyle only works for AOT
continue;
}
const budgetOption = findPropertyInAstObject(option, 'budgets');
if (!budgetOption) {
// add

View File

@ -169,5 +169,84 @@ describe('Migration to version 9', () => {
expect(config.configurations.production.budgets).toEqual([ANY_COMPONENT_STYLE_BUDGET]);
});
});
describe('aot option', () => {
it('should update aot option when false', async () => {
let config = getWorkspaceTargets(tree);
config.build.options.aot = false;
updateWorkspaceTargets(tree, config);
const tree2 = await schematicRunner.runSchematicAsync('migration-09', {}, tree.branch()).toPromise();
config = getWorkspaceTargets(tree2).build;
expect(config.options.aot).toBe(true);
});
it('should add aot option when not defined', async () => {
let config = getWorkspaceTargets(tree);
config.build.options.aot = undefined;
updateWorkspaceTargets(tree, config);
const tree2 = await schematicRunner.runSchematicAsync('migration-09', {}, tree.branch()).toPromise();
config = getWorkspaceTargets(tree2).build;
expect(config.options.aot).toBe(true);
});
it('should not aot option when opted-out of Ivy', async () => {
const tsConfig = JSON.stringify(
{
extends: './tsconfig.json',
angularCompilerOptions: {
enableIvy: false,
},
},
null,
2,
);
tree.overwrite('/tsconfig.app.json', tsConfig);
let config = getWorkspaceTargets(tree);
config.build.options.aot = false;
updateWorkspaceTargets(tree, config);
const tree2 = await schematicRunner.runSchematicAsync('migration-09', {}, tree.branch()).toPromise();
config = getWorkspaceTargets(tree2).build;
expect(config.options.aot).toBe(false);
});
it('should not aot option when opted-out of Ivy in workspace', async () => {
const tsConfig = JSON.stringify(
{
angularCompilerOptions: {
enableIvy: false,
},
},
null,
2,
);
tree.overwrite('/tsconfig.json', tsConfig);
let config = getWorkspaceTargets(tree);
config.build.options.aot = false;
updateWorkspaceTargets(tree, config);
const tree2 = await schematicRunner.runSchematicAsync('migration-09', {}, tree.branch()).toPromise();
config = getWorkspaceTargets(tree2).build;
expect(config.options.aot).toBe(false);
});
it('should remove aot option from production configuration', async () => {
let config = getWorkspaceTargets(tree);
config.build.options.aot = false;
config.build.configurations.production.aot = true;
updateWorkspaceTargets(tree, config);
const tree2 = await schematicRunner.runSchematicAsync('migration-09', {}, tree.branch()).toPromise();
config = getWorkspaceTargets(tree2).build;
expect(config.options.aot).toBe(true);
expect(config.configurations.production.aot).toBeUndefined();
});
});
});
});

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import { JsonAstObject, JsonParseMode, parseJsonAst } from '@angular-devkit/core';
import { JsonAstObject, JsonParseMode, dirname, normalize, parseJsonAst, resolve } from '@angular-devkit/core';
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { getWorkspacePath } from '../../utility/config';
import { findPropertyInAstObject } from '../../utility/json-utils';
@ -81,3 +81,40 @@ export function getWorkspace(host: Tree): JsonAstObject {
return parseJsonAst(content, JsonParseMode.Loose) as JsonAstObject;
}
export function isIvyEnabled(tree: Tree, tsConfigPath: string): boolean {
// In version 9, Ivy is turned on by default
// Ivy is opted out only when 'enableIvy' is set to false.
const buffer = tree.read(tsConfigPath);
if (!buffer) {
return true;
}
const tsCfgAst = parseJsonAst(buffer.toString(), JsonParseMode.Loose);
if (tsCfgAst.kind !== 'object') {
return true;
}
const ngCompilerOptions = findPropertyInAstObject(tsCfgAst, 'angularCompilerOptions');
if (ngCompilerOptions && ngCompilerOptions.kind === 'object') {
const enableIvy = findPropertyInAstObject(ngCompilerOptions, 'enableIvy');
if (enableIvy) {
return !!enableIvy.value;
}
}
const configExtends = findPropertyInAstObject(tsCfgAst, 'extends');
if (configExtends && configExtends.kind === 'string') {
const extendedTsConfigPath = resolve(
dirname(normalize(tsConfigPath)),
normalize(configExtends.value),
);
return isIvyEnabled(tree, extendedTsConfigPath);
}
return true;
}

View File

@ -0,0 +1,107 @@
/**
* @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 { HostTree } from '@angular-devkit/schematics';
import { isIvyEnabled } from './utils';
describe('migrations update-9 utils', () => {
describe('isIvyEnabled', () => {
let tree: HostTree;
beforeEach(() => {
tree = new HostTree();
});
it('should return false when disabled in base tsconfig', () => {
tree.create('tsconfig.json', JSON.stringify({
angularCompilerOptions: {
enableIvy: false,
},
}));
tree.create('foo/tsconfig.app.json', JSON.stringify({
extends: '../tsconfig.json',
}));
expect(isIvyEnabled(tree, 'foo/tsconfig.app.json')).toBe(false);
});
it('should return true when enable in child tsconfig but disabled in base tsconfig', () => {
tree.create('tsconfig.json', JSON.stringify({
angularCompilerOptions: {
enableIvy: false,
},
}));
tree.create('foo/tsconfig.app.json', JSON.stringify({
extends: '../tsconfig.json',
angularCompilerOptions: {
enableIvy: true,
},
}));
expect(isIvyEnabled(tree, 'foo/tsconfig.app.json')).toBe(true);
});
it('should return false when disabled in child tsconfig but enabled in base tsconfig', () => {
tree.create('tsconfig.json', JSON.stringify({
angularCompilerOptions: {
enableIvy: true,
},
}));
tree.create('foo/tsconfig.app.json', JSON.stringify({
extends: '../tsconfig.json',
angularCompilerOptions: {
enableIvy: false,
},
}));
expect(isIvyEnabled(tree, 'foo/tsconfig.app.json')).toBe(false);
});
it('should return false when disabled in base with multiple extends', () => {
tree.create('tsconfig.json', JSON.stringify({
angularCompilerOptions: {
enableIvy: false,
},
}));
tree.create('foo/tsconfig.project.json', JSON.stringify({
extends: '../tsconfig.json',
}));
tree.create('foo/tsconfig.app.json', JSON.stringify({
extends: './tsconfig.project.json',
}));
expect(isIvyEnabled(tree, 'foo/tsconfig.app.json')).toBe(false);
});
it('should return true when enable in intermediate tsconfig with multiple extends', () => {
tree.create('tsconfig.json', JSON.stringify({
angularCompilerOptions: {
enableIvy: false,
},
}));
tree.create('foo/tsconfig.project.json', JSON.stringify({
extends: '../tsconfig.json',
angularCompilerOptions: {
enableIvy: true,
},
}));
tree.create('foo/tsconfig.app.json', JSON.stringify({
extends: './tsconfig.project.json',
}));
expect(isIvyEnabled(tree, 'foo/tsconfig.app.json')).toBe(true);
});
});
});