/
githubmirror
/
angular-cli
Обзор
Документация
Войти
/
githubmirror
/
angular-cli
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/angular/build/src/builders/application/execute-build.ts
388 строк
15 KB
Alan Agius
fix(@angular/build): return only lowest version per target engine
07 авг 2026, 16:28
07 авг 2026, 16:28
3c7ac15
Код
Авторство
О чём код?
/** * @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 */ import { BuilderContext } from '@angular-devkit/architect'; import { createAngularCompilation } from '../../tools/angular/compilation'; import { AngularCompilationContext } from '../../tools/esbuild/angular/compilation-state'; import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache'; import { generateBudgetStats } from '../../tools/esbuild/budget-stats'; import { BundleContextResult, BundlerContext } from '../../tools/esbuild/bundler-context'; import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result'; import { BuildOutputFileType } from '../../tools/esbuild/bundler-files'; import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker'; import { LOCALE_DATA_BASE_MODULE } from '../../tools/esbuild/i18n-locale-plugin'; import { extractLicenses } from '../../tools/esbuild/license-extractor'; import { profileAsync } from '../../tools/esbuild/profiling'; import { transformSupportedBrowsersToTargets } from '../../tools/esbuild/target'; import { calculateEstimatedTransferSizes, logBuildStats } from '../../tools/esbuild/utils'; import { BudgetCalculatorResult, checkBudgets } from '../../utils/bundle-calculator'; import { optimizeChunksThreshold } from '../../utils/environment-options'; import { resolveAssets } from '../../utils/resolve-assets'; import { SERVER_APP_ENGINE_MANIFEST_FILENAME, generateAngularServerAppEngineManifest, } from '../../utils/server-rendering/manifest'; import { getSupportedBrowsers } from '../../utils/supported-browsers'; import { executePostBundleSteps } from './execute-post-bundle'; import { inlineI18n, loadActiveTranslations } from './i18n'; import { NormalizedApplicationBuildOptions } from './options'; import { createComponentStyleBundler, setupBundlerContexts } from './setup-bundling'; // eslint-disable-next-line max-lines-per-function export async function executeBuild( options: NormalizedApplicationBuildOptions, context: BuilderContext, rebuildState?: RebuildState, ): Promise<ExecutionResult> { const { projectRoot, workspaceRoot, i18nOptions, optimizationOptions, assets, cacheOptions, serverEntryPoint, baseHref, ssrOptions, verbose, colors, jsonLogs, security, } = options; // TODO: Consider integrating into watch mode. Would require full rebuild on target changes. const browsers = getSupportedBrowsers(projectRoot, context.logger); // Load active translations if inlining // TODO: Integrate into watch mode and only load changed translations if (i18nOptions.shouldInline) { await loadActiveTranslations(context, i18nOptions); } // Reuse rebuild state or create new bundle contexts for code and global stylesheets let bundlerContexts; let componentStyleBundler; let codeBundleCache; let bundlingResult: BundleContextResult; let templateUpdates: Map<string, string> | undefined; let angularCompilationContext: AngularCompilationContext | undefined; let executionResult: ExecutionResult | undefined; try { if (rebuildState) { bundlerContexts = rebuildState.rebuildContexts; componentStyleBundler = rebuildState.componentStyleBundler; codeBundleCache = rebuildState.codeBundleCache; templateUpdates = rebuildState.templateUpdates; executionResult = new ExecutionResult( bundlerContexts, componentStyleBundler, codeBundleCache, templateUpdates, ); // Reset template updates for new rebuild templateUpdates?.clear(); const allFileChanges = rebuildState.fileChanges.all; // Bundle all contexts that do not require TypeScript changed file checks. // These will automatically use cached results based on the changed files. bundlingResult = await BundlerContext.bundleAll( bundlerContexts.otherContexts, allFileChanges, ); // Check the TypeScript code bundling cache for changes. If invalid, force a rebundle of // all TypeScript related contexts. const forceTypeScriptRebuild = codeBundleCache?.invalidate(allFileChanges); const typescriptResults: BundleContextResult[] = []; for (const typescriptContext of bundlerContexts.typescriptContexts) { typescriptContext.invalidate(allFileChanges); const result = await typescriptContext.bundle(forceTypeScriptRebuild); typescriptResults.push(result); } bundlingResult = BundlerContext.mergeResults([bundlingResult, ...typescriptResults]); } else { const target = transformSupportedBrowsersToTargets(browsers); codeBundleCache = new SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined); componentStyleBundler = createComponentStyleBundler(options, target); if (options.templateUpdates) { templateUpdates = new Map<string, string>(); } const angularCompilation = await createAngularCompilation( !!options.jit, !options.serverEntryPoint, ); angularCompilationContext = new AngularCompilationContext(angularCompilation); bundlerContexts = setupBundlerContexts( options, target, codeBundleCache, componentStyleBundler, angularCompilationContext, templateUpdates, ); executionResult = new ExecutionResult( bundlerContexts, componentStyleBundler, codeBundleCache, templateUpdates, ); // Bundle everything on initial build bundlingResult = await BundlerContext.bundleAll([ ...bundlerContexts.typescriptContexts, ...bundlerContexts.otherContexts, ]); } // Update any external component styles if enabled and rebuilding. // TODO: Only attempt rebundling of invalidated styles once incremental build results are supported. if (rebuildState && options.externalRuntimeStyles) { componentStyleBundler.invalidate(rebuildState.fileChanges.all); const componentResults = await componentStyleBundler.bundleAllFiles(true, true); bundlingResult = BundlerContext.mergeResults([bundlingResult, ...componentResults]); } executionResult.addWarnings(bundlingResult.warnings); // Add used external component style referenced files to be watched if (options.externalRuntimeStyles) { executionResult.extraWatchFiles.push(...componentStyleBundler.collectReferencedFiles()); } // In non-watch mode, the bundler rebuild contexts and Angular compilation // are no longer needed once bundling completes. Dispose them early to free // worker processes, TypeScript ASTs, and caches before post-bundling steps // execute or before returning bundling errors. // This is fire-and-forget so worker teardown runs concurrently in the background // without blocking the critical path of post-bundle optimization steps. // Any in-flight disposal is awaited in the builder action's finally block. if (!options.watch) { void angularCompilationContext?.dispose(); void executionResult.dispose(); } // Return if the bundling has errors if (bundlingResult.errors) { executionResult.addErrors(bundlingResult.errors); return executionResult; } // Optimize chunks if enabled and threshold is met. // This pass uses Rollup/Rolldown to further optimize chunks generated by esbuild. if (options.optimizationOptions.scripts) { // Count lazy chunks (files not needed for initial load). // Advanced chunk optimization is most beneficial when there are multiple lazy chunks. const { metafile, initialFiles } = bundlingResult; const lazyChunksCount = Object.keys(metafile.outputs).filter( (path) => path.endsWith('.js') && !initialFiles.has(path), ).length; // Only run if the number of lazy chunks meets the configured threshold. // This avoids overhead for small projects with few chunks. if (lazyChunksCount >= optimizeChunksThreshold) { const { optimizeChunks } = await import('./chunk-optimizer'); const optimizationResult = await profileAsync('OPTIMIZE_CHUNKS', () => optimizeChunks( bundlingResult, options.sourcemapOptions.scripts ? !options.sourcemapOptions.hidden || 'hidden' : false, ), ); if (optimizationResult.errors) { executionResult.addErrors(optimizationResult.errors); return executionResult; } bundlingResult = optimizationResult; } } // Analyze external imports if external options are enabled if (options.externalPackages || bundlingResult.externalConfiguration) { const { externalConfiguration = [], externalImports: { browser = [], server = [] }, } = bundlingResult; // Similar to esbuild, --external:@foo/bar automatically implies --external:@foo/bar/*, // which matches import paths like @foo/bar/baz. // This means all paths within the @foo/bar package are also marked as external. const exclusionsPrefixes = externalConfiguration.map((exclusion) => exclusion + '/'); const exclusions = new Set(externalConfiguration); const explicitExternal = new Set<string>(); const isExplicitExternal = (dep: string): boolean => { // Locale-data imports are injected by the builder itself (see i18n-locale-plugin) and must stay resolvable // even when the containing package is externalized by the user. if (dep.startsWith(`${LOCALE_DATA_BASE_MODULE}/`)) { return false; } if (exclusions.has(dep)) { return true; } for (const prefix of exclusionsPrefixes) { if (dep.startsWith(prefix)) { return true; } } return false; }; const implicitBrowser: string[] = []; for (const dep of browser) { if (isExplicitExternal(dep)) { explicitExternal.add(dep); } else { implicitBrowser.push(dep); } } const implicitServer: string[] = []; for (const dep of server) { if (isExplicitExternal(dep)) { explicitExternal.add(dep); } else { implicitServer.push(dep); } } executionResult.setExternalMetadata(implicitBrowser, implicitServer, [...explicitExternal]); } const { metafile, initialFiles, outputFiles } = bundlingResult; executionResult.outputFiles.push(...outputFiles); // Analyze files for bundle budget failures if present let budgetFailures: BudgetCalculatorResult[] | undefined; if (options.budgets) { const compatStats = generateBudgetStats(metafile, outputFiles, initialFiles); budgetFailures = [...checkBudgets(options.budgets, compatStats, true)]; for (const { message, severity } of budgetFailures) { if (severity === 'error') { executionResult.addError(message); } else { executionResult.addWarning(message); } } } // Calculate estimated transfer size if scripts are optimized let estimatedTransferSizes; if (optimizationOptions.scripts || optimizationOptions.styles.minify) { estimatedTransferSizes = await calculateEstimatedTransferSizes(executionResult.outputFiles); } // Check metafile for CommonJS module usage if optimizing scripts if (optimizationOptions.scripts) { const messages = checkCommonJSModules(metafile, options.allowedCommonJsDependencies); executionResult.addWarnings(messages); } // Copy assets if (assets) { executionResult.addAssets(await resolveAssets(assets, workspaceRoot)); } // Extract and write licenses for used packages if (options.extractLicenses) { executionResult.addOutputFile( '3rdpartylicenses.txt', await extractLicenses(metafile, workspaceRoot), BuildOutputFileType.Root, ); } // Watch input index HTML file if configured if (options.indexHtmlOptions) { executionResult.extraWatchFiles.push(options.indexHtmlOptions.input); executionResult.htmlIndexPath = options.indexHtmlOptions.output; executionResult.htmlBaseHref = options.baseHref; } // Create server app engine manifest if (serverEntryPoint) { executionResult.addOutputFile( SERVER_APP_ENGINE_MANIFEST_FILENAME, generateAngularServerAppEngineManifest(i18nOptions, security.allowedHosts, baseHref), BuildOutputFileType.ServerRoot, ); } // Perform i18n translation inlining if enabled if (i18nOptions.shouldInline) { const result = await inlineI18n(metafile, options, executionResult, initialFiles); executionResult.addErrors(result.errors); executionResult.addWarnings(result.warnings); executionResult.addPrerenderedRoutes(result.prerenderedRoutes); } else { const result = await executePostBundleSteps( metafile, options, executionResult.outputFiles, executionResult.assetFiles, initialFiles, // Set lang attribute to the defined source locale if present i18nOptions.hasDefinedSourceLocale ? i18nOptions.sourceLocale : undefined, ); // Deduplicate and add errors and warnings executionResult.addErrors([...new Set(result.errors)]); executionResult.addWarnings([...new Set(result.warnings)]); executionResult.addPrerenderedRoutes(result.prerenderedRoutes); executionResult.outputFiles.push(...result.additionalOutputFiles); executionResult.assetFiles.push(...result.additionalAssets); } executionResult.addOutputFile( 'prerendered-routes.json', JSON.stringify({ routes: executionResult.prerenderedRoutes }, null, 2), BuildOutputFileType.Root, ); // Write metafile if stats option is enabled if (options.stats) { executionResult.addOutputFile( 'stats.json', JSON.stringify(metafile, null, 2), BuildOutputFileType.Root, ); } if (!jsonLogs && !options.quiet) { const changedFiles = rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputInfo); executionResult.addLog( logBuildStats( metafile, outputFiles, initialFiles, budgetFailures, colors, changedFiles, estimatedTransferSizes, !!ssrOptions, verbose, ), ); } return executionResult; } catch (error) { await Promise.allSettled([angularCompilationContext?.dispose(), executionResult?.dispose()]); throw error; } }