Skip to content

Commit

Permalink
refactor(@angular/build): create component stylesheet bundler at star…
Browse files Browse the repository at this point in the history
…t 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.
  • Loading branch information
clydin committed Oct 17, 2024
1 parent ba05932 commit bdcaa8b
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 95 deletions.
7 changes: 7 additions & 0 deletions goldens/circular-deps/packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
32 changes: 23 additions & 9 deletions packages/angular/build/src/builders/application/execute-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down
65 changes: 56 additions & 9 deletions packages/angular/build/src/builders/application/setup-bundling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';

/**
Expand All @@ -33,8 +31,9 @@ import { NormalizedApplicationBuildOptions } from './options';
*/
export function setupBundlerContexts(
options: NormalizedApplicationBuildOptions,
browsers: string[],
target: string[],
codeBundleCache: SourceFileCache,
stylesheetBundler: ComponentStylesheetBundler,
): BundlerContext[] {
const {
outputMode,
Expand All @@ -45,15 +44,14 @@ export function setupBundlerContexts(
workspaceRoot,
watch = false,
} = options;
const target = transformSupportedBrowsersToTargets(browsers);
const bundlerContexts = [];

// Browser application code
bundlerContexts.push(
new BundlerContext(
workspaceRoot,
watch,
createBrowserCodeBundleOptions(options, target, codeBundleCache),
createBrowserCodeBundleOptions(options, target, codeBundleCache, stylesheetBundler),
),
);

Expand All @@ -62,6 +60,7 @@ export function setupBundlerContexts(
options,
target,
codeBundleCache,
stylesheetBundler,
);
if (browserPolyfillBundleOptions) {
bundlerContexts.push(new BundlerContext(workspaceRoot, watch, browserPolyfillBundleOptions));
Expand Down Expand Up @@ -99,7 +98,7 @@ export function setupBundlerContexts(
new BundlerContext(
workspaceRoot,
watch,
createServerMainCodeBundleOptions(options, nodeTargets, codeBundleCache),
createServerMainCodeBundleOptions(options, nodeTargets, codeBundleCache, stylesheetBundler),
),
);

Expand All @@ -109,7 +108,7 @@ export function setupBundlerContexts(
new BundlerContext(
workspaceRoot,
watch,
createSsrEntryCodeBundleOptions(options, nodeTargets, codeBundleCache),
createSsrEntryCodeBundleOptions(options, nodeTargets, codeBundleCache, stylesheetBundler),
),
);
}
Expand All @@ -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,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,6 @@ export function createCompilerPlugin(

build.onDispose(() => {
sharedTSCompilationState?.dispose();
void stylesheetBundler.dispose();
void compilation.close?.();
void cacheStore?.close();
});
Expand Down
31 changes: 12 additions & 19 deletions packages/angular/build/src/tools/esbuild/application-code-bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -228,6 +227,7 @@ export function createServerMainCodeBundleOptions(
options: NormalizedApplicationBuildOptions,
target: string[],
sourceFileCache: SourceFileCache,
stylesheetBundler: ComponentStylesheetBundler,
): BuildOptions {
const {
serverEntryPoint: mainServerEntryPoint,
Expand All @@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -20,6 +21,7 @@ export interface BuildOutputAsset {

export interface RebuildState {
rebuildContexts: BundlerContext[];
componentStyleBundler: ComponentStylesheetBundler;
codeBundleCache?: SourceFileCache;
fileChanges: ChangedFiles;
previousOutputHashes: Map<string, string>;
Expand Down Expand Up @@ -50,6 +52,7 @@ export class ExecutionResult {

constructor(
private rebuildContexts: BundlerContext[],
private componentStyleBundler: ComponentStylesheetBundler,
private codeBundleCache?: SourceFileCache,
) {}

Expand Down Expand Up @@ -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])),
};
Expand All @@ -177,5 +181,6 @@ export class ExecutionResult {

async dispose(): Promise<void> {
await Promise.allSettled(this.rebuildContexts.map((context) => context.dispose()));
await this.componentStyleBundler.dispose();
}
}
Loading

0 comments on commit bdcaa8b

Please sign in to comment.