diff --git a/package-lock.json b/package-lock.json index 41b3da9ec53d..e3620ebf6919 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10191,6 +10191,14 @@ "wbuf": "^1.7.2" } }, + "speed-measure-webpack-plugin": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.2.2.tgz", + "integrity": "sha512-OPssnUTAdOwl/ijqToLrYUi8xHxdC9SOVV9e2AxkOC02Hua7+Jkfh3tdFCZxiMbtlghHK5Eyz87kG/XNpeM77w==", + "requires": { + "chalk": "^2.0.1" + } + }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", diff --git a/package.json b/package.json index f37babb9c5d0..0c91a15f461f 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "source-map": "^0.5.6", "source-map-loader": "^0.2.3", "source-map-support": "^0.5.0", + "speed-measure-webpack-plugin": "^1.2.2", "stats-webpack-plugin": "^0.6.2", "style-loader": "^0.21.0", "stylus": "^0.54.5", diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json index 3375164b0ca7..73084f322e3d 100644 --- a/packages/angular_devkit/build_angular/package.json +++ b/packages/angular_devkit/build_angular/package.json @@ -42,6 +42,7 @@ "sass-loader": "~6.0.7", "source-map-support": "^0.5.0", "source-map-loader": "^0.2.3", + "speed-measure-webpack-plugin": "^1.2.2", "stats-webpack-plugin": "^0.6.2", "style-loader": "^0.21.0", "stylus": "^0.54.5", diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts index c0f719e20bb3..ac24619c7b41 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts @@ -47,6 +47,7 @@ export interface BuildOptions { skipAppShell?: boolean; statsJson: boolean; forkTypeChecker: boolean; + profile?: boolean; main: string; index: string; diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts index 6e371e825637..59e7acd2afd5 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts @@ -9,7 +9,7 @@ // TODO: cleanup this file, it's copied as is from Angular CLI. import * as path from 'path'; -import { HashedModuleIdsPlugin } from 'webpack'; +import { HashedModuleIdsPlugin, debug } from 'webpack'; import * as CopyWebpackPlugin from 'copy-webpack-plugin'; import { getOutputHashFormat } from './utils'; import { isDirectory } from '../../utilities/is-directory'; @@ -62,6 +62,12 @@ export function getCommonConfig(wco: WebpackConfigOptions) { entryPoints['polyfills'] = [path.resolve(root, buildOptions.polyfills)]; } + if (buildOptions.profile) { + extraPlugins.push(new debug.ProfilingPlugin({ + outputPath: path.resolve(root, 'chrome-profiler-events.json'), + })) + } + // determine hashing format const hashFormat = getOutputHashFormat(buildOptions.outputHashing as any); diff --git a/packages/angular_devkit/build_angular/src/browser/index.ts b/packages/angular_devkit/build_angular/src/browser/index.ts index 9dff620ff51a..6b544611282f 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -12,7 +12,7 @@ import { BuilderContext, } from '@angular-devkit/architect'; import { LoggingCallback, WebpackBuilder } from '@angular-devkit/build-webpack'; -import { Path, getSystemPath, normalize, resolve, virtualFs } from '@angular-devkit/core'; +import { Path, getSystemPath, join, normalize, resolve, virtualFs } from '@angular-devkit/core'; import * as fs from 'fs'; import { Observable, concat, of, throwError } from 'rxjs'; import { concatMap, last, tap } from 'rxjs/operators'; @@ -36,6 +36,7 @@ import { } from '../angular-cli-files/utilities/stats'; import { addFileReplacements, normalizeAssetPatterns } from '../utils'; import { AssetPatternObject, BrowserBuilderSchema, CurrentFileReplacement } from './schema'; +const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const webpackMerge = require('webpack-merge'); @@ -151,7 +152,18 @@ export class BrowserBuilder implements Builder { webpackConfigs.push(typescriptConfigPartial); } - return webpackMerge(webpackConfigs); + const webpackConfig = webpackMerge(webpackConfigs); + + if (options.profile) { + const smp = new SpeedMeasurePlugin({ + outputFormat: 'json', + outputTarget: getSystemPath(join(root, 'speed-measure-plugin.json')), + }); + + return smp.wrap(webpackConfig); + } + + return webpackConfig; } private _deleteOutputDir(root: Path, outputPath: Path, host: virtualFs.Host) { diff --git a/packages/angular_devkit/build_angular/src/browser/schema.d.ts b/packages/angular_devkit/build_angular/src/browser/schema.d.ts index 65ea0742abdc..2abb4681d797 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.d.ts +++ b/packages/angular_devkit/build_angular/src/browser/schema.d.ts @@ -222,6 +222,11 @@ export interface BrowserBuilderSchema { * Budget thresholds to ensure parts of your application stay within boundaries which you set. */ budgets: Budget[]; + + /** + * Output profile events for Chrome profiler. + */ + profile: boolean; } export type AssetPattern = string | AssetPatternObject; diff --git a/packages/angular_devkit/build_angular/src/browser/schema.json b/packages/angular_devkit/build_angular/src/browser/schema.json index 004a423e8d79..4f97ed3cf703 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/browser/schema.json @@ -237,6 +237,11 @@ "$ref": "#/definitions/budget" }, "default": [] + }, + "profile": { + "type": "boolean", + "description": "Output profile events for Chrome profiler.", + "default": false } }, "additionalProperties": false, diff --git a/packages/angular_devkit/build_angular/test/browser/profile_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/profile_spec_large.ts new file mode 100644 index 000000000000..e17349953549 --- /dev/null +++ b/packages/angular_devkit/build_angular/test/browser/profile_spec_large.ts @@ -0,0 +1,29 @@ +/** + * @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 { runTargetSpec } from '@angular-devkit/architect/testing'; +import { normalize } from '@angular-devkit/core'; +import { tap } from 'rxjs/operators'; +import { browserTargetSpec, host } from '../utils'; + + +describe('Browser Builder profile', () => { + beforeEach(done => host.initialize().toPromise().then(done, done.fail)); + afterEach(done => host.restore().toPromise().then(done, done.fail)); + + it('works', (done) => { + const overrides = { profile: true }; + runTargetSpec(host, browserTargetSpec, overrides).pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + tap(() => { + expect(host.scopedSync().exists(normalize('chrome-profiler-events.json'))).toBe(true); + expect(host.scopedSync().exists(normalize('speed-measure-plugin.json'))).toBe(true); + }), + ).toPromise().then(done, done.fail); + }); +});