Skip to content

Commit

Permalink
feat(@schematics/angular): add migration to enable AOT by default
Browse files Browse the repository at this point in the history
With this change we enable the AOT option for the browser builder when an application will use Ivy as rendering engine.
  • Loading branch information
alan-agius4 authored and vikerman committed Aug 29, 2019
1 parent 87b01ff commit f4691a5
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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)) {
Expand All @@ -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);

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
});
39 changes: 38 additions & 1 deletion packages/schematics/angular/migrations/update-9/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}
107 changes: 107 additions & 0 deletions packages/schematics/angular/migrations/update-9/utils_spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});

0 comments on commit f4691a5

Please sign in to comment.