refactor(compiler): adjust HMR initializer block for improved Vite support (#58173)

For the HMR initializer block to support being used in a Vite setup with
import analysis, the import call expression needs to be a runtime generated
value and include the `@vite-ignore` special comment. Without the first,
Vite will error prior to loading the application. Without the second, a
warning will be shown for each import which is effectively each component
within the application when HMR is enabled.

PR Close #58173
This commit is contained in:
Charles Lyding 2024-10-11 16:42:12 -04:00 committed by Paul Gschwendtner
parent 4a6c6505d9
commit bbca205d5e
3 changed files with 15 additions and 12 deletions

View file

@ -32,7 +32,6 @@ export function extractHmrInitializerMeta(
const meta: R3HmrInitializerMetadata = {
type: new WrappedNodeExpr(clazz.name),
className: clazz.name.text,
timestamp: Date.now() + '',
filePath,
};

View file

@ -10798,8 +10798,9 @@ runInEachFileSystem((os: string) => {
/import\.meta\.hot && import\.meta\.hot\.on\("angular:component-update", d => { if \(d\.id == "test\.ts%40Cmp"\) {/,
);
expect(jsContents).toMatch(
/import\("\/@ng\/component\?c=test\.ts%40Cmp&t=\d+"\).then\(m => i0\.ɵɵreplaceMetadata\(Cmp, m\.default\)\);/,
/import\(\s*\/\* @vite-ignore \*\/\s+"\/@ng\/component\?c=test\.ts%40Cmp&t=" \+ encodeURIComponent\(d.timestamp\)/,
);
expect(jsContents).toMatch(/\).then\(m => i0\.ɵɵreplaceMetadata\(Cmp, m\.default\)\);/);
});
});

View file

@ -20,19 +20,12 @@ export interface R3HmrInitializerMetadata {
/** File path of the component class. */
filePath: string;
/**
* Timestamp when the compilation took place.
* Necessary to invalidate the browser cache.
*/
timestamp: string;
}
/** Compiles the HMR initializer expression. */
export function compileClassHmrInitializer(meta: R3HmrInitializerMetadata): o.Expression {
const id = encodeURIComponent(`${meta.filePath}@${meta.className}`);
const timestamp = encodeURIComponent(meta.timestamp);
const url = `/@ng/component?c=${id}&t=${timestamp}`;
const urlPartial = `/@ng/component?c=${id}&t=`;
const moduleName = 'm';
const dataName = 'd';
@ -44,8 +37,18 @@ export function compileClassHmrInitializer(meta: R3HmrInitializerMetadata): o.Ex
// (m) => ɵɵreplaceMetadata(...)
const replaceCallback = o.arrowFn([new o.FnParam(moduleName)], replaceMetadata);
// import(url).then(() => replaceMetadata(...));
const dynamicImport = new o.DynamicImportExpr(url).prop('then').callFn([replaceCallback]);
// '<urlPartial>' + encodeURIComponent(d.timestamp)
const urlValue = o
.literal(urlPartial)
.plus(o.variable('encodeURIComponent').callFn([o.variable(dataName).prop('timestamp')]));
// import(/* @vite-ignore */ url).then(() => replaceMetadata(...));
// The vite-ignore special comment is required to avoid Vite from generating a superfluous
// warning for each usage within the development code. If Vite provides a method to
// programmatically avoid this warning in the future, this added comment can be removed here.
const dynamicImport = new o.DynamicImportExpr(urlValue, null, '@vite-ignore')
.prop('then')
.callFn([replaceCallback]);
// (d) => { if (d.id === <id>) { replaceMetadata(...) } }
const listenerCallback = o.arrowFn(