Skip to content

Commit

Permalink
refactor(@angular/build): support external runtime styles for inline …
Browse files Browse the repository at this point in the history
…component styles

The build system will now transform inline styles into a corresponding external runtime
style with a URL for the Angular AOT compiler when the development server has enabled
component HMR for styles. This allows both file-based and inline component styles to
be eligible for component style HMR. The inline styles are provided to the development
server in an equivalent form to the file-based styles which the Angular runtime will
request via `link` elements during development. A unique identifier is produced for
each inline style that combines the containing file and order of the style within the
containing file to represent the location of the style. This provides an equivalent
unique identifier to the full path used by file-based styles.
  • Loading branch information
clydin committed Sep 24, 2024
1 parent 9502d46 commit 1548827
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 5 deletions.
4 changes: 4 additions & 0 deletions packages/angular/build/src/tools/angular/angular-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface AngularHostOptions {
data: string,
containingFile: string,
stylesheetFile?: string,
order?: number,
): Promise<string | null>;
processWebWorker(workerFile: string, containingFile: string): string;
}
Expand Down Expand Up @@ -196,6 +197,9 @@ export function createAngularCompilerHost(
data,
context.containingFile,
context.resourceFile ?? undefined,
// TODO: Remove once available in compiler-cli types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(context as any).order,
);

return typeof result === 'string' ? { content: result } : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
PluginBuild,
} from 'esbuild';
import assert from 'node:assert';
import { createHash } from 'node:crypto';
import * as path from 'node:path';
import { maxWorkers, useTypeChecking } from '../../../utils/environment-options';
import { AngularHostOptions } from '../../angular/angular-host';
Expand Down Expand Up @@ -178,7 +179,7 @@ export function createCompilerPlugin(
fileReplacements: pluginOptions.fileReplacements,
modifiedFiles,
sourceFileCache: pluginOptions.sourceFileCache,
async transformStylesheet(data, containingFile, stylesheetFile) {
async transformStylesheet(data, containingFile, stylesheetFile, order) {
let stylesheetResult;

// Stylesheet file only exists for external stylesheets
Expand All @@ -190,6 +191,16 @@ export function createCompilerPlugin(
containingFile,
// Inline stylesheets from a template style element are always CSS
containingFile.endsWith('.html') ? 'css' : styleOptions.inlineStyleLanguage,
// When external runtime styles are enabled, an identifier for the style that does not change
// based on the content is required to avoid emitted JS code changes. Any JS code changes will
// invalid the output and force a full page reload for HMR cases. The containing file and order
// of the style within the containing file is used.
pluginOptions.externalRuntimeStyles
? createHash('sha-256')
.update(containingFile)
.update((order ?? 0).toString())
.digest('hex')
: undefined,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,14 @@ export class ComponentStylesheetBundler {
);
}

async bundleInline(data: string, filename: string, language: string) {
async bundleInline(data: string, filename: string, language: string, externalId?: string) {
// Use a hash of the inline stylesheet content to ensure a consistent identifier. External stylesheets will resolve
// to the actual stylesheet file path.
// TODO: Consider xxhash instead for hashing
const id = createHash('sha256').update(data).digest('hex');
const id = createHash('sha256')
.update(data)
.update(externalId ?? '')
.digest('hex');
const entry = [language, id, filename].join(';');

const bundlerContext = await this.#inlineContexts.getOrCreate(entry, () => {
Expand All @@ -77,7 +80,13 @@ export class ComponentStylesheetBundler {
const buildOptions = createStylesheetBundleOptions(this.options, loadCache, {
[entry]: data,
});
buildOptions.entryPoints = [`${namespace};${entry}`];
if (externalId) {
buildOptions.entryPoints = { [externalId]: `${namespace};${entry}` };
delete buildOptions.publicPath;
} else {
buildOptions.entryPoints = [`${namespace};${entry}`];
}

buildOptions.plugins.push({
name: 'angular-component-styles',
setup(build) {
Expand Down Expand Up @@ -106,7 +115,11 @@ export class ComponentStylesheetBundler {
});

// Extract the result of the bundling from the output files
return this.extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles, false);
return this.extractResult(
await bundlerContext.bundle(),
bundlerContext.watchFiles,
!!externalId,
);
}

/**
Expand Down

0 comments on commit 1548827

Please sign in to comment.