Skip to content

Commit

Permalink
feat(@angular/build): enable component template hot replacement by de…
Browse files Browse the repository at this point in the history
…fault

When using the `application` builder (default for new projects) with the
development server, component template only changes will now automatically
replace the template within the running application without a full reload
of the page. No application code changes are necessary and both file-based
(`templateUrl`) and inline (`template`) component templates are supported.
Additionally, changing a components styles in combination with a template
change is also supported for hot replacement. This includes both inline and
file-based changes.

If any issues are encountered or it is preferred to not hot replace component
templates, the `NG_HMR_TEMPLATES=0` environment variable can be used to disable
the feature. Setting the `liveReload` option or `hmr` option to false will
also disable all updates.
  • Loading branch information
clydin authored and dgp1130 committed Dec 17, 2024
1 parent 3b7e6a8 commit 298b554
Show file tree
Hide file tree
Showing 7 changed files with 29 additions and 21 deletions.
13 changes: 5 additions & 8 deletions packages/angular/build/src/builders/dev-server/vite-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,14 @@ export async function* serveWithVite(
process.setSourceMapsEnabled(true);
}

// Enable to support component style hot reloading (`NG_HMR_CSTYLES=0` can be used to disable selectively)
// Enable to support link-based component style hot reloading (`NG_HMR_CSTYLES=0` can be used to disable selectively)
browserOptions.externalRuntimeStyles =
serverOptions.liveReload && serverOptions.hmr && useComponentStyleHmr;

// Enable to support component template hot replacement (`NG_HMR_TEMPLATE=1` can be used to enable)
browserOptions.templateUpdates = !!serverOptions.liveReload && useComponentTemplateHmr;
if (browserOptions.templateUpdates) {
context.logger.warn(
'Experimental support for component template hot replacement has been enabled via the "NG_HMR_TEMPLATE" environment variable.',
);
}
// Enable to support component template hot replacement (`NG_HMR_TEMPLATE=0` can be used to disable selectively)
// This will also replace file-based/inline styles as code if external runtime styles are not enabled.
browserOptions.templateUpdates =
serverOptions.liveReload && serverOptions.hmr && useComponentTemplateHmr;

// Setup the prebundling transformer that will be shared across Vite prebundling requests
const prebundleTransformer = new JavaScriptTransformer(
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/build/src/utils/environment-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const useComponentStyleHmr =

const hmrComponentTemplateVariable = process.env['NG_HMR_TEMPLATES'];
export const useComponentTemplateHmr =
isPresent(hmrComponentTemplateVariable) && isEnabled(hmrComponentTemplateVariable);
!isPresent(hmrComponentTemplateVariable) || !isDisabled(hmrComponentTemplateVariable);

const partialSsrBuildVariable = process.env['NG_BUILD_PARTIAL_SSR'];
export const usePartialSsrBuild =
Expand Down
9 changes: 2 additions & 7 deletions tests/legacy-cli/e2e/tests/basic/rebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ export default async function () {
const validBundleRegEx = esbuild ? /sent to client/ : /Compiled successfully\./;
const lazyBundleRegEx = esbuild ? /chunk-/ : /src_app_lazy_lazy_component_ts\.js/;

// Disable component stylesheet HMR to support page reload based rebuild testing.
// Ideally this environment variable would be passed directly to the new serve process
// but this would require signficant test changes due to the existing `ngServe` signature.
const oldHMRValue = process.env['NG_HMR_CSTYLES'];
process.env['NG_HMR_CSTYLES'] = '0';
const port = await ngServe();
process.env['NG_HMR_CSTYLES'] = oldHMRValue;
// Disable HMR to support page reload based rebuild testing.
const port = await ngServe('--no-hmr');

// Add a lazy route.
await silentNg('generate', 'component', 'lazy');
Expand Down
6 changes: 5 additions & 1 deletion tests/legacy-cli/e2e/tests/vite/ssr-entry-express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export default async function () {
'src/app/home/home.component.html',
'home works',
'yay home works!!!',
true,
);
await validateResponse('/api/test', /foo/);
await validateResponse('/home', /yay home works/);
Expand All @@ -111,9 +112,12 @@ async function modifyFileAndWaitUntilUpdated(
filePath: string,
searchValue: string,
replaceValue: string,
hmr = false,
): Promise<void> {
await Promise.all([
waitForAnyProcessOutputToMatch(/Page reload sent to client/),
waitForAnyProcessOutputToMatch(
hmr ? /Component update sent to client/ : /Page reload sent to client/,
),
setTimeout(100).then(() => replaceInFile(filePath, searchValue, replaceValue)),
]);
}
6 changes: 5 additions & 1 deletion tests/legacy-cli/e2e/tests/vite/ssr-entry-fastify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export default async function () {
'src/app/home/home.component.html',
'home works',
'yay home works!!!',
true,
);
await validateResponse('/api/test', /foo/);
await validateResponse('/home', /yay home works/);
Expand All @@ -111,9 +112,12 @@ async function modifyFileAndWaitUntilUpdated(
filePath: string,
searchValue: string,
replaceValue: string,
hmr = false,
): Promise<void> {
await Promise.all([
waitForAnyProcessOutputToMatch(/Page reload sent to client/),
waitForAnyProcessOutputToMatch(
hmr ? /Component update sent to client/ : /Page reload sent to client/,
),
setTimeout(100).then(() => replaceInFile(filePath, searchValue, replaceValue)),
]);
}
6 changes: 5 additions & 1 deletion tests/legacy-cli/e2e/tests/vite/ssr-entry-h3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export default async function () {
'src/app/home/home.component.html',
'home works',
'yay home works!!!',
true,
);
await validateResponse('/api/test', /foo/);
await validateResponse('/home', /yay home works/);
Expand All @@ -102,9 +103,12 @@ async function modifyFileAndWaitUntilUpdated(
filePath: string,
searchValue: string,
replaceValue: string,
hmr = false,
): Promise<void> {
await Promise.all([
waitForAnyProcessOutputToMatch(/Page reload sent to client/),
waitForAnyProcessOutputToMatch(
hmr ? /Component update sent to client/ : /Page reload sent to client/,
),
setTimeout(100).then(() => replaceInFile(filePath, searchValue, replaceValue)),
]);
}
8 changes: 6 additions & 2 deletions tests/legacy-cli/e2e/tests/vite/ssr-entry-hono.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { setTimeout } from 'node:timers/promises';
import { replaceInFile, writeMultipleFiles } from '../../utils/fs';
import { ng, silentNg, waitForAnyProcessOutputToMatch } from '../../utils/process';
import { installPackage, installWorkspacePackages, uninstallPackage } from '../../utils/packages';
import { ngServe, updateJsonFile, useSha } from '../../utils/project';
import { ngServe, useSha } from '../../utils/project';
import { getGlobalVariable } from '../../utils/env';

export default async function () {
Expand Down Expand Up @@ -73,6 +73,7 @@ export default async function () {
'src/app/home/home.component.html',
'home works',
'yay home works!!!',
true,
);
await validateResponse('/api/test', /foo/);
await validateResponse('/home', /yay home works/);
Expand All @@ -94,9 +95,12 @@ async function modifyFileAndWaitUntilUpdated(
filePath: string,
searchValue: string,
replaceValue: string,
hmr = false,
): Promise<void> {
await Promise.all([
waitForAnyProcessOutputToMatch(/Page reload sent to client/),
waitForAnyProcessOutputToMatch(
hmr ? /Component update sent to client/ : /Page reload sent to client/,
),
setTimeout(100).then(() => replaceInFile(filePath, searchValue, replaceValue)),
]);
}

0 comments on commit 298b554

Please sign in to comment.