From 39f1be02beb7391bfbb34cd4c342d97c94d8fb1c Mon Sep 17 00:00:00 2001 From: Alan Date: Fri, 19 Jul 2019 11:26:32 +0200 Subject: [PATCH 1/5] fix(@ngtools/webpack): retain child compilation warnings and errors At the moment child compilation warnings and errors are being lost as they are not passed to the root compilation. This means that any errors that are being set by clean-css for component styles are being lost. --- .../test/browser/aot_spec_large.ts | 26 ++++++++++++++++++- .../ngtools/webpack/src/resource_loader.ts | 17 +++++++++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/angular_devkit/build_angular/test/browser/aot_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/aot_spec_large.ts index 785713cac442..4d0a141a5c77 100644 --- a/packages/angular_devkit/build_angular/test/browser/aot_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/browser/aot_spec_large.ts @@ -7,7 +7,7 @@ */ import { Architect } from '@angular-devkit/architect'; -import { join, normalize, virtualFs } from '@angular-devkit/core'; +import { join, logging, normalize, virtualFs } from '@angular-devkit/core'; import { BrowserBuilderOutput } from '../../src/browser'; import { createArchitect, host, ivyEnabled } from '../utils'; @@ -39,4 +39,28 @@ describe('Browser Builder AOT', () => { await run.stop(); }); + + it('shows warnings for component styles', async () => { + const overrides = { + aot: true, + optimization: true, + }; + + host.writeMultipleFiles({ + 'src/app/app.component.css': ` + .foo { color: white; padding: 1px; }; + .buz { color: white; padding: 2px; }; + `, + }); + + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe(e => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, overrides, { logger }); + const output = await run.result; + expect(output.success).toBe(true); + expect(logs.join()).toContain('WARNING in Invalid selector'); + await run.stop(); + }); }); diff --git a/packages/ngtools/webpack/src/resource_loader.ts b/packages/ngtools/webpack/src/resource_loader.ts index 75d9a68db70d..8dc72d93f202 100644 --- a/packages/ngtools/webpack/src/resource_loader.ts +++ b/packages/ngtools/webpack/src/resource_loader.ts @@ -100,10 +100,19 @@ export class WebpackResourceLoader { return new Promise((resolve, reject) => { childCompiler.compile((err: Error, childCompilation: any) => { // Resolve / reject the promise - if (childCompilation && childCompilation.errors && childCompilation.errors.length) { - const errorDetails = childCompilation.errors.map(function (error: any) { - return error.message + (error.error ? ':\n' + error.error : ''); - }).join('\n'); + const { warnings, errors } = childCompilation; + + if (warnings && warnings.length) { + this._parentCompilation.warnings.push(...warnings); + } + + if (errors && errors.length) { + this._parentCompilation.errors.push(...errors); + + const errorDetails = errors + .map((error: any) => error.message + (error.error ? ':\n' + error.error : '')) + .join('\n'); + reject(new Error('Child compilation failed:\n' + errorDetails)); } else if (err) { reject(err); From d7cbf4471dc263d3ecfc24445f2c8001c496e45c Mon Sep 17 00:00:00 2001 From: Alan Date: Fri, 19 Jul 2019 11:22:45 +0200 Subject: [PATCH 2/5] feat(@angular-devkit/build-angular): add bundle budget for component styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s very easy to inadvertently import toplevel css in component styles. Since component css is standalone and self-contained, it will never be shared between components and remains as a single large bundle for each component. This in turn adds a large amount of code that must be processed and increases bundle size. Related to: TOOL-949 --- .../plugins/bundle-budget.ts | 68 ++++++++++++------- .../utilities/bundle-calculator.ts | 16 +++++ .../build_angular/src/browser/schema.json | 1 + .../test/browser/bundle-budgets_spec_large.ts | 62 +++++++++++++++++ 4 files changed, 121 insertions(+), 26 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts index 4ee676886846..911e4c55d852 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import { Compiler, compilation } from 'webpack'; -import { Budget } from '../../browser/schema'; +import { Budget, Type } from '../../browser/schema'; import { Size, calculateBytes, calculateSizes } from '../utilities/bundle-calculator'; import { formatSize } from '../utilities/stats'; @@ -30,33 +30,28 @@ export class BundleBudgetPlugin { apply(compiler: Compiler): void { const { budgets } = this.options; - compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation: compilation.Compilation) => { - if (!budgets || budgets.length === 0) { - return; - } - budgets.map(budget => { - const thresholds = this.calculate(budget); - - return { - budget, - thresholds, - sizes: calculateSizes(budget, compilation), - }; - }) - .forEach(budgetCheck => { - budgetCheck.sizes.forEach(size => { - this.checkMaximum(budgetCheck.thresholds.maximumWarning, size, compilation.warnings); - this.checkMaximum(budgetCheck.thresholds.maximumError, size, compilation.errors); - this.checkMinimum(budgetCheck.thresholds.minimumWarning, size, compilation.warnings); - this.checkMinimum(budgetCheck.thresholds.minimumError, size, compilation.errors); - this.checkMinimum(budgetCheck.thresholds.warningLow, size, compilation.warnings); - this.checkMaximum(budgetCheck.thresholds.warningHigh, size, compilation.warnings); - this.checkMinimum(budgetCheck.thresholds.errorLow, size, compilation.errors); - this.checkMaximum(budgetCheck.thresholds.errorHigh, size, compilation.errors); - }); + if (!budgets || budgets.length === 0) { + return; + } - }); + compiler.hooks.compilation.tap('BundleBudgetPlugin', (compilation: compilation.Compilation) => { + compilation.hooks.afterOptimizeChunkAssets.tap('BundleBudgetPlugin', () => { + // In AOT compilations component styles get processed in child compilations. + // tslint:disable-next-line: no-any + const parentCompilation = (compilation.compiler as any).parentCompilation; + if (!parentCompilation) { + return; + } + + const filteredBudgets = budgets.filter(budget => budget.type === Type.AnyComponentStyle); + this.runChecks(filteredBudgets, compilation); + }); + }); + + compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation: compilation.Compilation) => { + const filteredBudgets = budgets.filter(budget => budget.type !== Type.AnyComponentStyle); + this.runChecks(filteredBudgets, compilation); }); } @@ -116,4 +111,25 @@ export class BundleBudgetPlugin { return thresholds; } + + private runChecks(budgets: Budget[], compilation: compilation.Compilation) { + budgets + .map(budget => ({ + budget, + thresholds: this.calculate(budget), + sizes: calculateSizes(budget, compilation), + })) + .forEach(budgetCheck => { + budgetCheck.sizes.forEach(size => { + this.checkMaximum(budgetCheck.thresholds.maximumWarning, size, compilation.warnings); + this.checkMaximum(budgetCheck.thresholds.maximumError, size, compilation.errors); + this.checkMinimum(budgetCheck.thresholds.minimumWarning, size, compilation.warnings); + this.checkMinimum(budgetCheck.thresholds.minimumError, size, compilation.errors); + this.checkMinimum(budgetCheck.thresholds.warningLow, size, compilation.warnings); + this.checkMaximum(budgetCheck.thresholds.warningHigh, size, compilation.warnings); + this.checkMinimum(budgetCheck.thresholds.errorLow, size, compilation.errors); + this.checkMaximum(budgetCheck.thresholds.errorHigh, size, compilation.errors); + }); + }); + } } diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts index 8af29c8e1208..8ef3cb93884f 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts @@ -25,9 +25,11 @@ export function calculateSizes(budget: Budget, compilation: Compilation): Size[] allScript: AllScriptCalculator, any: AnyCalculator, anyScript: AnyScriptCalculator, + anyComponentStyle: AnyComponentStyleCalculator, bundle: BundleCalculator, initial: InitialCalculator, }; + const ctor = calculatorMap[budget.type]; const calculator = new ctor(budget, compilation); @@ -101,6 +103,20 @@ class AllCalculator extends Calculator { } } +/** + * Any components styles + */ +class AnyComponentStyleCalculator extends Calculator { + calculate() { + return Object.keys(this.compilation.assets) + .filter(key => key.endsWith('.css')) + .map(key => ({ + size: this.compilation.assets[key].size(), + label: key, + })); + } +} + /** * Any script, individually. */ diff --git a/packages/angular_devkit/build_angular/src/browser/schema.json b/packages/angular_devkit/build_angular/src/browser/schema.json index 42c8ef8045cc..dbc930292512 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/browser/schema.json @@ -482,6 +482,7 @@ "allScript", "any", "anyScript", + "anyComponentStyle", "bundle", "initial" ] diff --git a/packages/angular_devkit/build_angular/test/browser/bundle-budgets_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/bundle-budgets_spec_large.ts index 37e150210ecb..2ce1a5691e41 100644 --- a/packages/angular_devkit/build_angular/test/browser/bundle-budgets_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/browser/bundle-budgets_spec_large.ts @@ -64,6 +64,68 @@ describe('Browser Builder bundle budgets', () => { await run.stop(); }); + it(`shows warnings for large component css when using 'anyComponentStyle' when AOT`, async () => { + const overrides = { + aot: true, + optimization: true, + budgets: [{ type: 'anyComponentStyle', maximumWarning: '1b' }], + }; + + const cssContent = ` + .foo { color: white; padding: 1px; } + .buz { color: white; padding: 2px; } + .bar { color: white; padding: 3px; } + `; + + host.writeMultipleFiles({ + 'src/app/app.component.css': cssContent, + 'src/assets/foo.css': cssContent, + 'src/styles.css': cssContent, + }); + + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe(e => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, overrides, { logger }); + const output = await run.result; + expect(output.success).toBe(true); + expect(logs.length).toBe(2); + expect(logs.join()).toMatch(/WARNING.+app\.component\.css/); + await run.stop(); + }); + + it(`shows error for large component css when using 'anyComponentStyle' when AOT`, async () => { + const overrides = { + aot: true, + optimization: true, + budgets: [{ type: 'anyComponentStyle', maximumError: '1b' }], + }; + + const cssContent = ` + .foo { color: white; padding: 1px; } + .buz { color: white; padding: 2px; } + .bar { color: white; padding: 3px; } + `; + + host.writeMultipleFiles({ + 'src/app/app.component.css': cssContent, + 'src/assets/foo.css': cssContent, + 'src/styles.css': cssContent, + }); + + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe(e => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, overrides, { logger }); + const output = await run.result; + expect(output.success).toBe(false); + expect(logs.length).toBe(2); + expect(logs.join()).toMatch(/ERROR.+app\.component\.css/); + await run.stop(); + }); + describe(`should ignore '.map' files`, () => { it(`when 'bundle' budget`, async () => { const overrides = { From de710651d20de0982937ccca82f63b915a1e9138 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Sat, 20 Jul 2019 11:48:21 +0200 Subject: [PATCH 3/5] feat(@angular/cli): add `anyComponentStyle` budget type --- packages/angular/cli/lib/config/schema.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/angular/cli/lib/config/schema.json b/packages/angular/cli/lib/config/schema.json index 8a3117b98f1d..1b7b03daa269 100644 --- a/packages/angular/cli/lib/config/schema.json +++ b/packages/angular/cli/lib/config/schema.json @@ -1024,6 +1024,7 @@ "allScript", "any", "anyScript", + "anyComponentStyle", "bundle", "initial" ] From fdc54c251be71d08c107f9fa47e56f789d120fd8 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Tue, 23 Jul 2019 19:58:40 +0200 Subject: [PATCH 4/5] feat(@schematics/angular): add migration to add `anyComponentStyle` bundle budget --- .../update-9/update-workspace-config.ts | 62 ++++++- .../update-9/update-workspace-config_spec.ts | 153 +++++++++++------- packages/schematics/angular/utility/config.ts | 2 +- .../schematics/angular/utility/json-utils.ts | 27 +++- .../angular/utility/workspace-models.ts | 4 +- 5 files changed, 181 insertions(+), 67 deletions(-) diff --git a/packages/schematics/angular/migrations/update-9/update-workspace-config.ts b/packages/schematics/angular/migrations/update-9/update-workspace-config.ts index 3dc9f637ce53..ca8310724223 100644 --- a/packages/schematics/angular/migrations/update-9/update-workspace-config.ts +++ b/packages/schematics/angular/migrations/update-9/update-workspace-config.ts @@ -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); + } + } +} diff --git a/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts b/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts index 64865e83c83b..dd83dbdeb1f2 100644 --- a/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts +++ b/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts @@ -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; - - 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); - }); - - 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; - - 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); - }); - - 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 } }; - - 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); + 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; + + 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 test target', () => { - let config = readWorkspaceConfig(tree); - let test = config.projects['migration-test'].architect.test; - test.options.styles = stylesWithLazy; - test.configurations = { production: { styles: stylesWithLazy } }; - - 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); + 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' }, + ]; + + let config = getWorkspaceTargets(tree); + config.build.configurations.production.budgets = defaultBudget; + updateWorkspaceTargets(tree, config); + + const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch()); + config = getWorkspaceTargets(tree2).build; + expect(config.configurations.production.budgets).toEqual(defaultBudget); + }); + + 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); + + const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch()); + config = getWorkspaceTargets(tree2).build; + expect(config.configurations.production.budgets).toEqual([ + ...defaultBudget, + ANY_COMPONENT_STYLE_BUDGET, + ]); + }); + + 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]); + }); }); }); }); diff --git a/packages/schematics/angular/utility/config.ts b/packages/schematics/angular/utility/config.ts index 2d3b0c8c6cd0..5b72c4d4f5ed 100644 --- a/packages/schematics/angular/utility/config.ts +++ b/packages/schematics/angular/utility/config.ts @@ -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 */ diff --git a/packages/schematics/angular/utility/json-utils.ts b/packages/schematics/angular/utility/json-utils.ts index f34c303f1717..72cb4937514f 100644 --- a/packages/schematics/angular/utility/json-utils.ts +++ b/packages/schematics/angular/utility/json-utils.ts @@ -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); +} diff --git a/packages/schematics/angular/utility/workspace-models.ts b/packages/schematics/angular/utility/workspace-models.ts index 9c8eebdcf78f..4c83b5f18f18 100644 --- a/packages/schematics/angular/utility/workspace-models.ts +++ b/packages/schematics/angular/utility/workspace-models.ts @@ -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; } From 95af6ab27e6b1b97321765a2e16c8b2674d03b97 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Tue, 23 Jul 2019 19:58:43 +0200 Subject: [PATCH 5/5] feat(@schematics/angular): add `anyComponentStyle` to the applications bundle budget --- packages/schematics/angular/application/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts index f7ff777a4b77..8a80af4bead8 100644 --- a/packages/schematics/angular/application/index.ts +++ b/packages/schematics/angular/application/index.ts @@ -223,6 +223,11 @@ function addAppToWorkspaceFile(options: ApplicationOptions, appDir: string): Rul type: 'initial', maximumWarning: '2mb', maximumError: '5mb', + }, + { + type: 'anyComponentStyle', + maximumWarning: '6kb', + maximumError: '10kb', }], }, },