feat(@schematics/angular): add migration to add anyComponentStyle bundle budget

This commit is contained in:
Alan Agius 2019-07-23 19:58:40 +02:00 committed by vikerman
parent f32c9dde1a
commit 19b120947d
5 changed files with 175 additions and 61 deletions

View File

@ -12,11 +12,17 @@ import {
} from '@angular-devkit/core';
import { Rule, Tree, UpdateRecorder } from '@angular-devkit/schematics';
import {
appendValueInAstArray,
findPropertyInAstObject,
insertPropertyInAstObjectInOrder,
removePropertyInAstObject,
} from '../../utility/json-utils';
export const ANY_COMPONENT_STYLE_BUDGET = {
type: 'anyComponentStyle',
maximumWarning: '6kb',
};
export function UpdateWorkspaceConfig(): Rule {
return (tree: Tree) => {
let workspaceConfigPath = 'angular.json';
@ -59,8 +65,9 @@ export function UpdateWorkspaceConfig(): Rule {
const builder = findPropertyInAstObject(buildTarget, 'builder');
// Projects who's build builder is not build-angular:browser
if (builder && builder.kind === 'string' && builder.value === '@angular-devkit/build-angular:browser') {
updateOption('styles', recorder, buildTarget);
updateOption('scripts', recorder, buildTarget);
updateStyleOrScriptOption('styles', recorder, buildTarget);
updateStyleOrScriptOption('scripts', recorder, buildTarget);
addAnyComponentStyleBudget(recorder, buildTarget);
}
}
@ -69,8 +76,8 @@ export function UpdateWorkspaceConfig(): Rule {
const builder = findPropertyInAstObject(testTarget, 'builder');
// Projects who's build builder is not build-angular:browser
if (builder && builder.kind === 'string' && builder.value === '@angular-devkit/build-angular:karma') {
updateOption('styles', recorder, testTarget);
updateOption('scripts', recorder, testTarget);
updateStyleOrScriptOption('styles', recorder, testTarget);
updateStyleOrScriptOption('scripts', recorder, testTarget);
}
}
}
@ -84,19 +91,21 @@ export function UpdateWorkspaceConfig(): Rule {
/**
* Helper to retreive all the options in various configurations
*/
function getAllOptions(builderConfig: JsonAstObject): JsonAstObject[] {
function getAllOptions(builderConfig: JsonAstObject, configurationsOnly = false): JsonAstObject[] {
const options = [];
const configurations = findPropertyInAstObject(builderConfig, 'configurations');
if (configurations && configurations.kind === 'object') {
options.push(...configurations.properties.map(x => x.value));
}
options.push(findPropertyInAstObject(builderConfig, 'options'));
if (!configurationsOnly) {
options.push(findPropertyInAstObject(builderConfig, 'options'));
}
return options.filter(o => o && o.kind === 'object') as JsonAstObject[];
}
function updateOption(property: 'scripts' | 'styles', recorder: UpdateRecorder, builderConfig: JsonAstObject) {
function updateStyleOrScriptOption(property: 'scripts' | 'styles', recorder: UpdateRecorder, builderConfig: JsonAstObject) {
const options = getAllOptions(builderConfig);
for (const option of options) {
@ -121,3 +130,42 @@ function updateOption(property: 'scripts' | 'styles', recorder: UpdateRecorder,
}
}
}
function addAnyComponentStyleBudget(recorder: UpdateRecorder, builderConfig: JsonAstObject) {
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
insertPropertyInAstObjectInOrder(recorder, option, 'budgets', [ANY_COMPONENT_STYLE_BUDGET], 14);
continue;
}
if (budgetOption.kind !== 'array') {
continue;
}
// if 'anyComponentStyle' budget already exists don't add.
const hasAnyComponentStyle = budgetOption.elements.some(node => {
if (!node || node.kind !== 'object') {
// skip non complex objects
return false;
}
const budget = findPropertyInAstObject(node, 'type');
return !!budget && budget.kind === 'string' && budget.value === 'anyComponentStyle';
});
if (!hasAnyComponentStyle) {
appendValueInAstArray(recorder, budgetOption, ANY_COMPONENT_STYLE_BUDGET, 16);
}
}
}

View File

@ -8,9 +8,19 @@
import { EmptyTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { WorkspaceTargets } from '../../utility/workspace-models';
import { ANY_COMPONENT_STYLE_BUDGET } from './update-workspace-config';
function readWorkspaceConfig(tree: UnitTestTree) {
return JSON.parse(tree.readContent('/angular.json'));
// tslint:disable-next-line: no-any
function getWorkspaceTargets(tree: UnitTestTree): any {
return JSON.parse(tree.readContent(workspacePath))
.projects['migration-test'].architect;
}
function updateWorkspaceTargets(tree: UnitTestTree, workspaceTargets: WorkspaceTargets) {
const config = JSON.parse(tree.readContent(workspacePath));
config.projects['migration-test'].architect = workspaceTargets;
tree.overwrite(workspacePath, JSON.stringify(config, undefined, 2));
}
const scriptsWithLazy = [
@ -69,60 +79,95 @@ describe('Migration to version 9', () => {
.toPromise();
});
it('should update scripts in build target', () => {
let config = readWorkspaceConfig(tree);
let build = config.projects['migration-test'].architect.build;
build.options.scripts = scriptsWithLazy;
build.configurations.production.scripts = scriptsWithLazy;
describe('scripts and style options', () => {
it('should update scripts in build target', () => {
let config = getWorkspaceTargets(tree);
config.build.options.scripts = scriptsWithLazy;
config.build.configurations.production.scripts = scriptsWithLazy;
tree.overwrite(workspacePath, JSON.stringify(config));
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = readWorkspaceConfig(tree2);
build = config.projects['migration-test'].architect.build;
expect(build.options.scripts).toEqual(scriptsExpectWithLazy);
expect(build.configurations.production.scripts).toEqual(scriptsExpectWithLazy);
updateWorkspaceTargets(tree, config);
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = getWorkspaceTargets(tree2).build;
expect(config.options.scripts).toEqual(scriptsExpectWithLazy);
expect(config.configurations.production.scripts).toEqual(scriptsExpectWithLazy);
});
it('should update styles in build target', () => {
let config = getWorkspaceTargets(tree);
config.build.options.styles = stylesWithLazy;
config.build.configurations.production.styles = stylesWithLazy;
updateWorkspaceTargets(tree, config);
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = getWorkspaceTargets(tree2).build;
expect(config.options.styles).toEqual(stylesExpectWithLazy);
expect(config.configurations.production.styles).toEqual(stylesExpectWithLazy);
});
it('should update scripts in test target', () => {
let config = getWorkspaceTargets(tree);
config.test.options.scripts = scriptsWithLazy;
config.test.configurations = { production: { scripts: scriptsWithLazy } };
updateWorkspaceTargets(tree, config);
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = getWorkspaceTargets(tree2).test;
expect(config.options.scripts).toEqual(scriptsExpectWithLazy);
expect(config.configurations.production.scripts).toEqual(scriptsExpectWithLazy);
});
it('should update styles in test target', () => {
let config = getWorkspaceTargets(tree);
config.test.options.styles = stylesWithLazy;
config.test.configurations = { production: { styles: stylesWithLazy } };
updateWorkspaceTargets(tree, config);
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = getWorkspaceTargets(tree2).test;
expect(config.options.styles).toEqual(stylesExpectWithLazy);
expect(config.configurations.production.styles).toEqual(stylesExpectWithLazy);
});
});
it('should update styles in build target', () => {
let config = readWorkspaceConfig(tree);
let build = config.projects['migration-test'].architect.build;
build.options.styles = stylesWithLazy;
build.configurations.production.styles = stylesWithLazy;
describe('anyComponentStyle bundle budget', () => {
it('should not append budget when already exists', () => {
const defaultBudget = [
{ type: 'initial', maximumWarning: '2mb', maximumError: '5mb' },
{ type: 'anyComponentStyle', maximumWarning: '10kb', maximumError: '50kb' },
];
tree.overwrite(workspacePath, JSON.stringify(config));
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = readWorkspaceConfig(tree2);
build = config.projects['migration-test'].architect.build;
expect(build.options.styles).toEqual(stylesExpectWithLazy);
expect(build.configurations.production.styles).toEqual(stylesExpectWithLazy);
});
let config = getWorkspaceTargets(tree);
config.build.configurations.production.budgets = defaultBudget;
updateWorkspaceTargets(tree, config);
it('should update scripts in test target', () => {
let config = readWorkspaceConfig(tree);
let test = config.projects['migration-test'].architect.test;
test.options.scripts = scriptsWithLazy;
test.configurations = { production: { scripts: scriptsWithLazy } };
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = getWorkspaceTargets(tree2).build;
expect(config.configurations.production.budgets).toEqual(defaultBudget);
});
tree.overwrite(workspacePath, JSON.stringify(config));
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = readWorkspaceConfig(tree2);
test = config.projects['migration-test'].architect.test;
expect(test.options.scripts).toEqual(scriptsExpectWithLazy);
expect(test.configurations.production.scripts).toEqual(scriptsExpectWithLazy);
});
it('should append budget in build target', () => {
const defaultBudget = [{ type: 'initial', maximumWarning: '2mb', maximumError: '5mb' }];
let config = getWorkspaceTargets(tree);
config.build.configurations.production.budgets = defaultBudget;
updateWorkspaceTargets(tree, config);
it('should update styles in test target', () => {
let config = readWorkspaceConfig(tree);
let test = config.projects['migration-test'].architect.test;
test.options.styles = stylesWithLazy;
test.configurations = { production: { styles: stylesWithLazy } };
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = getWorkspaceTargets(tree2).build;
expect(config.configurations.production.budgets).toEqual([
...defaultBudget,
ANY_COMPONENT_STYLE_BUDGET,
]);
});
tree.overwrite(workspacePath, JSON.stringify(config));
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = readWorkspaceConfig(tree2);
test = config.projects['migration-test'].architect.test;
expect(test.options.styles).toEqual(stylesExpectWithLazy);
expect(test.configurations.production.styles).toEqual(stylesExpectWithLazy);
it('should add budget in build target', () => {
let config = getWorkspaceTargets(tree);
config.build.configurations.production.budgets = undefined;
updateWorkspaceTargets(tree, config);
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
config = getWorkspaceTargets(tree2).build;
expect(config.configurations.production.budgets).toEqual([ANY_COMPONENT_STYLE_BUDGET]);
});
});
});
});

View File

@ -130,7 +130,7 @@ export interface AppConfig {
/**
* The type of budget
*/
type?: ('bundle' | 'initial' | 'allScript' | 'all' | 'anyScript' | 'any');
type?: ('bundle' | 'initial' | 'allScript' | 'all' | 'anyScript' | 'any' | 'anyComponentStyle');
/**
* The name of the bundle
*/

View File

@ -31,7 +31,7 @@ export function appendPropertyInAstObject(
recorder.insertRight(commaIndex, ',');
index = end.offset;
}
const content = JSON.stringify(value, null, indent).replace(/\n/g, indentStr);
const content = _stringifyContent(value, indentStr);
recorder.insertRight(
index,
(node.properties.length === 0 && indent ? '\n' : '')
@ -84,7 +84,7 @@ export function insertPropertyInAstObjectInOrder(
const insertIndex = insertAfterProp === null
? node.start.offset + 1
: insertAfterProp.end.offset + 1;
const content = JSON.stringify(value, null, indent).replace(/\n/g, indentStr);
const content = _stringifyContent(value, indentStr);
recorder.insertRight(
insertIndex,
indentStr
@ -168,7 +168,7 @@ export function appendValueInAstArray(
index,
(node.elements.length === 0 && indent ? '\n' : '')
+ ' '.repeat(indent)
+ JSON.stringify(value, null, indent).replace(/\n/g, indentStr)
+ _stringifyContent(value, indentStr)
+ indentStr.slice(0, -indent),
);
}
@ -191,3 +191,24 @@ export function findPropertyInAstObject(
function _buildIndent(count: number): string {
return count ? '\n' + ' '.repeat(count) : '';
}
function _stringifyContent(value: JsonValue, indentStr: string): string {
// TODO: Add snapshot tests
// The 'space' value is 2, because we want to add 2 additional
// indents from the 'key' node.
// If we use the indent provided we will have double indents:
// "budgets": [
// {
// "type": "initial",
// "maximumWarning": "2mb",
// "maximumError": "5mb"
// },
// {
// "type": "anyComponentStyle",
// 'maximumWarning": "5kb"
// }
// ]
return JSON.stringify(value, null, 2).replace(/\n/g, indentStr);
}

View File

@ -38,8 +38,8 @@ export interface BrowserBuilderBaseOptions {
index?: string;
polyfills: string;
assets?: (object|string)[];
styles?: string[];
scripts?: string[];
styles?: (object|string)[];
scripts?: (object|string)[];
sourceMap?: boolean;
}