From a220f4625fadbb308b23c8d37f7e67682772a2c2 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Fri, 23 Oct 2020 17:21:34 +0200 Subject: [PATCH] fix(@angular-devkit/build-angular): improve builder phase reporting (cherry picked from commit f3ba5f487964570f36119221d8a8dd6ea1824104) --- .../build_angular/src/app-shell/index.ts | 11 +- .../build_angular/src/browser/index.ts | 220 ++++++++++-------- .../src/browser/specs/bundle-budgets_spec.ts | 2 - .../build_angular/src/dev-server/index.ts | 3 +- .../build_angular/src/utils/i18n-inlining.ts | 10 +- .../build_angular/src/utils/spinner.ts | 45 ++++ .../src/webpack/configs/common.ts | 45 +++- .../build_angular/src/webpack/utils/stats.ts | 9 +- tests/legacy-cli/e2e/tests/basic/e2e.ts | 2 +- tests/legacy-cli/e2e/tests/basic/rebuild.ts | 2 +- tests/legacy-cli/e2e/tests/build/poll.ts | 2 +- .../e2e/tests/build/rebuild-css-change.ts | 2 +- .../tests/build/rebuild-deps-type-check.ts | 2 +- .../e2e/tests/build/rebuild-error.ts | 2 +- .../e2e/tests/build/rebuild-ngfactories.ts | 2 +- .../e2e/tests/build/rebuild-replacements.ts | 3 +- .../e2e/tests/build/rebuild-types.ts | 2 +- .../legacy-cli/e2e/tests/misc/live-reload.ts | 2 +- tests/legacy-cli/e2e/utils/project.ts | 2 +- 19 files changed, 233 insertions(+), 135 deletions(-) create mode 100644 packages/angular_devkit/build_angular/src/utils/spinner.ts diff --git a/packages/angular_devkit/build_angular/src/app-shell/index.ts b/packages/angular_devkit/build_angular/src/app-shell/index.ts index 6bf05351bf07..aa447da83541 100644 --- a/packages/angular_devkit/build_angular/src/app-shell/index.ts +++ b/packages/angular_devkit/build_angular/src/app-shell/index.ts @@ -19,6 +19,7 @@ import { BrowserBuilderOutput } from '../browser'; import { Schema as BrowserBuilderSchema } from '../browser/schema'; import { ServerBuilderOutput } from '../server'; import { augmentAppWithServiceWorker } from '../utils/service-worker'; +import { Spinner } from '../utils/spinner'; import { Schema as BuildWebpackAppShellSchema } from './schema'; async function _renderUniversal( @@ -152,6 +153,8 @@ async function _appShellBuilder( watch: false, }); + let spinner: Spinner | undefined; + try { const [browserResult, serverResult] = await Promise.all([ browserTargetRun.result as unknown as BrowserBuilderOutput, @@ -164,8 +167,14 @@ async function _appShellBuilder( return serverResult; } - return await _renderUniversal(options, context, browserResult, serverResult); + spinner = new Spinner().start('Generating application shell...'); + const result = await _renderUniversal(options, context, browserResult, serverResult); + spinner.succeed('Application shell generation complete.'); + + return result; } catch (err) { + spinner?.fail('Application shell generation failed.'); + return { success: false, error: err.message }; } finally { // Just be good citizens and stop those jobs. diff --git a/packages/angular_devkit/build_angular/src/browser/index.ts b/packages/angular_devkit/build_angular/src/browser/index.ts index 7c6711efd311..8e96279fff06 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -10,7 +10,6 @@ import { EmittedFiles, WebpackLoggingCallback, runWebpack } from '@angular-devki import { getSystemPath, json, normalize, resolve, tags, virtualFs } from '@angular-devkit/core'; import { NodeJsSyncHost } from '@angular-devkit/core/node'; import * as fs from 'fs'; -import * as ora from 'ora'; import * as path from 'path'; import { Observable, from } from 'rxjs'; import { concatMap, map, switchMap } from 'rxjs/operators'; @@ -48,6 +47,7 @@ import { } from '../utils/process-bundle'; import { readTsconfig } from '../utils/read-tsconfig'; import { augmentAppWithServiceWorker } from '../utils/service-worker'; +import { Spinner } from '../utils/spinner'; import { assertCompatibleAngularVersion } from '../utils/version'; import { generateI18nBrowserWebpackConfigFromContext, @@ -69,13 +69,13 @@ import { NgBuildAnalyticsPlugin } from '../webpack/plugins/analytics'; import { markAsyncChunksNonInitial } from '../webpack/utils/async-chunks'; import { BundleStats, - createWebpackLoggingCallback, generateBuildStats, generateBuildStatsTable, generateBundleStats, statsErrorsToString, statsHasErrors, statsHasWarnings, + statsToString, statsWarningsToString, } from '../webpack/utils/stats'; import { Schema as BrowserBuilderSchema } from './schema'; @@ -92,14 +92,6 @@ export type BrowserBuilderOutput = json.JsonObject & outputPath: string; }; -// todo: the below should be cleaned once dev-server support the new i18n -interface ConfigFromContextReturn { - config: webpack.Configuration; - projectRoot: string; - projectSourceRoot?: string; - i18n: I18nOptions; -} - export function getAnalyticsConfig( wco: WebpackConfigOptions, context: BuilderContext, @@ -277,7 +269,6 @@ export function buildWebpackBrowser( }), // tslint:disable-next-line: no-big-function switchMap(({ config, projectRoot, projectSourceRoot, i18n, buildBrowserFeatures, isDifferentialLoadingNeeded, target }) => { - const useBundleDownleveling = isDifferentialLoadingNeeded && !options.watch; const startTime = Date.now(); const normalizedOptimization = normalizeOptimization(options.optimization); const indexTransforms = getHtmlTransforms( @@ -289,13 +280,13 @@ export function buildWebpackBrowser( return runWebpack(config, context, { webpackFactory: require('webpack') as typeof webpack, logging: - transforms.logging || - (useBundleDownleveling - ? () => { } - : createWebpackLoggingCallback(!!options.verbose, context.logger)), + transforms.logging || (() => { }), }).pipe( // tslint:disable-next-line: no-big-function concatMap(async buildEvent => { + const spinner = new Spinner(); + spinner.enabled = !!options.progress; + const { webpackStats: webpackRawStats, success, emittedFiles = [] } = buildEvent; if (!webpackRawStats) { throw new Error('Webpack stats build result is required.'); @@ -309,7 +300,7 @@ export function buildWebpackBrowser( chunks: markAsyncChunksNonInitial(webpackRawStats, extraEntryPoints), }; - if (!success && useBundleDownleveling) { + if (!success) { // If using bundle downleveling then there is only one build // If it fails show any diagnostic messages and bail if (statsHasWarnings(webpackStats)) { @@ -320,7 +311,8 @@ export function buildWebpackBrowser( } return { success }; - } else if (success) { + } else { + const bundleInfoStats: BundleStats[] = []; outputPaths = ensureOutputPaths(baseOutputPath, i18n); let noModuleFiles: EmittedFiles[] | undefined; @@ -487,7 +479,7 @@ export function buildWebpackBrowser( // Execute the bundle processing actions try { - const dlSpinner = ora('Generating ES5 bundles for differential loading...').start(); + spinner.start('Generating ES5 bundles for differential loading...'); for await (const result of executor.processAll(processActions)) { processResults.push(result); } @@ -504,11 +496,10 @@ export function buildWebpackBrowser( ); } - dlSpinner.succeed('ES5 bundle generation complete.'); + spinner.succeed('ES5 bundle generation complete.'); if (i18n.shouldInline) { - const spinner = ora('Generating localized bundles...').start(); - + spinner.start('Generating localized bundles...'); const inlineActions: InlineOptions[] = []; const processedFiles = new Set(); for (const result of processResults) { @@ -586,13 +577,13 @@ export function buildWebpackBrowser( '', ); } catch (err) { - spinner.fail(colors.redBright('Localized bundle generation failed.')); + spinner.fail('Localized bundle generation failed.'); return { success: false, error: mapErrorToMessage(err) }; } if (hasErrors) { - spinner.fail(colors.redBright('Localized bundle generation failed.')); + spinner.fail('Localized bundle generation failed.'); } else { spinner.succeed('Localized bundle generation complete.'); } @@ -604,26 +595,6 @@ export function buildWebpackBrowser( } finally { executor.stop(); } - - type ArrayElement = A extends ReadonlyArray ? T : never; - function generateBundleInfoStats( - bundle: ProcessBundleFile, - chunk: ArrayElement | undefined, - ): BundleStats { - return generateBundleStats( - { - size: bundle.size, - files: bundle.map ? [bundle.filename, bundle.map.filename] : [bundle.filename], - names: chunk?.names, - entry: !!chunk?.names.includes('runtime'), - initial: !!chunk?.initial, - rendered: true, - }, - true, - ); - } - - const bundleInfoStats: BundleStats[] = []; for (const result of processResults) { const chunk = webpackStats.chunks?.find((chunk) => chunk.id.toString() === result.name); @@ -644,17 +615,6 @@ export function buildWebpackBrowser( bundleInfoStats.push(generateBundleStats({ ...chunk, size: asset?.size }, true)); } - context.logger.info( - '\n' + - generateBuildStatsTable(bundleInfoStats, colors.enabled) + - '\n\n' + - generateBuildStats( - webpackStats?.hash || '', - Date.now() - startTime, - true, - ), - ); - // Check for budget errors and display them to the user. const budgets = options.budgets || []; const budgetFailures = checkBudgets(budgets, webpackStats, processResults); @@ -670,15 +630,6 @@ export function buildWebpackBrowser( assertNever(severity); } } - - if (statsHasWarnings(webpackStats)) { - context.logger.warn(statsWarningsToString(webpackStats, { colors: true })); - } - if (statsHasErrors(webpackStats)) { - context.logger.error(statsErrorsToString(webpackStats, { colors: true })); - - return { success: false }; - } } else { files = emittedFiles.filter(x => x.name !== 'polyfills-es5'); noModuleFiles = emittedFiles.filter(x => x.name === 'polyfills-es5'); @@ -703,6 +654,7 @@ export function buildWebpackBrowser( // Copy assets if (!options.watch && options.assets?.length) { + spinner.start('Copying assets...'); try { await copyAssets( normalizeAssetPatterns( @@ -715,55 +667,93 @@ export function buildWebpackBrowser( Array.from(outputPaths.values()), context.workspaceRoot, ); + spinner.succeed('Copying assets complete.'); } catch (err) { + spinner.fail(colors.redBright('Copying of assets failed.')); + return { success: false, error: 'Unable to copy assets: ' + err.message }; } } - for (const [locale, outputPath] of outputPaths.entries()) { - let localeBaseHref; - if (i18n.locales[locale] && i18n.locales[locale].baseHref !== '') { - localeBaseHref = urlJoin( - options.baseHref || '', - i18n.locales[locale].baseHref ?? `/${locale}/`, - ); - } + if (success) { + if (options.index) { + spinner.start('Generating index html...'); + for (const [locale, outputPath] of outputPaths.entries()) { + try { + await writeIndexHtml({ + host, + outputPath: path.join(outputPath, getIndexOutputFile(options.index)), + indexPath: path.join(context.workspaceRoot, getIndexInputFile(options.index)), + files, + noModuleFiles, + moduleFiles, + baseHref: getLocaleBaseHref(i18n, locale) || options.baseHref, + deployUrl: options.deployUrl, + sri: options.subresourceIntegrity, + scripts: options.scripts, + styles: options.styles, + postTransforms: indexTransforms, + crossOrigin: options.crossOrigin, + // i18nLocale is used when Ivy is disabled + lang: locale || options.i18nLocale, + }); + } catch (error) { + spinner.fail('Index html generation failed.'); + + return { success: false, error: mapErrorToMessage(error) }; + } - try { - if (options.index) { - await writeIndexHtml({ - host, - outputPath: path.join(outputPath, getIndexOutputFile(options.index)), - indexPath: path.join(context.workspaceRoot, getIndexInputFile(options.index)), - files, - noModuleFiles, - moduleFiles, - baseHref: localeBaseHref || options.baseHref, - deployUrl: options.deployUrl, - sri: options.subresourceIntegrity, - scripts: options.scripts, - styles: options.styles, - postTransforms: indexTransforms, - crossOrigin: options.crossOrigin, - // i18nLocale is used when Ivy is disabled - lang: locale || options.i18nLocale, - }); + spinner.succeed('Index html generation complete.'); } + } - if (options.serviceWorker) { - await augmentAppWithServiceWorker( - host, - root, - normalize(projectRoot), - normalize(outputPath), - localeBaseHref || options.baseHref || '/', - options.ngswConfigPath, - ); + if (options.serviceWorker) { + spinner.start('Generating service worker...'); + for (const [locale, outputPath] of outputPaths.entries()) { + try { + await augmentAppWithServiceWorker( + host, + root, + normalize(projectRoot), + normalize(outputPath), + getLocaleBaseHref(i18n, locale) || options.baseHref || '/', + options.ngswConfigPath, + ); + } catch (error) { + spinner.fail('Service worker generation failed.'); + + return { success: false, error: mapErrorToMessage(error) }; + } + + spinner.succeed('Service worker generation complete.'); } - } catch (err) { - return { success: false, error: mapErrorToMessage(err) }; } } + + if (bundleInfoStats.length) { + context.logger.info( + '\n' + + generateBuildStatsTable(bundleInfoStats, colors.enabled) + + '\n\n' + + generateBuildStats( + webpackStats?.hash || '', + Date.now() - startTime, + true, + ), + ); + } else { + context.logger.info(statsToString(webpackStats, config.stats)); + } + + if (statsHasWarnings(webpackStats)) { + context.logger.warn(statsWarningsToString(webpackStats, { colors: true })); + } + + if (statsHasErrors(webpackStats)) { + context.logger.error(statsErrorsToString(webpackStats, { colors: true })); + + return { success: false }; + } } return { success }; @@ -780,6 +770,17 @@ export function buildWebpackBrowser( ); }), ); + + function getLocaleBaseHref(i18n: I18nOptions, locale: string): string | undefined { + if (i18n.locales[locale] && i18n.locales[locale]?.baseHref !== '') { + return urlJoin( + options.baseHref || '', + i18n.locales[locale].baseHref ?? `/${locale}/`, + ); + } + + return undefined; + } } function mapErrorToMessage(error: unknown): string | undefined { @@ -799,4 +800,21 @@ function assertNever(input: never): never { JSON.stringify(input, null /* replacer */, 4 /* tabSize */)}`); } +type ArrayElement = A extends ReadonlyArray ? T : never; +function generateBundleInfoStats( + bundle: ProcessBundleFile, + chunk: ArrayElement | undefined, +): BundleStats { + return generateBundleStats( + { + size: bundle.size, + files: bundle.map ? [bundle.filename, bundle.map.filename] : [bundle.filename], + names: chunk?.names, + entry: !!chunk?.names.includes('runtime'), + initial: !!chunk?.initial, + rendered: true, + }, + true, + ); +} export default createBuilder(buildWebpackBrowser); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/bundle-budgets_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/bundle-budgets_spec.ts index 3eaed7bb7d63..e8fbf1434282 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/bundle-budgets_spec.ts +++ b/packages/angular_devkit/build_angular/src/browser/specs/bundle-budgets_spec.ts @@ -99,7 +99,6 @@ describe('Browser Builder bundle budgets', () => { 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\.${ext}`); await run.stop(); }); @@ -139,7 +138,6 @@ describe('Browser Builder bundle budgets', () => { 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\.${ext}`); await run.stop(); }); diff --git a/packages/angular_devkit/build_angular/src/dev-server/index.ts b/packages/angular_devkit/build_angular/src/dev-server/index.ts index 13b7357bf02c..a5b43a91eb18 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/index.ts +++ b/packages/angular_devkit/build_angular/src/dev-server/index.ts @@ -27,6 +27,7 @@ import { ExecutionTransformer } from '../transforms'; import { BuildBrowserFeatures, normalizeOptimization } from '../utils'; import { findCachePath } from '../utils/cache-path'; import { checkPort } from '../utils/check-port'; +import { colors } from '../utils/color'; import { I18nOptions } from '../utils/i18n-options'; import { getHtmlTransforms } from '../utils/index-file/transforms'; import { IndexHtmlTransform } from '../utils/index-file/write-index-html'; @@ -312,7 +313,7 @@ export function serveWebpackBrowser( } if (buildEvent.success) { - logger.info(': Compiled successfully.'); + logger.info(`${colors.greenBright(colors.symbols.check)} Compiled successfully.`); } return of({ ...buildEvent, baseUrl: serverAddress } as DevServerBuilderOutput); diff --git a/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts b/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts index 66a905c1ef95..ee1fb9e9e49d 100644 --- a/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts +++ b/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts @@ -8,13 +8,12 @@ import { BuilderContext } from '@angular-devkit/architect'; import { EmittedFiles } from '@angular-devkit/build-webpack'; import * as fs from 'fs'; -import * as ora from 'ora'; import * as path from 'path'; import { BundleActionExecutor } from './action-executor'; -import { colors } from './color'; import { copyAssets } from './copy-assets'; import { I18nOptions } from './i18n-options'; import { InlineOptions } from './process-bundle'; +import { Spinner } from './spinner'; function emittedFilesToInlineOptions( emittedFiles: EmittedFiles[], @@ -75,7 +74,8 @@ export async function i18nInlineEmittedFiles( ): Promise { const executor = new BundleActionExecutor({ i18n }); let hasErrors = false; - const spinner = ora('Generating localized bundles...').start(); + const spinner = new Spinner(); + spinner.start('Generating localized bundles...'); try { const { options, originalFiles: processedFiles } = emittedFilesToInlineOptions( @@ -114,7 +114,7 @@ export async function i18nInlineEmittedFiles( '', ); } catch (err) { - spinner.fail(colors.redBright('Localized bundle generation failed: ' + err.message)); + spinner.fail('Localized bundle generation failed: ' + err.message); return false; } finally { @@ -122,7 +122,7 @@ export async function i18nInlineEmittedFiles( } if (hasErrors) { - spinner.fail(colors.redBright('Localized bundle generation failed.')); + spinner.fail('Localized bundle generation failed.'); } else { spinner.succeed('Localized bundle generation complete.'); } diff --git a/packages/angular_devkit/build_angular/src/utils/spinner.ts b/packages/angular_devkit/build_angular/src/utils/spinner.ts new file mode 100644 index 000000000000..83c73de9cc66 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/spinner.ts @@ -0,0 +1,45 @@ +/** + * @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 * as ora from 'ora'; +import { colors } from './color'; + +export class Spinner { + private readonly spinner: ora.Ora; + + /** When false, only fail messages will be displayed. */ + enabled = true; + + constructor(text?: string) { + this.spinner = ora(text); + } + + set text(text: string) { + this.spinner.text = text; + } + + succeed(text?: string): void { + if (this.enabled) { + this.spinner.succeed(text); + } + } + + fail(text?: string): void { + this.spinner.fail(text && colors.redBright(text)); + } + + stop(): void { + this.spinner.stop(); + } + + start(text?: string) { + if (this.enabled) { + this.spinner.start(text); + } + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/common.ts b/packages/angular_devkit/build_angular/src/webpack/configs/common.ts index 7f282abf47ef..8f0dfd69a726 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/common.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/common.ts @@ -36,6 +36,7 @@ import { shouldBeautify, } from '../../utils/environment-options'; import { findAllNodeModules } from '../../utils/find-up'; +import { Spinner } from '../../utils/spinner'; import { isWebpackFiveOrHigher, withWebpackFourOrFive } from '../../utils/webpack-version'; import { BundleBudgetPlugin, @@ -53,12 +54,18 @@ const PnpWebpackPlugin = require('pnp-webpack-plugin'); // tslint:disable-next-line:no-big-function export function getCommonConfig(wco: WebpackConfigOptions): Configuration { const { root, projectRoot, buildOptions, tsConfig } = wco; - const { styles: stylesOptimization, scripts: scriptsOptimization } = buildOptions.optimization; const { - styles: stylesSourceMap, - scripts: scriptsSourceMap, - vendor: vendorSourceMap, - } = buildOptions.sourceMap; + platform = 'browser', + sourceMap: { + styles: stylesSourceMap, + scripts: scriptsSourceMap, + vendor: vendorSourceMap, + }, + optimization: { + styles: stylesOptimization, + scripts: scriptsOptimization, + }, + } = buildOptions; const extraPlugins: { apply(compiler: Compiler): void }[] = []; const extraRules: RuleSetRule[] = []; @@ -121,7 +128,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { } const differentialLoadingMode = buildOptions.differentialLoadingMode; - if (wco.buildOptions.platform !== 'server') { + if (platform !== 'server') { if (differentialLoadingMode || tsConfig.options.target === ScriptTarget.ES5) { const buildBrowserFeatures = new BuildBrowserFeatures( projectRoot, @@ -299,7 +306,23 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { if (buildOptions.progress) { const ProgressPlugin = require('webpack/lib/ProgressPlugin'); - extraPlugins.push(new ProgressPlugin({ profile: buildOptions.verbose })); + const spinner = new Spinner(); + + extraPlugins.push(new ProgressPlugin({ + handler: (percentage: number, message: string) => { + switch (percentage) { + case 0: + spinner.start(`Generating ${platform} application bundles...`); + break; + case 1: + spinner.succeed(`${platform.replace(/^\w/, s => s.toUpperCase())} application bundle generation complete.`); + break; + default: + spinner.text = `Generating ${platform} application bundles (phase: ${message})...`; + break; + } + }, + })); } if (buildOptions.showCircularDependencies) { @@ -392,7 +415,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { // to remove dev code, and ngI18nClosureMode to remove Closure compiler i18n code compress: allowMinify && - (buildOptions.platform == 'server' + (platform === 'server' ? { ecma: terserEcma, global_defs: angularGlobalDefinitions, @@ -408,7 +431,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { }), // We also want to avoid mangling on server. // Name mangling is handled within the browser builder - mangle: allowMangle && buildOptions.platform !== 'server' && !differentialLoadingMode, + mangle: allowMangle && platform !== 'server' && !differentialLoadingMode, }; const globalScriptsNames = globalScriptsByBundleName.map(s => s.bundleName); @@ -439,7 +462,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { ...terserOptions.output, ecma: 5, }, - mangle: allowMangle && buildOptions.platform !== 'server', + mangle: allowMangle && platform !== 'server', }, }), ); @@ -490,7 +513,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { options: { name: `[name]${hashFormat.file}.[ext]`, // Re-use emitted files from browser builder on the server. - emitFile: wco.buildOptions.platform !== 'server', + emitFile: platform !== 'server', }, }, { diff --git a/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts b/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts index 0a4b1faa0704..08837033f2fa 100644 --- a/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts +++ b/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts @@ -122,19 +122,24 @@ export function statsToString(json: any, statsConfig: any) { const unchangedChunkNumber = json.chunks.length - changedChunksStats.length; const statsTable = generateBuildStatsTable(changedChunksStats, colors); + // In some cases we do things outside of webpack context + // Such us index generation, service worker augmentation etc... + // This will correct the time and include these. + const time = (Date.now() - json.builtAt) + json.time; + if (unchangedChunkNumber > 0) { return '\n' + rs(tags.stripIndents` ${statsTable} ${unchangedChunkNumber} unchanged chunks - ${generateBuildStats(json.hash, json.time, colors)} + ${generateBuildStats(json.hash, time, colors)} `); } else { return '\n' + rs(tags.stripIndents` ${statsTable} - ${generateBuildStats(json.hash, json.time, colors)} + ${generateBuildStats(json.hash, time, colors)} `); } } diff --git a/tests/legacy-cli/e2e/tests/basic/e2e.ts b/tests/legacy-cli/e2e/tests/basic/e2e.ts index 8f7ef2de0569..20b8465cdfc6 100644 --- a/tests/legacy-cli/e2e/tests/basic/e2e.ts +++ b/tests/legacy-cli/e2e/tests/basic/e2e.ts @@ -59,7 +59,7 @@ export default function () { )) // Should run side-by-side with `ng serve` .then(() => execAndWaitForOutputToMatch('ng', ['serve'], - /: Compiled successfully./)) + / Compiled successfully./)) .then(() => ng('e2e', 'test-project', '--devServerTarget=')) .then(() => killAllProcesses(), (err: any) => { killAllProcesses(); diff --git a/tests/legacy-cli/e2e/tests/basic/rebuild.ts b/tests/legacy-cli/e2e/tests/basic/rebuild.ts index 8f2ef17dae99..27645ab86a12 100644 --- a/tests/legacy-cli/e2e/tests/basic/rebuild.ts +++ b/tests/legacy-cli/e2e/tests/basic/rebuild.ts @@ -8,7 +8,7 @@ import {writeFile, writeMultipleFiles} from '../../utils/fs'; import {wait} from '../../utils/utils'; import {request} from '../../utils/http'; -const validBundleRegEx = /: Compiled successfully./; +const validBundleRegEx = / Compiled successfully./; export default function() { return execAndWaitForOutputToMatch('ng', ['serve'], validBundleRegEx) diff --git a/tests/legacy-cli/e2e/tests/build/poll.ts b/tests/legacy-cli/e2e/tests/build/poll.ts index e2aacf46f586..9a63d5edb1c2 100644 --- a/tests/legacy-cli/e2e/tests/build/poll.ts +++ b/tests/legacy-cli/e2e/tests/build/poll.ts @@ -6,7 +6,7 @@ import { import { ngServe } from '../../utils/project'; import { expectToFail, wait } from '../../utils/utils'; -const webpackGoodRegEx = /: Compiled successfully./; +const webpackGoodRegEx = / Compiled successfully./; export default async function() { try { diff --git a/tests/legacy-cli/e2e/tests/build/rebuild-css-change.ts b/tests/legacy-cli/e2e/tests/build/rebuild-css-change.ts index 739e946bd8ff..952220ec662e 100644 --- a/tests/legacy-cli/e2e/tests/build/rebuild-css-change.ts +++ b/tests/legacy-cli/e2e/tests/build/rebuild-css-change.ts @@ -6,7 +6,7 @@ import { import {appendToFile} from '../../utils/fs'; import {getGlobalVariable} from '../../utils/env'; -const webpackGoodRegEx = /: Compiled successfully./; +const webpackGoodRegEx = / Compiled successfully./; export default function() { // TODO(architect): Delete this test. It is now in devkit/build-angular. diff --git a/tests/legacy-cli/e2e/tests/build/rebuild-deps-type-check.ts b/tests/legacy-cli/e2e/tests/build/rebuild-deps-type-check.ts index 57f051357200..a4942e1d0d99 100644 --- a/tests/legacy-cli/e2e/tests/build/rebuild-deps-type-check.ts +++ b/tests/legacy-cli/e2e/tests/build/rebuild-deps-type-check.ts @@ -7,7 +7,7 @@ import {writeFile, prependToFile, appendToFile} from '../../utils/fs'; const doneRe = - /: Compiled successfully.|: Failed to compile./; + / Compiled successfully.|: Failed to compile./; const errorRe = /Error/; diff --git a/tests/legacy-cli/e2e/tests/build/rebuild-error.ts b/tests/legacy-cli/e2e/tests/build/rebuild-error.ts index ac5070a29e40..2aeb84e291d0 100644 --- a/tests/legacy-cli/e2e/tests/build/rebuild-error.ts +++ b/tests/legacy-cli/e2e/tests/build/rebuild-error.ts @@ -9,7 +9,7 @@ import { wait, expectToFail } from '../../utils/utils'; const failedRe = /: Failed to compile/; -const successRe = /: Compiled successfully/; +const successRe = / Compiled successfully/; const errorRe = /ERROR in/; const extraErrors = [ `Final loader didn't return a Buffer or String`, diff --git a/tests/legacy-cli/e2e/tests/build/rebuild-ngfactories.ts b/tests/legacy-cli/e2e/tests/build/rebuild-ngfactories.ts index 1828ca8efaf7..d87d343d2256 100644 --- a/tests/legacy-cli/e2e/tests/build/rebuild-ngfactories.ts +++ b/tests/legacy-cli/e2e/tests/build/rebuild-ngfactories.ts @@ -6,7 +6,7 @@ import { import { appendToFile, writeMultipleFiles, replaceInFile, expectFileToMatch } from '../../utils/fs'; import { getGlobalVariable } from '../../utils/env'; -const validBundleRegEx = /: Compiled successfully./; +const validBundleRegEx = / Compiled successfully./; export default function () { // TODO(architect): This test is behaving oddly both here and in devkit/build-angular. diff --git a/tests/legacy-cli/e2e/tests/build/rebuild-replacements.ts b/tests/legacy-cli/e2e/tests/build/rebuild-replacements.ts index b38df33587b7..12b4428cd142 100644 --- a/tests/legacy-cli/e2e/tests/build/rebuild-replacements.ts +++ b/tests/legacy-cli/e2e/tests/build/rebuild-replacements.ts @@ -1,4 +1,3 @@ -import { getGlobalVariable } from '../../utils/env'; import { appendToFile } from '../../utils/fs'; import { execAndWaitForOutputToMatch, @@ -7,7 +6,7 @@ import { } from '../../utils/process'; import { wait } from '../../utils/utils'; -const webpackGoodRegEx = /: Compiled successfully./; +const webpackGoodRegEx = / Compiled successfully./; export default async function() { if (process.platform.startsWith('win')) { diff --git a/tests/legacy-cli/e2e/tests/build/rebuild-types.ts b/tests/legacy-cli/e2e/tests/build/rebuild-types.ts index 4056dee0b802..90d1bc4c2eff 100644 --- a/tests/legacy-cli/e2e/tests/build/rebuild-types.ts +++ b/tests/legacy-cli/e2e/tests/build/rebuild-types.ts @@ -7,7 +7,7 @@ import { writeFile, prependToFile } from '../../utils/fs'; import {getGlobalVariable} from '../../utils/env'; -const successRe = /: Compiled successfully/; +const successRe = / Compiled successfully/; export default async function() { // TODO(architect): Delete this test. It is now in devkit/build-angular. diff --git a/tests/legacy-cli/e2e/tests/misc/live-reload.ts b/tests/legacy-cli/e2e/tests/misc/live-reload.ts index c70ae92ab091..8787089dcd7d 100644 --- a/tests/legacy-cli/e2e/tests/misc/live-reload.ts +++ b/tests/legacy-cli/e2e/tests/misc/live-reload.ts @@ -15,7 +15,7 @@ export default function temporarilyDisabledTest() { return Promise.resolve(); } // export default function () { // const protractorGoodRegEx = /Jasmine started/; -// const webpackGoodRegEx = /: Compiled successfully./; +// const webpackGoodRegEx = / Compiled successfully./; // // Create an express api for the Angular app to call. // const app = express(); diff --git a/tests/legacy-cli/e2e/utils/project.ts b/tests/legacy-cli/e2e/utils/project.ts index 02d10a15264d..4be5dce5145c 100644 --- a/tests/legacy-cli/e2e/utils/project.ts +++ b/tests/legacy-cli/e2e/utils/project.ts @@ -30,7 +30,7 @@ export function updateTsConfig(fn: (json: any) => any | void) { export function ngServe(...args: string[]) { return execAndWaitForOutputToMatch('ng', ['serve', ...args], - /: Compiled successfully./); + / Compiled successfully./); }