UNPKG

@angular-devkit/build-angular

Version:
301 lines (300 loc) 12.7 kB
"use strict"; /** * @license * Copyright Google LLC 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.dev/license */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.statsWarningsToString = statsWarningsToString; exports.statsErrorsToString = statsErrorsToString; exports.statsHasErrors = statsHasErrors; exports.statsHasWarnings = statsHasWarnings; exports.createWebpackLoggingCallback = createWebpackLoggingCallback; exports.generateBuildEventStats = generateBuildEventStats; exports.webpackStatsLogger = webpackStatsLogger; const private_1 = require("@angular/build/private"); const node_assert_1 = __importDefault(require("node:assert")); const path = __importStar(require("node:path")); const utils_1 = require("../../../utils"); const color_1 = require("../../../utils/color"); const async_chunks_1 = require("./async-chunks"); const helpers_1 = require("./helpers"); function getBuildDuration(webpackStats) { (0, node_assert_1.default)(webpackStats.builtAt, 'buildAt cannot be undefined'); (0, node_assert_1.default)(webpackStats.time, 'time cannot be undefined'); return Date.now() - webpackStats.builtAt + webpackStats.time; } function generateBundleStats(info) { const rawSize = typeof info.rawSize === 'number' ? info.rawSize : '-'; const estimatedTransferSize = typeof info.estimatedTransferSize === 'number' ? info.estimatedTransferSize : '-'; const files = info.files ?.filter((f) => !f.endsWith('.map')) .map((f) => path.basename(f)) .join(', ') ?? ''; const names = info.names?.length ? info.names.join(', ') : '-'; const initial = !!info.initial; return { initial, stats: [files, names, rawSize, estimatedTransferSize], }; } // We use this cache because we can have multiple builders running in the same process, // where each builder has different output path. // Ideally, we should create the logging callback as a factory, but that would need a refactoring. const runsCache = new Set(); function statsToString(json, // eslint-disable-next-line @typescript-eslint/no-explicit-any statsConfig, budgetFailures) { if (!json.chunks?.length) { return ''; } const colors = statsConfig.colors; const rs = (x) => (colors ? color_1.colors.reset(x) : x); const w = (x) => (colors ? color_1.colors.bold.white(x) : x); const changedChunksStats = []; let unchangedChunkNumber = 0; let hasEstimatedTransferSizes = false; const isFirstRun = !runsCache.has(json.outputPath || ''); for (const chunk of json.chunks) { // During first build we want to display unchanged chunks // but unchanged cached chunks are always marked as not rendered. if (!isFirstRun && !chunk.rendered) { continue; } const assets = json.assets?.filter((asset) => chunk.files?.includes(asset.name)); let rawSize = 0; let estimatedTransferSize; if (assets) { for (const asset of assets) { if (asset.name.endsWith('.map')) { continue; } rawSize += asset.size; if (typeof asset.info.estimatedTransferSize === 'number') { if (estimatedTransferSize === undefined) { estimatedTransferSize = 0; hasEstimatedTransferSizes = true; } estimatedTransferSize += asset.info.estimatedTransferSize; } } } changedChunksStats.push(generateBundleStats({ ...chunk, rawSize, estimatedTransferSize })); } unchangedChunkNumber = json.chunks.length - changedChunksStats.length; runsCache.add(json.outputPath || ''); const statsTable = (0, private_1.generateBuildStatsTable)(changedChunksStats, colors, unchangedChunkNumber === 0, hasEstimatedTransferSizes, budgetFailures); // 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 = getBuildDuration(json); return rs(`\n${statsTable}\n\n` + (unchangedChunkNumber > 0 ? `${unchangedChunkNumber} unchanged chunks\n\n` : '') + `Build at: ${w(new Date().toISOString())} - Hash: ${w(json.hash || '')} - Time: ${w('' + time)}ms`); } function statsWarningsToString(json, statsConfig) { const colors = statsConfig.colors; const c = (x) => (colors ? color_1.colors.reset.cyan(x) : x); const y = (x) => (colors ? color_1.colors.reset.yellow(x) : x); const yb = (x) => (colors ? color_1.colors.reset.yellowBright(x) : x); const warnings = json.warnings ? [...json.warnings] : []; if (json.children) { warnings.push(...json.children.map((c) => c.warnings ?? []).reduce((a, b) => [...a, ...b], [])); } let output = ''; for (const warning of warnings) { if (typeof warning === 'string') { output += yb(`Warning: ${warning}\n\n`); } else { let file = warning.file || warning.moduleName; // Clean up warning paths // Ex: ./src/app/styles.scss.webpack[javascript/auto]!=!./node_modules/css-loader/dist/cjs.js.... // to ./src/app/styles.scss.webpack if (file && !statsConfig.errorDetails) { const webpackPathIndex = file.indexOf('.webpack['); if (webpackPathIndex !== -1) { file = file.substring(0, webpackPathIndex); } } if (file) { output += c(file); if (warning.loc) { output += ':' + yb(warning.loc); } output += ' - '; } if (!/^warning/i.test(warning.message)) { output += y('Warning: '); } output += `${warning.message}\n\n`; } } return output ? '\n' + output : output; } function statsErrorsToString(json, statsConfig) { const colors = statsConfig.colors; const c = (x) => (colors ? color_1.colors.reset.cyan(x) : x); const yb = (x) => (colors ? color_1.colors.reset.yellowBright(x) : x); const r = (x) => (colors ? color_1.colors.reset.redBright(x) : x); const errors = json.errors ? [...json.errors] : []; if (json.children) { errors.push(...json.children.map((c) => c?.errors || []).reduce((a, b) => [...a, ...b], [])); } let output = ''; for (const error of errors) { if (typeof error === 'string') { output += r(`Error: ${error}\n\n`); } else { let file = error.file || error.moduleName; // Clean up error paths // Ex: ./src/app/styles.scss.webpack[javascript/auto]!=!./node_modules/css-loader/dist/cjs.js.... // to ./src/app/styles.scss.webpack if (file && !statsConfig.errorDetails) { const webpackPathIndex = file.indexOf('.webpack['); if (webpackPathIndex !== -1) { file = file.substring(0, webpackPathIndex); } } if (file) { output += c(file); if (error.loc) { output += ':' + yb(error.loc); } output += ' - '; } // In most cases webpack will add stack traces to error messages. // This below cleans up the error from stacks. // See: https://github.com/webpack/webpack/issues/15980 const index = error.message.search(/[\n\s]+at /); const message = statsConfig.errorStack || index === -1 ? error.message : error.message.substring(0, index); if (!/^error/i.test(message)) { output += r('Error: '); } output += `${message}\n\n`; } } return output ? '\n' + output : output; } function statsHasErrors(json) { return !!(json.errors?.length || json.children?.some((c) => c.errors?.length)); } function statsHasWarnings(json) { return !!(json.warnings?.length || json.children?.some((c) => c.warnings?.length)); } function createWebpackLoggingCallback(options, logger) { const { verbose = false, scripts = [], styles = [] } = options; const extraEntryPoints = [ ...(0, helpers_1.normalizeExtraEntryPoints)(styles, 'styles'), ...(0, helpers_1.normalizeExtraEntryPoints)(scripts, 'scripts'), ]; return (stats, config) => { if (verbose && config.stats !== false) { const statsOptions = config.stats === true ? undefined : config.stats; logger.info(stats.toString(statsOptions)); } const rawStats = stats.toJson((0, helpers_1.getStatsOptions)(false)); const webpackStats = { ...rawStats, chunks: (0, async_chunks_1.markAsyncChunksNonInitial)(rawStats, extraEntryPoints), }; webpackStatsLogger(logger, webpackStats, config); }; } function generateBuildEventStats(webpackStats, browserBuilderOptions) { const { chunks = [], assets = [] } = webpackStats; let jsSizeInBytes = 0; let cssSizeInBytes = 0; let initialChunksCount = 0; let ngComponentCount = 0; let changedChunksCount = 0; const allChunksCount = chunks.length; const isFirstRun = !runsCache.has(webpackStats.outputPath || ''); const chunkFiles = new Set(); for (const chunk of chunks) { if (!isFirstRun && chunk.rendered) { changedChunksCount++; } if (chunk.initial) { initialChunksCount++; } for (const file of chunk.files ?? []) { chunkFiles.add(file); } } for (const asset of assets) { if (asset.name.endsWith('.map') || !chunkFiles.has(asset.name)) { continue; } if (asset.name.endsWith('.js')) { jsSizeInBytes += asset.size; ngComponentCount += asset.info.ngComponentCount ?? 0; } else if (asset.name.endsWith('.css')) { cssSizeInBytes += asset.size; } } return { optimization: !!(0, utils_1.normalizeOptimization)(browserBuilderOptions.optimization).scripts, aot: browserBuilderOptions.aot !== false, allChunksCount, lazyChunksCount: allChunksCount - initialChunksCount, initialChunksCount, changedChunksCount, durationInMs: getBuildDuration(webpackStats), cssSizeInBytes, jsSizeInBytes, ngComponentCount, }; } function webpackStatsLogger(logger, json, config, budgetFailures) { logger.info(statsToString(json, config.stats, budgetFailures)); if (typeof config.stats !== 'object') { throw new Error('Invalid Webpack stats configuration.'); } if (statsHasWarnings(json)) { logger.warn(statsWarningsToString(json, config.stats)); } if (statsHasErrors(json)) { logger.error(statsErrorsToString(json, config.stats)); } }