Skip to content

Commit f410835

Browse files
clydinalan-agius4
authored andcommitted
refactor(@angular/build): integrate template update function generation with builder results
When using the development server with the application builder (default for new projects), experimental support for component template hot replacement is now available for use. To enable support, the `NG_HMR_TEMPLATES=1` environment variable must present when executing the development server (for example, `NG_HMR_TEMPLATES=1 ng serve`). Once support has become stable, template hot replacement will be enabled by default. (cherry picked from commit ae9dfdd)
1 parent 65d3257 commit f410835

File tree

8 files changed

+60
-10
lines changed

8 files changed

+60
-10
lines changed

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { logMessages, withNoProgress, withSpinner } from '../../tools/esbuild/ut
1616
import { shouldWatchRoot } from '../../utils/environment-options';
1717
import { NormalizedCachedOptions } from '../../utils/normalize-cache';
1818
import { NormalizedApplicationBuildOptions, NormalizedOutputOptions } from './options';
19-
import { FullResult, Result, ResultKind, ResultMessage } from './results';
19+
import { ComponentUpdateResult, FullResult, Result, ResultKind, ResultMessage } from './results';
2020

2121
// Watch workspace for package manager changes
2222
const packageWatchFiles = [
@@ -207,6 +207,7 @@ async function emitOutputResult(
207207
externalMetadata,
208208
htmlIndexPath,
209209
htmlBaseHref,
210+
templateUpdates,
210211
}: ExecutionResult,
211212
outputOptions: NormalizedApplicationBuildOptions['outputOptions'],
212213
): Promise<Result> {
@@ -221,6 +222,20 @@ async function emitOutputResult(
221222
};
222223
}
223224

225+
// Template updates only exist if no other changes have occurred
226+
if (templateUpdates?.size) {
227+
const updateResult: ComponentUpdateResult = {
228+
kind: ResultKind.ComponentUpdate,
229+
updates: Array.from(templateUpdates).map(([id, content]) => ({
230+
type: 'template',
231+
id,
232+
content,
233+
})),
234+
};
235+
236+
return updateResult;
237+
}
238+
224239
const result: FullResult = {
225240
kind: ResultKind.Full,
226241
warnings: warnings as ResultMessage[],

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

+16-3
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export async function executeBuild(
4949
i18nOptions,
5050
optimizationOptions,
5151
assets,
52-
outputMode,
5352
cacheOptions,
5453
serverEntryPoint,
5554
baseHref,
@@ -73,10 +72,15 @@ export async function executeBuild(
7372
let componentStyleBundler;
7473
let codeBundleCache;
7574
let bundlingResult: BundleContextResult;
75+
let templateUpdates: Map<string, string> | undefined;
7676
if (rebuildState) {
7777
bundlerContexts = rebuildState.rebuildContexts;
7878
componentStyleBundler = rebuildState.componentStyleBundler;
7979
codeBundleCache = rebuildState.codeBundleCache;
80+
templateUpdates = rebuildState.templateUpdates;
81+
// Reset template updates for new rebuild
82+
templateUpdates?.clear();
83+
8084
const allFileChanges = rebuildState.fileChanges.all;
8185

8286
// Bundle all contexts that do not require TypeScript changed file checks.
@@ -85,7 +89,6 @@ export async function executeBuild(
8589

8690
// Check the TypeScript code bundling cache for changes. If invalid, force a rebundle of
8791
// all TypeScript related contexts.
88-
// TODO: Enable cached bundling for the typescript contexts
8992
const forceTypeScriptRebuild = codeBundleCache?.invalidate(allFileChanges);
9093
const typescriptResults: BundleContextResult[] = [];
9194
for (const typescriptContext of bundlerContexts.typescriptContexts) {
@@ -98,7 +101,16 @@ export async function executeBuild(
98101
const target = transformSupportedBrowsersToTargets(browsers);
99102
codeBundleCache = new SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined);
100103
componentStyleBundler = createComponentStyleBundler(options, target);
101-
bundlerContexts = setupBundlerContexts(options, target, codeBundleCache, componentStyleBundler);
104+
if (options.templateUpdates) {
105+
templateUpdates = new Map<string, string>();
106+
}
107+
bundlerContexts = setupBundlerContexts(
108+
options,
109+
target,
110+
codeBundleCache,
111+
componentStyleBundler,
112+
templateUpdates,
113+
);
102114

103115
// Bundle everything on initial build
104116
bundlingResult = await BundlerContext.bundleAll([
@@ -129,6 +141,7 @@ export async function executeBuild(
129141
bundlerContexts,
130142
componentStyleBundler,
131143
codeBundleCache,
144+
templateUpdates,
132145
);
133146
executionResult.addWarnings(bundlingResult.warnings);
134147

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export function setupBundlerContexts(
3434
target: string[],
3535
codeBundleCache: SourceFileCache,
3636
stylesheetBundler: ComponentStylesheetBundler,
37+
templateUpdates: Map<string, string> | undefined,
3738
): {
3839
typescriptContexts: BundlerContext[];
3940
otherContexts: BundlerContext[];
@@ -55,7 +56,13 @@ export function setupBundlerContexts(
5556
new BundlerContext(
5657
workspaceRoot,
5758
watch,
58-
createBrowserCodeBundleOptions(options, target, codeBundleCache, stylesheetBundler),
59+
createBrowserCodeBundleOptions(
60+
options,
61+
target,
62+
codeBundleCache,
63+
stylesheetBundler,
64+
templateUpdates,
65+
),
5966
),
6067
);
6168

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export interface CompilerPluginOptions {
5151
incremental: boolean;
5252
externalRuntimeStyles?: boolean;
5353
instrumentForCoverage?: (request: string) => boolean;
54-
templateUpdates?: boolean;
54+
templateUpdates?: Map<string, string>;
5555
}
5656

5757
// eslint-disable-next-line max-lines-per-function
@@ -303,6 +303,12 @@ export function createCompilerPlugin(
303303
!!initializationResult.compilerOptions.inlineSourceMap;
304304
referencedFiles = initializationResult.referencedFiles;
305305
externalStylesheets = initializationResult.externalStylesheets;
306+
if (initializationResult.templateUpdates) {
307+
// Propagate any template updates
308+
initializationResult.templateUpdates.forEach((value, key) =>
309+
pluginOptions.templateUpdates?.set(key, value),
310+
);
311+
}
306312
} catch (error) {
307313
(result.errors ??= []).push({
308314
text: 'Angular compilation initialization failed.',
@@ -657,7 +663,7 @@ function createCompilerOptionsTransformer(
657663
sourceRoot: undefined,
658664
preserveSymlinks,
659665
externalRuntimeStyles: pluginOptions.externalRuntimeStyles,
660-
_enableHmr: pluginOptions.templateUpdates,
666+
_enableHmr: !!pluginOptions.templateUpdates,
661667
};
662668
};
663669
}

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,17 @@ export function createBrowserCodeBundleOptions(
3838
target: string[],
3939
sourceFileCache: SourceFileCache,
4040
stylesheetBundler: ComponentStylesheetBundler,
41+
templateUpdates: Map<string, string> | undefined,
4142
): BundlerOptionsFactory {
4243
return (loadCache) => {
4344
const { entryPoints, outputNames, polyfills } = options;
4445

45-
const pluginOptions = createCompilerPluginOptions(options, sourceFileCache, loadCache);
46+
const pluginOptions = createCompilerPluginOptions(
47+
options,
48+
sourceFileCache,
49+
loadCache,
50+
templateUpdates,
51+
);
4652

4753
const zoneless = isZonelessApp(polyfills);
4854

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

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface RebuildState {
2828
codeBundleCache?: SourceFileCache;
2929
fileChanges: ChangedFiles;
3030
previousOutputHashes: Map<string, string>;
31+
templateUpdates?: Map<string, string>;
3132
}
3233

3334
export interface ExternalResultMetadata {
@@ -60,6 +61,7 @@ export class ExecutionResult {
6061
},
6162
private componentStyleBundler: ComponentStylesheetBundler,
6263
private codeBundleCache?: SourceFileCache,
64+
readonly templateUpdates?: Map<string, string>,
6365
) {}
6466

6567
addOutputFile(path: string, content: string | Uint8Array, type: BuildOutputFileType): void {
@@ -166,6 +168,7 @@ export class ExecutionResult {
166168
componentStyleBundler: this.componentStyleBundler,
167169
fileChanges,
168170
previousOutputHashes: new Map(this.outputFiles.map((file) => [file.path, file.hash])),
171+
templateUpdates: this.templateUpdates,
169172
};
170173
}
171174

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function createCompilerPluginOptions(
1717
options: NormalizedApplicationBuildOptions,
1818
sourceFileCache: SourceFileCache,
1919
loadResultCache?: LoadResultCache,
20+
templateUpdates?: Map<string, string>,
2021
): CreateCompilerPluginParameters[0] {
2122
const {
2223
sourcemapOptions,
@@ -26,7 +27,6 @@ export function createCompilerPluginOptions(
2627
jit,
2728
externalRuntimeStyles,
2829
instrumentForCoverage,
29-
templateUpdates,
3030
} = options;
3131
const incremental = !!options.watch;
3232

packages/angular/build/src/tools/vite/middlewares/component-middleware.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function createAngularComponentMiddleware(
3333
return;
3434
}
3535

36-
const updateCode = templateUpdates.get(componentId) ?? '';
36+
const updateCode = templateUpdates.get(encodeURIComponent(componentId)) ?? '';
3737

3838
res.setHeader('Content-Type', 'text/javascript');
3939
res.setHeader('Cache-Control', 'no-cache');

0 commit comments

Comments
 (0)