Skip to content

Commit 19b1209

Browse files
alan-agius4vikerman
authored andcommitted
feat(@schematics/angular): add migration to add anyComponentStyle bundle budget
1 parent f32c9dd commit 19b1209

File tree

5 files changed

+181
-67
lines changed

5 files changed

+181
-67
lines changed

packages/schematics/angular/migrations/update-9/update-workspace-config.ts

+55-7
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@ import {
1212
} from '@angular-devkit/core';
1313
import { Rule, Tree, UpdateRecorder } from '@angular-devkit/schematics';
1414
import {
15+
appendValueInAstArray,
1516
findPropertyInAstObject,
1617
insertPropertyInAstObjectInOrder,
1718
removePropertyInAstObject,
1819
} from '../../utility/json-utils';
1920

21+
export const ANY_COMPONENT_STYLE_BUDGET = {
22+
type: 'anyComponentStyle',
23+
maximumWarning: '6kb',
24+
};
25+
2026
export function UpdateWorkspaceConfig(): Rule {
2127
return (tree: Tree) => {
2228
let workspaceConfigPath = 'angular.json';
@@ -59,8 +65,9 @@ export function UpdateWorkspaceConfig(): Rule {
5965
const builder = findPropertyInAstObject(buildTarget, 'builder');
6066
// Projects who's build builder is not build-angular:browser
6167
if (builder && builder.kind === 'string' && builder.value === '@angular-devkit/build-angular:browser') {
62-
updateOption('styles', recorder, buildTarget);
63-
updateOption('scripts', recorder, buildTarget);
68+
updateStyleOrScriptOption('styles', recorder, buildTarget);
69+
updateStyleOrScriptOption('scripts', recorder, buildTarget);
70+
addAnyComponentStyleBudget(recorder, buildTarget);
6471
}
6572
}
6673

@@ -69,8 +76,8 @@ export function UpdateWorkspaceConfig(): Rule {
6976
const builder = findPropertyInAstObject(testTarget, 'builder');
7077
// Projects who's build builder is not build-angular:browser
7178
if (builder && builder.kind === 'string' && builder.value === '@angular-devkit/build-angular:karma') {
72-
updateOption('styles', recorder, testTarget);
73-
updateOption('scripts', recorder, testTarget);
79+
updateStyleOrScriptOption('styles', recorder, testTarget);
80+
updateStyleOrScriptOption('scripts', recorder, testTarget);
7481
}
7582
}
7683
}
@@ -84,19 +91,21 @@ export function UpdateWorkspaceConfig(): Rule {
8491
/**
8592
* Helper to retreive all the options in various configurations
8693
*/
87-
function getAllOptions(builderConfig: JsonAstObject): JsonAstObject[] {
94+
function getAllOptions(builderConfig: JsonAstObject, configurationsOnly = false): JsonAstObject[] {
8895
const options = [];
8996
const configurations = findPropertyInAstObject(builderConfig, 'configurations');
9097
if (configurations && configurations.kind === 'object') {
9198
options.push(...configurations.properties.map(x => x.value));
9299
}
93100

94-
options.push(findPropertyInAstObject(builderConfig, 'options'));
101+
if (!configurationsOnly) {
102+
options.push(findPropertyInAstObject(builderConfig, 'options'));
103+
}
95104

96105
return options.filter(o => o && o.kind === 'object') as JsonAstObject[];
97106
}
98107

99-
function updateOption(property: 'scripts' | 'styles', recorder: UpdateRecorder, builderConfig: JsonAstObject) {
108+
function updateStyleOrScriptOption(property: 'scripts' | 'styles', recorder: UpdateRecorder, builderConfig: JsonAstObject) {
100109
const options = getAllOptions(builderConfig);
101110

102111
for (const option of options) {
@@ -121,3 +130,42 @@ function updateOption(property: 'scripts' | 'styles', recorder: UpdateRecorder,
121130
}
122131
}
123132
}
133+
134+
function addAnyComponentStyleBudget(recorder: UpdateRecorder, builderConfig: JsonAstObject) {
135+
const options = getAllOptions(builderConfig, true);
136+
137+
for (const option of options) {
138+
const aotOption = findPropertyInAstObject(option, 'aot');
139+
if (!aotOption || aotOption.kind !== 'true') {
140+
// AnyComponentStyle only works for AOT
141+
continue;
142+
}
143+
144+
const budgetOption = findPropertyInAstObject(option, 'budgets');
145+
if (!budgetOption) {
146+
// add
147+
insertPropertyInAstObjectInOrder(recorder, option, 'budgets', [ANY_COMPONENT_STYLE_BUDGET], 14);
148+
continue;
149+
}
150+
151+
if (budgetOption.kind !== 'array') {
152+
continue;
153+
}
154+
155+
// if 'anyComponentStyle' budget already exists don't add.
156+
const hasAnyComponentStyle = budgetOption.elements.some(node => {
157+
if (!node || node.kind !== 'object') {
158+
// skip non complex objects
159+
return false;
160+
}
161+
162+
const budget = findPropertyInAstObject(node, 'type');
163+
164+
return !!budget && budget.kind === 'string' && budget.value === 'anyComponentStyle';
165+
});
166+
167+
if (!hasAnyComponentStyle) {
168+
appendValueInAstArray(recorder, budgetOption, ANY_COMPONENT_STYLE_BUDGET, 16);
169+
}
170+
}
171+
}

packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts

+99-54
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,19 @@
88

99
import { EmptyTree } from '@angular-devkit/schematics';
1010
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
11+
import { WorkspaceTargets } from '../../utility/workspace-models';
12+
import { ANY_COMPONENT_STYLE_BUDGET } from './update-workspace-config';
1113

12-
function readWorkspaceConfig(tree: UnitTestTree) {
13-
return JSON.parse(tree.readContent('/angular.json'));
14+
// tslint:disable-next-line: no-any
15+
function getWorkspaceTargets(tree: UnitTestTree): any {
16+
return JSON.parse(tree.readContent(workspacePath))
17+
.projects['migration-test'].architect;
18+
}
19+
20+
function updateWorkspaceTargets(tree: UnitTestTree, workspaceTargets: WorkspaceTargets) {
21+
const config = JSON.parse(tree.readContent(workspacePath));
22+
config.projects['migration-test'].architect = workspaceTargets;
23+
tree.overwrite(workspacePath, JSON.stringify(config, undefined, 2));
1424
}
1525

1626
const scriptsWithLazy = [
@@ -69,60 +79,95 @@ describe('Migration to version 9', () => {
6979
.toPromise();
7080
});
7181

72-
it('should update scripts in build target', () => {
73-
let config = readWorkspaceConfig(tree);
74-
let build = config.projects['migration-test'].architect.build;
75-
build.options.scripts = scriptsWithLazy;
76-
build.configurations.production.scripts = scriptsWithLazy;
77-
78-
tree.overwrite(workspacePath, JSON.stringify(config));
79-
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
80-
config = readWorkspaceConfig(tree2);
81-
build = config.projects['migration-test'].architect.build;
82-
expect(build.options.scripts).toEqual(scriptsExpectWithLazy);
83-
expect(build.configurations.production.scripts).toEqual(scriptsExpectWithLazy);
84-
});
85-
86-
it('should update styles in build target', () => {
87-
let config = readWorkspaceConfig(tree);
88-
let build = config.projects['migration-test'].architect.build;
89-
build.options.styles = stylesWithLazy;
90-
build.configurations.production.styles = stylesWithLazy;
91-
92-
tree.overwrite(workspacePath, JSON.stringify(config));
93-
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
94-
config = readWorkspaceConfig(tree2);
95-
build = config.projects['migration-test'].architect.build;
96-
expect(build.options.styles).toEqual(stylesExpectWithLazy);
97-
expect(build.configurations.production.styles).toEqual(stylesExpectWithLazy);
98-
});
99-
100-
it('should update scripts in test target', () => {
101-
let config = readWorkspaceConfig(tree);
102-
let test = config.projects['migration-test'].architect.test;
103-
test.options.scripts = scriptsWithLazy;
104-
test.configurations = { production: { scripts: scriptsWithLazy } };
105-
106-
tree.overwrite(workspacePath, JSON.stringify(config));
107-
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
108-
config = readWorkspaceConfig(tree2);
109-
test = config.projects['migration-test'].architect.test;
110-
expect(test.options.scripts).toEqual(scriptsExpectWithLazy);
111-
expect(test.configurations.production.scripts).toEqual(scriptsExpectWithLazy);
82+
describe('scripts and style options', () => {
83+
it('should update scripts in build target', () => {
84+
let config = getWorkspaceTargets(tree);
85+
config.build.options.scripts = scriptsWithLazy;
86+
config.build.configurations.production.scripts = scriptsWithLazy;
87+
88+
updateWorkspaceTargets(tree, config);
89+
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
90+
config = getWorkspaceTargets(tree2).build;
91+
expect(config.options.scripts).toEqual(scriptsExpectWithLazy);
92+
expect(config.configurations.production.scripts).toEqual(scriptsExpectWithLazy);
93+
});
94+
95+
it('should update styles in build target', () => {
96+
let config = getWorkspaceTargets(tree);
97+
config.build.options.styles = stylesWithLazy;
98+
config.build.configurations.production.styles = stylesWithLazy;
99+
100+
updateWorkspaceTargets(tree, config);
101+
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
102+
config = getWorkspaceTargets(tree2).build;
103+
expect(config.options.styles).toEqual(stylesExpectWithLazy);
104+
expect(config.configurations.production.styles).toEqual(stylesExpectWithLazy);
105+
});
106+
107+
it('should update scripts in test target', () => {
108+
let config = getWorkspaceTargets(tree);
109+
config.test.options.scripts = scriptsWithLazy;
110+
config.test.configurations = { production: { scripts: scriptsWithLazy } };
111+
112+
updateWorkspaceTargets(tree, config);
113+
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
114+
config = getWorkspaceTargets(tree2).test;
115+
expect(config.options.scripts).toEqual(scriptsExpectWithLazy);
116+
expect(config.configurations.production.scripts).toEqual(scriptsExpectWithLazy);
117+
});
118+
119+
it('should update styles in test target', () => {
120+
let config = getWorkspaceTargets(tree);
121+
config.test.options.styles = stylesWithLazy;
122+
config.test.configurations = { production: { styles: stylesWithLazy } };
123+
124+
updateWorkspaceTargets(tree, config);
125+
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
126+
config = getWorkspaceTargets(tree2).test;
127+
expect(config.options.styles).toEqual(stylesExpectWithLazy);
128+
expect(config.configurations.production.styles).toEqual(stylesExpectWithLazy);
129+
});
112130
});
113131

114-
it('should update styles in test target', () => {
115-
let config = readWorkspaceConfig(tree);
116-
let test = config.projects['migration-test'].architect.test;
117-
test.options.styles = stylesWithLazy;
118-
test.configurations = { production: { styles: stylesWithLazy } };
119-
120-
tree.overwrite(workspacePath, JSON.stringify(config));
121-
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
122-
config = readWorkspaceConfig(tree2);
123-
test = config.projects['migration-test'].architect.test;
124-
expect(test.options.styles).toEqual(stylesExpectWithLazy);
125-
expect(test.configurations.production.styles).toEqual(stylesExpectWithLazy);
132+
describe('anyComponentStyle bundle budget', () => {
133+
it('should not append budget when already exists', () => {
134+
const defaultBudget = [
135+
{ type: 'initial', maximumWarning: '2mb', maximumError: '5mb' },
136+
{ type: 'anyComponentStyle', maximumWarning: '10kb', maximumError: '50kb' },
137+
];
138+
139+
let config = getWorkspaceTargets(tree);
140+
config.build.configurations.production.budgets = defaultBudget;
141+
updateWorkspaceTargets(tree, config);
142+
143+
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
144+
config = getWorkspaceTargets(tree2).build;
145+
expect(config.configurations.production.budgets).toEqual(defaultBudget);
146+
});
147+
148+
it('should append budget in build target', () => {
149+
const defaultBudget = [{ type: 'initial', maximumWarning: '2mb', maximumError: '5mb' }];
150+
let config = getWorkspaceTargets(tree);
151+
config.build.configurations.production.budgets = defaultBudget;
152+
updateWorkspaceTargets(tree, config);
153+
154+
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
155+
config = getWorkspaceTargets(tree2).build;
156+
expect(config.configurations.production.budgets).toEqual([
157+
...defaultBudget,
158+
ANY_COMPONENT_STYLE_BUDGET,
159+
]);
160+
});
161+
162+
it('should add budget in build target', () => {
163+
let config = getWorkspaceTargets(tree);
164+
config.build.configurations.production.budgets = undefined;
165+
updateWorkspaceTargets(tree, config);
166+
167+
const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch());
168+
config = getWorkspaceTargets(tree2).build;
169+
expect(config.configurations.production.budgets).toEqual([ANY_COMPONENT_STYLE_BUDGET]);
170+
});
126171
});
127172
});
128173
});

packages/schematics/angular/utility/config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export interface AppConfig {
130130
/**
131131
* The type of budget
132132
*/
133-
type?: ('bundle' | 'initial' | 'allScript' | 'all' | 'anyScript' | 'any');
133+
type?: ('bundle' | 'initial' | 'allScript' | 'all' | 'anyScript' | 'any' | 'anyComponentStyle');
134134
/**
135135
* The name of the bundle
136136
*/

packages/schematics/angular/utility/json-utils.ts

+24-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function appendPropertyInAstObject(
3131
recorder.insertRight(commaIndex, ',');
3232
index = end.offset;
3333
}
34-
const content = JSON.stringify(value, null, indent).replace(/\n/g, indentStr);
34+
const content = _stringifyContent(value, indentStr);
3535
recorder.insertRight(
3636
index,
3737
(node.properties.length === 0 && indent ? '\n' : '')
@@ -84,7 +84,7 @@ export function insertPropertyInAstObjectInOrder(
8484
const insertIndex = insertAfterProp === null
8585
? node.start.offset + 1
8686
: insertAfterProp.end.offset + 1;
87-
const content = JSON.stringify(value, null, indent).replace(/\n/g, indentStr);
87+
const content = _stringifyContent(value, indentStr);
8888
recorder.insertRight(
8989
insertIndex,
9090
indentStr
@@ -168,7 +168,7 @@ export function appendValueInAstArray(
168168
index,
169169
(node.elements.length === 0 && indent ? '\n' : '')
170170
+ ' '.repeat(indent)
171-
+ JSON.stringify(value, null, indent).replace(/\n/g, indentStr)
171+
+ _stringifyContent(value, indentStr)
172172
+ indentStr.slice(0, -indent),
173173
);
174174
}
@@ -191,3 +191,24 @@ export function findPropertyInAstObject(
191191
function _buildIndent(count: number): string {
192192
return count ? '\n' + ' '.repeat(count) : '';
193193
}
194+
195+
function _stringifyContent(value: JsonValue, indentStr: string): string {
196+
// TODO: Add snapshot tests
197+
198+
// The 'space' value is 2, because we want to add 2 additional
199+
// indents from the 'key' node.
200+
201+
// If we use the indent provided we will have double indents:
202+
// "budgets": [
203+
// {
204+
// "type": "initial",
205+
// "maximumWarning": "2mb",
206+
// "maximumError": "5mb"
207+
// },
208+
// {
209+
// "type": "anyComponentStyle",
210+
// 'maximumWarning": "5kb"
211+
// }
212+
// ]
213+
return JSON.stringify(value, null, 2).replace(/\n/g, indentStr);
214+
}

packages/schematics/angular/utility/workspace-models.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ export interface BrowserBuilderBaseOptions {
3838
index?: string;
3939
polyfills: string;
4040
assets?: (object|string)[];
41-
styles?: string[];
42-
scripts?: string[];
41+
styles?: (object|string)[];
42+
scripts?: (object|string)[];
4343
sourceMap?: boolean;
4444
}
4545

0 commit comments

Comments
 (0)