From 446fd9486f8faaa38128b383021bee348195dc36 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:38:57 -0400 Subject: [PATCH] refactor(@angular/build): create component stylesheet bundler at start of build The component stylesheet bundler is now created at the start of the build and accessible prior to the bundling actions. This will provide support for generating updated component styles and bypassing all code bundling when using the development server and only component styles have been modified. --- goldens/circular-deps/packages.json | 7 ++ .../src/builders/application/execute-build.ts | 32 ++++++--- .../builders/application/setup-bundling.ts | 65 +++++++++++++++--- .../tools/esbuild/angular/compiler-plugin.ts | 1 - .../tools/esbuild/application-code-bundle.ts | 31 ++++----- .../tools/esbuild/bundler-execution-result.ts | 5 ++ .../tools/esbuild/compiler-plugin-options.ts | 68 ++++--------------- .../src/utils/server-rendering/manifest.ts | 2 +- 8 files changed, 116 insertions(+), 95 deletions(-) diff --git a/goldens/circular-deps/packages.json b/goldens/circular-deps/packages.json index 9289dfc27248..0d4d97ad1edd 100644 --- a/goldens/circular-deps/packages.json +++ b/goldens/circular-deps/packages.json @@ -3,6 +3,13 @@ "packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts", "packages/angular_devkit/build_angular/src/builders/dev-server/options.ts" ], + [ + "packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts", + "packages/angular/build/src/tools/esbuild/bundler-context.ts", + "packages/angular/build/src/tools/esbuild/utils.ts", + "packages/angular/build/src/utils/server-rendering/manifest.ts", + "packages/angular/build/src/tools/esbuild/bundler-execution-result.ts" + ], [ "packages/angular/build/src/tools/esbuild/bundler-context.ts", "packages/angular/build/src/tools/esbuild/utils.ts" diff --git a/packages/angular/build/src/builders/application/execute-build.ts b/packages/angular/build/src/builders/application/execute-build.ts index ef3bb68d4184..bc78be6aa3e5 100644 --- a/packages/angular/build/src/builders/application/execute-build.ts +++ b/packages/angular/build/src/builders/application/execute-build.ts @@ -15,7 +15,11 @@ import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execu import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker'; import { extractLicenses } from '../../tools/esbuild/license-extractor'; import { profileAsync } from '../../tools/esbuild/profiling'; -import { calculateEstimatedTransferSizes, logBuildStats } from '../../tools/esbuild/utils'; +import { + calculateEstimatedTransferSizes, + logBuildStats, + transformSupportedBrowsersToTargets, +} from '../../tools/esbuild/utils'; import { BudgetCalculatorResult, checkBudgets } from '../../utils/bundle-calculator'; import { shouldOptimizeChunks } from '../../utils/environment-options'; import { resolveAssets } from '../../utils/resolve-assets'; @@ -29,7 +33,7 @@ import { executePostBundleSteps } from './execute-post-bundle'; import { inlineI18n, loadActiveTranslations } from './i18n'; import { NormalizedApplicationBuildOptions } from './options'; import { OutputMode } from './schema'; -import { setupBundlerContexts } from './setup-bundling'; +import { createComponentStyleBundler, setupBundlerContexts } from './setup-bundling'; // eslint-disable-next-line max-lines-per-function export async function executeBuild( @@ -63,12 +67,18 @@ export async function executeBuild( } // Reuse rebuild state or create new bundle contexts for code and global stylesheets - let bundlerContexts = rebuildState?.rebuildContexts; - const codeBundleCache = - rebuildState?.codeBundleCache ?? - new SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined); - if (bundlerContexts === undefined) { - bundlerContexts = setupBundlerContexts(options, browsers, codeBundleCache); + let bundlerContexts; + let componentStyleBundler; + let codeBundleCache; + if (rebuildState) { + bundlerContexts = rebuildState.rebuildContexts; + componentStyleBundler = rebuildState.componentStyleBundler; + codeBundleCache = rebuildState.codeBundleCache; + } else { + const target = transformSupportedBrowsersToTargets(browsers); + codeBundleCache = new SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined); + componentStyleBundler = createComponentStyleBundler(options, target); + bundlerContexts = setupBundlerContexts(options, target, codeBundleCache, componentStyleBundler); } let bundlingResult = await BundlerContext.bundleAll( @@ -85,7 +95,11 @@ export async function executeBuild( ); } - const executionResult = new ExecutionResult(bundlerContexts, codeBundleCache); + const executionResult = new ExecutionResult( + bundlerContexts, + componentStyleBundler, + codeBundleCache, + ); executionResult.addWarnings(bundlingResult.warnings); // Return if the bundling has errors diff --git a/packages/angular/build/src/builders/application/setup-bundling.ts b/packages/angular/build/src/builders/application/setup-bundling.ts index eb4012594f90..8f38137ff3ee 100644 --- a/packages/angular/build/src/builders/application/setup-bundling.ts +++ b/packages/angular/build/src/builders/application/setup-bundling.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import { ComponentStylesheetBundler } from '../../tools/esbuild/angular/component-stylesheets'; import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache'; import { createBrowserCodeBundleOptions, @@ -17,10 +18,7 @@ import { import { BundlerContext } from '../../tools/esbuild/bundler-context'; import { createGlobalScriptsBundleOptions } from '../../tools/esbuild/global-scripts'; import { createGlobalStylesBundleOptions } from '../../tools/esbuild/global-styles'; -import { - getSupportedNodeTargets, - transformSupportedBrowsersToTargets, -} from '../../tools/esbuild/utils'; +import { getSupportedNodeTargets } from '../../tools/esbuild/utils'; import { NormalizedApplicationBuildOptions } from './options'; /** @@ -33,8 +31,9 @@ import { NormalizedApplicationBuildOptions } from './options'; */ export function setupBundlerContexts( options: NormalizedApplicationBuildOptions, - browsers: string[], + target: string[], codeBundleCache: SourceFileCache, + stylesheetBundler: ComponentStylesheetBundler, ): BundlerContext[] { const { outputMode, @@ -45,7 +44,6 @@ export function setupBundlerContexts( workspaceRoot, watch = false, } = options; - const target = transformSupportedBrowsersToTargets(browsers); const bundlerContexts = []; // Browser application code @@ -53,7 +51,7 @@ export function setupBundlerContexts( new BundlerContext( workspaceRoot, watch, - createBrowserCodeBundleOptions(options, target, codeBundleCache), + createBrowserCodeBundleOptions(options, target, codeBundleCache, stylesheetBundler), ), ); @@ -62,6 +60,7 @@ export function setupBundlerContexts( options, target, codeBundleCache, + stylesheetBundler, ); if (browserPolyfillBundleOptions) { bundlerContexts.push(new BundlerContext(workspaceRoot, watch, browserPolyfillBundleOptions)); @@ -99,7 +98,7 @@ export function setupBundlerContexts( new BundlerContext( workspaceRoot, watch, - createServerMainCodeBundleOptions(options, nodeTargets, codeBundleCache), + createServerMainCodeBundleOptions(options, nodeTargets, codeBundleCache, stylesheetBundler), ), ); @@ -109,7 +108,7 @@ export function setupBundlerContexts( new BundlerContext( workspaceRoot, watch, - createSsrEntryCodeBundleOptions(options, nodeTargets, codeBundleCache), + createSsrEntryCodeBundleOptions(options, nodeTargets, codeBundleCache, stylesheetBundler), ), ); } @@ -128,3 +127,51 @@ export function setupBundlerContexts( return bundlerContexts; } + +export function createComponentStyleBundler( + options: NormalizedApplicationBuildOptions, + target: string[], +): ComponentStylesheetBundler { + const { + workspaceRoot, + optimizationOptions, + sourcemapOptions, + outputNames, + externalDependencies, + preserveSymlinks, + stylePreprocessorOptions, + inlineStyleLanguage, + cacheOptions, + tailwindConfiguration, + postcssConfiguration, + publicPath, + } = options; + const incremental = !!options.watch; + + return new ComponentStylesheetBundler( + { + workspaceRoot, + inlineFonts: !!optimizationOptions.fonts.inline, + optimization: !!optimizationOptions.styles.minify, + sourcemap: + // Hidden component stylesheet sourcemaps are inaccessible which is effectively + // the same as being disabled. Disabling has the advantage of avoiding the overhead + // of sourcemap processing. + sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false, + outputNames, + includePaths: stylePreprocessorOptions?.includePaths, + // string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sass: stylePreprocessorOptions?.sass as any, + externalDependencies, + target, + preserveSymlinks, + tailwindConfiguration, + postcssConfiguration, + cacheOptions, + publicPath, + }, + inlineStyleLanguage, + incremental, + ); +} diff --git a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts index 544f03fe31b4..fda63af4b56c 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts @@ -532,7 +532,6 @@ export function createCompilerPlugin( build.onDispose(() => { sharedTSCompilationState?.dispose(); - void stylesheetBundler.dispose(); void compilation.close?.(); void cacheStore?.close(); }); diff --git a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts index 1fa63bdfb4e8..05b61a819fa0 100644 --- a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts +++ b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts @@ -18,6 +18,7 @@ import { SERVER_APP_MANIFEST_FILENAME, } from '../../utils/server-rendering/manifest'; import { createCompilerPlugin } from './angular/compiler-plugin'; +import { ComponentStylesheetBundler } from './angular/component-stylesheets'; import { SourceFileCache } from './angular/source-file-cache'; import { BundlerOptionsFactory } from './bundler-context'; import { createCompilerPluginOptions } from './compiler-plugin-options'; @@ -34,15 +35,12 @@ import { createWasmPlugin } from './wasm-plugin'; export function createBrowserCodeBundleOptions( options: NormalizedApplicationBuildOptions, target: string[], - sourceFileCache?: SourceFileCache, + sourceFileCache: SourceFileCache, + stylesheetBundler: ComponentStylesheetBundler, ): BuildOptions { const { entryPoints, outputNames, polyfills } = options; - const { pluginOptions, stylesheetBundler } = createCompilerPluginOptions( - options, - target, - sourceFileCache, - ); + const pluginOptions = createCompilerPluginOptions(options, sourceFileCache); const zoneless = isZonelessApp(polyfills); @@ -100,7 +98,8 @@ export function createBrowserCodeBundleOptions( export function createBrowserPolyfillBundleOptions( options: NormalizedApplicationBuildOptions, target: string[], - sourceFileCache?: SourceFileCache, + sourceFileCache: SourceFileCache, + stylesheetBundler: ComponentStylesheetBundler, ): BuildOptions | BundlerOptionsFactory | undefined { const namespace = 'angular:polyfills'; const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions( @@ -134,9 +133,9 @@ export function createBrowserPolyfillBundleOptions( // Only add the Angular TypeScript compiler if TypeScript files are provided in the polyfills if (hasTypeScriptEntries) { buildOptions.plugins ??= []; - const { pluginOptions, stylesheetBundler } = createCompilerPluginOptions( + const pluginOptions = createCompilerPluginOptions( options, - target, + sourceFileCache, ); buildOptions.plugins.push( @@ -228,6 +227,7 @@ export function createServerMainCodeBundleOptions( options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache, + stylesheetBundler: ComponentStylesheetBundler, ): BuildOptions { const { serverEntryPoint: mainServerEntryPoint, @@ -243,11 +243,7 @@ export function createServerMainCodeBundleOptions( 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.', ); - const { pluginOptions, stylesheetBundler } = createCompilerPluginOptions( - options, - target, - sourceFileCache, - ); + const pluginOptions = createCompilerPluginOptions(options, sourceFileCache); const mainServerNamespace = 'angular:main-server'; const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills'; @@ -374,6 +370,7 @@ export function createSsrEntryCodeBundleOptions( options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache, + stylesheetBundler: ComponentStylesheetBundler, ): BuildOptions { const { workspaceRoot, ssrOptions, externalPackages } = options; const serverEntryPoint = ssrOptions?.entry; @@ -382,11 +379,7 @@ export function createSsrEntryCodeBundleOptions( 'createSsrEntryCodeBundleOptions should not be called without a defined serverEntryPoint.', ); - const { pluginOptions, stylesheetBundler } = createCompilerPluginOptions( - options, - target, - sourceFileCache, - ); + const pluginOptions = createCompilerPluginOptions(options, sourceFileCache); const ssrEntryNamespace = 'angular:ssr-entry'; const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest'; diff --git a/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts b/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts index 10f285e795c9..5cc37c139e5f 100644 --- a/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts +++ b/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts @@ -9,6 +9,7 @@ import type { Message, PartialMessage } from 'esbuild'; import { normalize } from 'node:path'; import type { ChangedFiles } from '../../tools/esbuild/watcher'; +import type { ComponentStylesheetBundler } from './angular/component-stylesheets'; import type { SourceFileCache } from './angular/source-file-cache'; import type { BuildOutputFile, BuildOutputFileType, BundlerContext } from './bundler-context'; import { createOutputFile } from './utils'; @@ -20,6 +21,7 @@ export interface BuildOutputAsset { export interface RebuildState { rebuildContexts: BundlerContext[]; + componentStyleBundler: ComponentStylesheetBundler; codeBundleCache?: SourceFileCache; fileChanges: ChangedFiles; previousOutputHashes: Map; @@ -50,6 +52,7 @@ export class ExecutionResult { constructor( private rebuildContexts: BundlerContext[], + private componentStyleBundler: ComponentStylesheetBundler, private codeBundleCache?: SourceFileCache, ) {} @@ -158,6 +161,7 @@ export class ExecutionResult { return { rebuildContexts: this.rebuildContexts, codeBundleCache: this.codeBundleCache, + componentStyleBundler: this.componentStyleBundler, fileChanges, previousOutputHashes: new Map(this.outputFiles.map((file) => [file.path, file.hash])), }; @@ -177,5 +181,6 @@ export class ExecutionResult { async dispose(): Promise { await Promise.allSettled(this.rebuildContexts.map((context) => context.dispose())); + await this.componentStyleBundler.dispose(); } } diff --git a/packages/angular/build/src/tools/esbuild/compiler-plugin-options.ts b/packages/angular/build/src/tools/esbuild/compiler-plugin-options.ts index 568e6f8e0b85..192a7a142acf 100644 --- a/packages/angular/build/src/tools/esbuild/compiler-plugin-options.ts +++ b/packages/angular/build/src/tools/esbuild/compiler-plugin-options.ts @@ -15,74 +15,30 @@ type CreateCompilerPluginParameters = Parameters; export function createCompilerPluginOptions( options: NormalizedApplicationBuildOptions, - target: string[], sourceFileCache?: SourceFileCache, -): { - pluginOptions: CreateCompilerPluginParameters[0]; - stylesheetBundler: ComponentStylesheetBundler; -} { +): CreateCompilerPluginParameters[0] { const { - workspaceRoot, - optimizationOptions, sourcemapOptions, tsconfig, - outputNames, fileReplacements, - externalDependencies, - preserveSymlinks, - stylePreprocessorOptions, advancedOptimizations, - inlineStyleLanguage, jit, - cacheOptions, - tailwindConfiguration, - postcssConfiguration, - publicPath, externalRuntimeStyles, instrumentForCoverage, } = options; const incremental = !!options.watch; return { - // JS/TS options - pluginOptions: { - sourcemap: !!sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true), - thirdPartySourcemaps: sourcemapOptions.vendor, - tsconfig, - jit, - advancedOptimizations, - fileReplacements, - sourceFileCache, - loadResultCache: sourceFileCache?.loadResultCache, - incremental, - externalRuntimeStyles, - instrumentForCoverage, - }, - stylesheetBundler: new ComponentStylesheetBundler( - { - workspaceRoot, - inlineFonts: !!optimizationOptions.fonts.inline, - optimization: !!optimizationOptions.styles.minify, - sourcemap: - // Hidden component stylesheet sourcemaps are inaccessible which is effectively - // the same as being disabled. Disabling has the advantage of avoiding the overhead - // of sourcemap processing. - sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false, - outputNames, - includePaths: stylePreprocessorOptions?.includePaths, - // string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - sass: stylePreprocessorOptions?.sass as any, - externalDependencies, - target, - preserveSymlinks, - tailwindConfiguration, - postcssConfiguration, - cacheOptions, - publicPath, - }, - inlineStyleLanguage, - incremental, - ), + sourcemap: !!sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true), + thirdPartySourcemaps: sourcemapOptions.vendor, + tsconfig, + jit, + advancedOptimizations, + fileReplacements, + sourceFileCache, + loadResultCache: sourceFileCache?.loadResultCache, + incremental, + externalRuntimeStyles, + instrumentForCoverage, }; } diff --git a/packages/angular/build/src/utils/server-rendering/manifest.ts b/packages/angular/build/src/utils/server-rendering/manifest.ts index b75af9435c34..c2c295d74a7a 100644 --- a/packages/angular/build/src/utils/server-rendering/manifest.ts +++ b/packages/angular/build/src/utils/server-rendering/manifest.ts @@ -13,7 +13,7 @@ import { getLocaleBaseHref, } from '../../builders/application/options'; import type { BuildOutputFile } from '../../tools/esbuild/bundler-context'; -import { PrerenderedRoutesRecord } from '../../tools/esbuild/bundler-execution-result'; +import type { PrerenderedRoutesRecord } from '../../tools/esbuild/bundler-execution-result'; export const SERVER_APP_MANIFEST_FILENAME = 'angular-app-manifest.mjs'; export const SERVER_APP_ENGINE_MANIFEST_FILENAME = 'angular-app-engine-manifest.mjs';