Skip to content

Commit 446fd94

Browse files
committed
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.
1 parent 7fa971d commit 446fd94

File tree

8 files changed

+116
-95
lines changed

8 files changed

+116
-95
lines changed

goldens/circular-deps/packages.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
"packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts",
44
"packages/angular_devkit/build_angular/src/builders/dev-server/options.ts"
55
],
6+
[
7+
"packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts",
8+
"packages/angular/build/src/tools/esbuild/bundler-context.ts",
9+
"packages/angular/build/src/tools/esbuild/utils.ts",
10+
"packages/angular/build/src/utils/server-rendering/manifest.ts",
11+
"packages/angular/build/src/tools/esbuild/bundler-execution-result.ts"
12+
],
613
[
714
"packages/angular/build/src/tools/esbuild/bundler-context.ts",
815
"packages/angular/build/src/tools/esbuild/utils.ts"

packages/angular/build/src/builders/application/execute-build.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execu
1515
import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker';
1616
import { extractLicenses } from '../../tools/esbuild/license-extractor';
1717
import { profileAsync } from '../../tools/esbuild/profiling';
18-
import { calculateEstimatedTransferSizes, logBuildStats } from '../../tools/esbuild/utils';
18+
import {
19+
calculateEstimatedTransferSizes,
20+
logBuildStats,
21+
transformSupportedBrowsersToTargets,
22+
} from '../../tools/esbuild/utils';
1923
import { BudgetCalculatorResult, checkBudgets } from '../../utils/bundle-calculator';
2024
import { shouldOptimizeChunks } from '../../utils/environment-options';
2125
import { resolveAssets } from '../../utils/resolve-assets';
@@ -29,7 +33,7 @@ import { executePostBundleSteps } from './execute-post-bundle';
2933
import { inlineI18n, loadActiveTranslations } from './i18n';
3034
import { NormalizedApplicationBuildOptions } from './options';
3135
import { OutputMode } from './schema';
32-
import { setupBundlerContexts } from './setup-bundling';
36+
import { createComponentStyleBundler, setupBundlerContexts } from './setup-bundling';
3337

3438
// eslint-disable-next-line max-lines-per-function
3539
export async function executeBuild(
@@ -63,12 +67,18 @@ export async function executeBuild(
6367
}
6468

6569
// Reuse rebuild state or create new bundle contexts for code and global stylesheets
66-
let bundlerContexts = rebuildState?.rebuildContexts;
67-
const codeBundleCache =
68-
rebuildState?.codeBundleCache ??
69-
new SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined);
70-
if (bundlerContexts === undefined) {
71-
bundlerContexts = setupBundlerContexts(options, browsers, codeBundleCache);
70+
let bundlerContexts;
71+
let componentStyleBundler;
72+
let codeBundleCache;
73+
if (rebuildState) {
74+
bundlerContexts = rebuildState.rebuildContexts;
75+
componentStyleBundler = rebuildState.componentStyleBundler;
76+
codeBundleCache = rebuildState.codeBundleCache;
77+
} else {
78+
const target = transformSupportedBrowsersToTargets(browsers);
79+
codeBundleCache = new SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined);
80+
componentStyleBundler = createComponentStyleBundler(options, target);
81+
bundlerContexts = setupBundlerContexts(options, target, codeBundleCache, componentStyleBundler);
7282
}
7383

7484
let bundlingResult = await BundlerContext.bundleAll(
@@ -85,7 +95,11 @@ export async function executeBuild(
8595
);
8696
}
8797

88-
const executionResult = new ExecutionResult(bundlerContexts, codeBundleCache);
98+
const executionResult = new ExecutionResult(
99+
bundlerContexts,
100+
componentStyleBundler,
101+
codeBundleCache,
102+
);
89103
executionResult.addWarnings(bundlingResult.warnings);
90104

91105
// Return if the bundling has errors

packages/angular/build/src/builders/application/setup-bundling.ts

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9+
import { ComponentStylesheetBundler } from '../../tools/esbuild/angular/component-stylesheets';
910
import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
1011
import {
1112
createBrowserCodeBundleOptions,
@@ -17,10 +18,7 @@ import {
1718
import { BundlerContext } from '../../tools/esbuild/bundler-context';
1819
import { createGlobalScriptsBundleOptions } from '../../tools/esbuild/global-scripts';
1920
import { createGlobalStylesBundleOptions } from '../../tools/esbuild/global-styles';
20-
import {
21-
getSupportedNodeTargets,
22-
transformSupportedBrowsersToTargets,
23-
} from '../../tools/esbuild/utils';
21+
import { getSupportedNodeTargets } from '../../tools/esbuild/utils';
2422
import { NormalizedApplicationBuildOptions } from './options';
2523

2624
/**
@@ -33,8 +31,9 @@ import { NormalizedApplicationBuildOptions } from './options';
3331
*/
3432
export function setupBundlerContexts(
3533
options: NormalizedApplicationBuildOptions,
36-
browsers: string[],
34+
target: string[],
3735
codeBundleCache: SourceFileCache,
36+
stylesheetBundler: ComponentStylesheetBundler,
3837
): BundlerContext[] {
3938
const {
4039
outputMode,
@@ -45,15 +44,14 @@ export function setupBundlerContexts(
4544
workspaceRoot,
4645
watch = false,
4746
} = options;
48-
const target = transformSupportedBrowsersToTargets(browsers);
4947
const bundlerContexts = [];
5048

5149
// Browser application code
5250
bundlerContexts.push(
5351
new BundlerContext(
5452
workspaceRoot,
5553
watch,
56-
createBrowserCodeBundleOptions(options, target, codeBundleCache),
54+
createBrowserCodeBundleOptions(options, target, codeBundleCache, stylesheetBundler),
5755
),
5856
);
5957

@@ -62,6 +60,7 @@ export function setupBundlerContexts(
6260
options,
6361
target,
6462
codeBundleCache,
63+
stylesheetBundler,
6564
);
6665
if (browserPolyfillBundleOptions) {
6766
bundlerContexts.push(new BundlerContext(workspaceRoot, watch, browserPolyfillBundleOptions));
@@ -99,7 +98,7 @@ export function setupBundlerContexts(
9998
new BundlerContext(
10099
workspaceRoot,
101100
watch,
102-
createServerMainCodeBundleOptions(options, nodeTargets, codeBundleCache),
101+
createServerMainCodeBundleOptions(options, nodeTargets, codeBundleCache, stylesheetBundler),
103102
),
104103
);
105104

@@ -109,7 +108,7 @@ export function setupBundlerContexts(
109108
new BundlerContext(
110109
workspaceRoot,
111110
watch,
112-
createSsrEntryCodeBundleOptions(options, nodeTargets, codeBundleCache),
111+
createSsrEntryCodeBundleOptions(options, nodeTargets, codeBundleCache, stylesheetBundler),
113112
),
114113
);
115114
}
@@ -128,3 +127,51 @@ export function setupBundlerContexts(
128127

129128
return bundlerContexts;
130129
}
130+
131+
export function createComponentStyleBundler(
132+
options: NormalizedApplicationBuildOptions,
133+
target: string[],
134+
): ComponentStylesheetBundler {
135+
const {
136+
workspaceRoot,
137+
optimizationOptions,
138+
sourcemapOptions,
139+
outputNames,
140+
externalDependencies,
141+
preserveSymlinks,
142+
stylePreprocessorOptions,
143+
inlineStyleLanguage,
144+
cacheOptions,
145+
tailwindConfiguration,
146+
postcssConfiguration,
147+
publicPath,
148+
} = options;
149+
const incremental = !!options.watch;
150+
151+
return new ComponentStylesheetBundler(
152+
{
153+
workspaceRoot,
154+
inlineFonts: !!optimizationOptions.fonts.inline,
155+
optimization: !!optimizationOptions.styles.minify,
156+
sourcemap:
157+
// Hidden component stylesheet sourcemaps are inaccessible which is effectively
158+
// the same as being disabled. Disabling has the advantage of avoiding the overhead
159+
// of sourcemap processing.
160+
sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false,
161+
outputNames,
162+
includePaths: stylePreprocessorOptions?.includePaths,
163+
// string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'.
164+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
165+
sass: stylePreprocessorOptions?.sass as any,
166+
externalDependencies,
167+
target,
168+
preserveSymlinks,
169+
tailwindConfiguration,
170+
postcssConfiguration,
171+
cacheOptions,
172+
publicPath,
173+
},
174+
inlineStyleLanguage,
175+
incremental,
176+
);
177+
}

packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,6 @@ export function createCompilerPlugin(
532532

533533
build.onDispose(() => {
534534
sharedTSCompilationState?.dispose();
535-
void stylesheetBundler.dispose();
536535
void compilation.close?.();
537536
void cacheStore?.close();
538537
});

packages/angular/build/src/tools/esbuild/application-code-bundle.ts

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
SERVER_APP_MANIFEST_FILENAME,
1919
} from '../../utils/server-rendering/manifest';
2020
import { createCompilerPlugin } from './angular/compiler-plugin';
21+
import { ComponentStylesheetBundler } from './angular/component-stylesheets';
2122
import { SourceFileCache } from './angular/source-file-cache';
2223
import { BundlerOptionsFactory } from './bundler-context';
2324
import { createCompilerPluginOptions } from './compiler-plugin-options';
@@ -34,15 +35,12 @@ import { createWasmPlugin } from './wasm-plugin';
3435
export function createBrowserCodeBundleOptions(
3536
options: NormalizedApplicationBuildOptions,
3637
target: string[],
37-
sourceFileCache?: SourceFileCache,
38+
sourceFileCache: SourceFileCache,
39+
stylesheetBundler: ComponentStylesheetBundler,
3840
): BuildOptions {
3941
const { entryPoints, outputNames, polyfills } = options;
4042

41-
const { pluginOptions, stylesheetBundler } = createCompilerPluginOptions(
42-
options,
43-
target,
44-
sourceFileCache,
45-
);
43+
const pluginOptions = createCompilerPluginOptions(options, sourceFileCache);
4644

4745
const zoneless = isZonelessApp(polyfills);
4846

@@ -100,7 +98,8 @@ export function createBrowserCodeBundleOptions(
10098
export function createBrowserPolyfillBundleOptions(
10199
options: NormalizedApplicationBuildOptions,
102100
target: string[],
103-
sourceFileCache?: SourceFileCache,
101+
sourceFileCache: SourceFileCache,
102+
stylesheetBundler: ComponentStylesheetBundler,
104103
): BuildOptions | BundlerOptionsFactory | undefined {
105104
const namespace = 'angular:polyfills';
106105
const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions(
@@ -134,9 +133,9 @@ export function createBrowserPolyfillBundleOptions(
134133
// Only add the Angular TypeScript compiler if TypeScript files are provided in the polyfills
135134
if (hasTypeScriptEntries) {
136135
buildOptions.plugins ??= [];
137-
const { pluginOptions, stylesheetBundler } = createCompilerPluginOptions(
136+
const pluginOptions = createCompilerPluginOptions(
138137
options,
139-
target,
138+
140139
sourceFileCache,
141140
);
142141
buildOptions.plugins.push(
@@ -228,6 +227,7 @@ export function createServerMainCodeBundleOptions(
228227
options: NormalizedApplicationBuildOptions,
229228
target: string[],
230229
sourceFileCache: SourceFileCache,
230+
stylesheetBundler: ComponentStylesheetBundler,
231231
): BuildOptions {
232232
const {
233233
serverEntryPoint: mainServerEntryPoint,
@@ -243,11 +243,7 @@ export function createServerMainCodeBundleOptions(
243243
'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.',
244244
);
245245

246-
const { pluginOptions, stylesheetBundler } = createCompilerPluginOptions(
247-
options,
248-
target,
249-
sourceFileCache,
250-
);
246+
const pluginOptions = createCompilerPluginOptions(options, sourceFileCache);
251247

252248
const mainServerNamespace = 'angular:main-server';
253249
const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills';
@@ -374,6 +370,7 @@ export function createSsrEntryCodeBundleOptions(
374370
options: NormalizedApplicationBuildOptions,
375371
target: string[],
376372
sourceFileCache: SourceFileCache,
373+
stylesheetBundler: ComponentStylesheetBundler,
377374
): BuildOptions {
378375
const { workspaceRoot, ssrOptions, externalPackages } = options;
379376
const serverEntryPoint = ssrOptions?.entry;
@@ -382,11 +379,7 @@ export function createSsrEntryCodeBundleOptions(
382379
'createSsrEntryCodeBundleOptions should not be called without a defined serverEntryPoint.',
383380
);
384381

385-
const { pluginOptions, stylesheetBundler } = createCompilerPluginOptions(
386-
options,
387-
target,
388-
sourceFileCache,
389-
);
382+
const pluginOptions = createCompilerPluginOptions(options, sourceFileCache);
390383

391384
const ssrEntryNamespace = 'angular:ssr-entry';
392385
const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';

packages/angular/build/src/tools/esbuild/bundler-execution-result.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import type { Message, PartialMessage } from 'esbuild';
1010
import { normalize } from 'node:path';
1111
import type { ChangedFiles } from '../../tools/esbuild/watcher';
12+
import type { ComponentStylesheetBundler } from './angular/component-stylesheets';
1213
import type { SourceFileCache } from './angular/source-file-cache';
1314
import type { BuildOutputFile, BuildOutputFileType, BundlerContext } from './bundler-context';
1415
import { createOutputFile } from './utils';
@@ -20,6 +21,7 @@ export interface BuildOutputAsset {
2021

2122
export interface RebuildState {
2223
rebuildContexts: BundlerContext[];
24+
componentStyleBundler: ComponentStylesheetBundler;
2325
codeBundleCache?: SourceFileCache;
2426
fileChanges: ChangedFiles;
2527
previousOutputHashes: Map<string, string>;
@@ -50,6 +52,7 @@ export class ExecutionResult {
5052

5153
constructor(
5254
private rebuildContexts: BundlerContext[],
55+
private componentStyleBundler: ComponentStylesheetBundler,
5356
private codeBundleCache?: SourceFileCache,
5457
) {}
5558

@@ -158,6 +161,7 @@ export class ExecutionResult {
158161
return {
159162
rebuildContexts: this.rebuildContexts,
160163
codeBundleCache: this.codeBundleCache,
164+
componentStyleBundler: this.componentStyleBundler,
161165
fileChanges,
162166
previousOutputHashes: new Map(this.outputFiles.map((file) => [file.path, file.hash])),
163167
};
@@ -177,5 +181,6 @@ export class ExecutionResult {
177181

178182
async dispose(): Promise<void> {
179183
await Promise.allSettled(this.rebuildContexts.map((context) => context.dispose()));
184+
await this.componentStyleBundler.dispose();
180185
}
181186
}

0 commit comments

Comments
 (0)