From 6294fb9106752a1b093cb623ea8fbc1c3c7a8913 Mon Sep 17 00:00:00 2001 From: Zed Spencer-Milnes Date: Thu, 10 Jan 2019 20:26:20 +0000 Subject: [PATCH] feat(@angular-devkit/build-angular): Add progress reporters for webpack builds Add progress reporters for webpack builds. Previously only the default webpack progress handler was used. The default handler did not display well in all CI environments as it attempted to use the same console line for all progress updates. Add simple progress report, logging on new lines each time the build stage or build percentage changes. Add verbose progress report, logging on new lines on every progress update with additional data provided by webpack. Change default behaviour on non TTY consoles to use the simple progress report over the current behaviour of no progress reporting, this gives visibility over the status of build. --- .../angular-cli-files/models/build-options.ts | 1 + .../models/webpack-configs/common.ts | 10 +++- .../progress-reporter-selector.ts | 50 +++++++++++++++++ .../progress-reporters/progress-reporter.ts | 28 ++++++++++ .../simple-progress-reporter.ts | 56 +++++++++++++++++++ .../verbose-progress-reporter.ts | 53 ++++++++++++++++++ .../webpack-default-progress-reporter.ts | 31 ++++++++++ .../build_angular/src/browser/index.ts | 3 +- .../build_angular/src/browser/schema.d.ts | 5 ++ .../build_angular/src/browser/schema.json | 4 ++ .../build_angular/src/karma/index.ts | 3 +- .../build_angular/src/server/index.ts | 3 +- .../src/utils/default-progress.ts | 11 +++- 13 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/progress-reporter-selector.ts create mode 100644 packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/progress-reporter.ts create mode 100644 packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/simple-progress-reporter.ts create mode 100644 packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/verbose-progress-reporter.ts create mode 100644 packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/webpack-default-progress-reporter.ts 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 552b515b53cd..2bf4ea1b06d6 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 @@ -35,6 +35,7 @@ export interface BuildOptions { deployUrl?: string; verbose?: boolean; progress?: boolean; + progressType?: string; i18nFile?: string; i18nFormat?: string; i18nLocale?: 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 bab2266a41f7..e1daa5d5af99 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 @@ -14,6 +14,9 @@ import { CleanCssWebpackPlugin } from '../../plugins/cleancss-webpack-plugin'; import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin'; import { findUp } from '../../utilities/find-up'; import { isDirectory } from '../../utilities/is-directory'; +import { + selectProgressReporter, +} from '../../utilities/progress-reporters/progress-reporter-selector'; import { requireProjectModule } from '../../utilities/require-project-module'; import { BuildOptions, WebpackConfigOptions } from '../build-options'; import { getOutputHashFormat, normalizeExtraEntryPoints } from './utils'; @@ -149,8 +152,11 @@ export function getCommonConfig(wco: WebpackConfigOptions) { extraPlugins.push(copyWebpackPluginInstance); } - if (buildOptions.progress) { - extraPlugins.push(new ProgressPlugin({ profile: buildOptions.verbose })); + if (buildOptions.progress ) { + extraPlugins.push( + new ProgressPlugin( + selectProgressReporter(buildOptions.progressType) + .buildOptions(buildOptions))); } if (buildOptions.showCircularDependencies) { diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/progress-reporter-selector.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/progress-reporter-selector.ts new file mode 100644 index 000000000000..f33f857a6453 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/progress-reporter-selector.ts @@ -0,0 +1,50 @@ +/** + * @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 { ProgressReporter } from './progress-reporter'; +import { SimpleProgressReporter } from './simple-progress-reporter'; +import { VerboseProgressReporter } from './verbose-progress-reporter'; +import { WebpackDefaultProgressReporter } from './webpack-default-progress-reporter'; + +const defaultProgressReporter = 'webpack-default'; + +const factories: { + // Mapping of string key to factory of a ProgressReporter + [key: string]: (() => ProgressReporter), +} = { + 'webpack-default': () => new WebpackDefaultProgressReporter(), + 'verbose-colors': () => new VerboseProgressReporter(true), + 'verbose-plain': () => new VerboseProgressReporter(false), + 'simple-plain': () => new SimpleProgressReporter(false), + 'simple-colors': () => new SimpleProgressReporter(true), + +}; + +const aliases: { + // Mapping of default to factory + [key: string]: (string), +} = { + 'ci-friendly': 'simple', + 'verbose': 'verbose-colors', + 'simple': 'simple-colors', + 'non-tty': 'simple', + 'tty': 'webpack-default', +}; + +export function selectProgressReporter(userInput?: string): ProgressReporter { + if (userInput) { + if (factories[userInput]) { + return factories[userInput](); + } + if (aliases[userInput]) { + return selectProgressReporter(aliases[userInput]); + } + throw new Error('Could not find progress reporter: ' + userInput); + } else { + return selectProgressReporter(defaultProgressReporter); + } +} diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/progress-reporter.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/progress-reporter.ts new file mode 100644 index 000000000000..54158c0dff53 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/progress-reporter.ts @@ -0,0 +1,28 @@ +/** + * @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 { ProgressPluginOptions } from 'webpack/declarations/plugins/ProgressPlugin'; +import { BuildOptions } from '../../models/build-options'; + +export abstract class ProgressReporter { + + protected abstract handleProgress(percentage: number, msg: string, ...args: string[]): void; + + protected baseOptions(): ProgressPluginOptions { + return {}; + } + + public buildOptions(buildOptions: BuildOptions): ProgressPluginOptions { + const progressPluginOptions = this.baseOptions(); + progressPluginOptions.handler = ((percentage, msg, ...args) => + this.handleProgress(percentage, msg, ...args)); + progressPluginOptions.profile = buildOptions.verbose; + + return progressPluginOptions; + } + +} diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/simple-progress-reporter.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/simple-progress-reporter.ts new file mode 100644 index 000000000000..ec856f091bea --- /dev/null +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/simple-progress-reporter.ts @@ -0,0 +1,56 @@ +/** + * @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 { terminal } from '@angular-devkit/core'; +import { ProgressReporter } from './progress-reporter'; + + +const { bold, green, white, yellow } = terminal; + +export class SimpleProgressReporter extends ProgressReporter { + + constructor(private colours: boolean) { + super(); + + } + + lastPercentage = -1; + lastMessage = ''; + + // ***% (msg) [detail] [detail] + + protected handleProgress(percentage: number, msg: string, ...args: string[]): void { + percentage = Math.floor(percentage * 100); + + if (this.lastMessage == msg && this.lastPercentage == percentage) { + return; + } + + this.lastMessage = msg; + this.lastPercentage = percentage; + + let buildingString = + (this.colours ? bold(percentage + '') : percentage) + + '%'; + if (percentage < 100) { // Shift string right + buildingString = ` ${buildingString}`; + } + + if (percentage < 10) { // Shift string right + buildingString = ` ${buildingString}`; + } + + if (msg) { + buildingString += ' (' + + (this.colours ? bold(yellow(msg)) : msg) + + ')'; + } + + console.log(buildingString); + } + +} diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/verbose-progress-reporter.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/verbose-progress-reporter.ts new file mode 100644 index 000000000000..dd383bd1dcc4 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/verbose-progress-reporter.ts @@ -0,0 +1,53 @@ +/** + * @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 { terminal } from '@angular-devkit/core'; +import { ProgressReporter } from './progress-reporter'; + + +const { bold, green, white, yellow } = terminal; + +export class VerboseProgressReporter extends ProgressReporter { + + constructor(private colours: boolean) { + super(); + } + + // ***% (msg) [detail] [detail] + + protected handleProgress(percentage: number, msg: string, ...args: string[]): void { + percentage = Math.floor(percentage * 100); + let buildingString = + (this.colours ? bold(percentage + '') : percentage) + + '%'; + if (percentage < 100) { // Shift string right + buildingString = ` ${buildingString}`; + } + + if (percentage < 10) { // Shift string right + buildingString = ` ${buildingString}`; + } + + if (msg) { + buildingString += ' (' + + (this.colours ? bold(yellow(msg)) : msg) + + ')'; + } + + if (args) { + for (const arg of args) { + if (arg) { + buildingString += ' [' + + (this.colours ? green(arg) : arg) + + ']'; + } + } + } + console.log(buildingString); + } + +} diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/webpack-default-progress-reporter.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/webpack-default-progress-reporter.ts new file mode 100644 index 000000000000..b9d0d5339f0e --- /dev/null +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/progress-reporters/webpack-default-progress-reporter.ts @@ -0,0 +1,31 @@ +/** + * @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 { ProgressPluginOptions } from 'webpack/declarations/plugins/ProgressPlugin'; +import { BuildOptions } from '../../models/build-options'; +import { ProgressReporter } from './progress-reporter'; + +export class WebpackDefaultProgressReporter extends ProgressReporter { + + constructor() { + super(); + } + + protected handleProgress(percentage: number, msg: string, ...args: string[]): void { + throw new Error('This method is not to be called'); + } + + public buildOptions(buildOptions: BuildOptions): ProgressPluginOptions { + const progressPluginOptions = super.buildOptions(buildOptions); + // Reset handler to undefined, this will cause ProgressPlugin to use it's default handler + progressPluginOptions.handler = undefined; + + return progressPluginOptions; + } + +} diff --git a/packages/angular_devkit/build_angular/src/browser/index.ts b/packages/angular_devkit/build_angular/src/browser/index.ts index b13b08eb1823..57fbdf2eb07d 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -34,7 +34,7 @@ import { statsToString, statsWarningsToString, } from '../angular-cli-files/utilities/stats'; -import { defaultProgress, normalizeBuilderSchema } from '../utils'; +import { defaultProgress, defaultProgressType, normalizeBuilderSchema } from '../utils'; import { BrowserBuilderSchema, NormalizedBrowserBuilderSchema } from './schema'; const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const webpackMerge = require('webpack-merge'); @@ -137,6 +137,7 @@ export class BrowserBuilder implements Builder { }; wco.buildOptions.progress = defaultProgress(wco.buildOptions.progress); + wco.buildOptions.progressType = defaultProgressType(wco.buildOptions.progressType); const webpackConfigs: {}[] = [ getCommonConfig(wco), 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 243c8f84fc96..81a8fc1be89a 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.d.ts +++ b/packages/angular_devkit/build_angular/src/browser/schema.d.ts @@ -113,6 +113,11 @@ export interface BrowserBuilderSchema { */ progress?: boolean; + /** + * The method in which progress is logged to the console while building + */ + progressType?: string; + /** * Localization file to use for i18n. */ diff --git a/packages/angular_devkit/build_angular/src/browser/schema.json b/packages/angular_devkit/build_angular/src/browser/schema.json index 817379b9fb42..b438994bb05f 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/browser/schema.json @@ -174,6 +174,10 @@ "type": "boolean", "description": "Log progress to the console while building." }, + "progressType": { + "type": "string", + "description": "The method in which progress is logged to the console while building." + }, "i18nFile": { "type": "string", "description": "Localization file to use for i18n." diff --git a/packages/angular_devkit/build_angular/src/karma/index.ts b/packages/angular_devkit/build_angular/src/karma/index.ts index fcb2a5881101..c3ee5220bd38 100644 --- a/packages/angular_devkit/build_angular/src/karma/index.ts +++ b/packages/angular_devkit/build_angular/src/karma/index.ts @@ -26,7 +26,7 @@ import { } from '../angular-cli-files/models/webpack-configs'; import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig'; import { requireProjectModule } from '../angular-cli-files/utilities/require-project-module'; -import { defaultProgress, normalizeBuilderSchema } from '../utils'; +import { defaultProgress, defaultProgressType, normalizeBuilderSchema } from '../utils'; import { KarmaBuilderSchema, NormalizedKarmaBuilderSchema } from './schema'; const webpackMerge = require('webpack-merge'); @@ -150,6 +150,7 @@ export class KarmaBuilder implements Builder { }; wco.buildOptions.progress = defaultProgress(wco.buildOptions.progress); + wco.buildOptions.progressType = defaultProgressType(wco.buildOptions.progressType); const webpackConfigs: {}[] = [ getCommonConfig(wco), diff --git a/packages/angular_devkit/build_angular/src/server/index.ts b/packages/angular_devkit/build_angular/src/server/index.ts index 0443c7020c51..f1bb41d92fc2 100644 --- a/packages/angular_devkit/build_angular/src/server/index.ts +++ b/packages/angular_devkit/build_angular/src/server/index.ts @@ -30,7 +30,7 @@ import { import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig'; import { requireProjectModule } from '../angular-cli-files/utilities/require-project-module'; import { getBrowserLoggingCb } from '../browser'; -import { defaultProgress, normalizeBuilderSchema } from '../utils'; +import { defaultProgress, defaultProgressType, normalizeBuilderSchema } from '../utils'; import { BuildWebpackServerSchema, NormalizedServerBuilderServerSchema } from './schema'; const webpackMerge = require('webpack-merge'); @@ -103,6 +103,7 @@ export class ServerBuilder implements Builder { }; wco.buildOptions.progress = defaultProgress(wco.buildOptions.progress); + wco.buildOptions.progressType = defaultProgressType(wco.buildOptions.progressType); const webpackConfigs: {}[] = [ getCommonConfig(wco), diff --git a/packages/angular_devkit/build_angular/src/utils/default-progress.ts b/packages/angular_devkit/build_angular/src/utils/default-progress.ts index 258412b460f1..08c737edae9e 100644 --- a/packages/angular_devkit/build_angular/src/utils/default-progress.ts +++ b/packages/angular_devkit/build_angular/src/utils/default-progress.ts @@ -8,8 +8,17 @@ export function defaultProgress(progress: boolean | undefined): boolean { if (progress === undefined) { - return process.stdout.isTTY === true; + return true; } return progress; } + + +export function defaultProgressType(progressType: string | undefined): string { + if (progressType === undefined) { + return process.stdout.isTTY ? 'tty' : 'non-tty'; + } + + return progressType; +}