refactor(compiler): pass more information to HMR replacement function (#59854)

Adjusts the code we generate for HMR so that it passes in the HMR ID and `import.meta` to the `replaceMetadata` call. This is necessary so we can do better logging of errors.

PR Close #59854
This commit is contained in:
Kristiyan Kostadinov
2025-02-05 04:55:15 -08:00
committed by Andrew Scott
parent 27c39f5a8a
commit 6e999300a7
4 changed files with 55 additions and 27 deletions
+34 -20
View File
@@ -104,18 +104,19 @@ runInEachFileSystem(() => {
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain(`import * as i0 from "@angular/core";`);
expect(jsContents).toContain('const id = "test.ts%40Cmp";');
expect(jsContents).toContain('function Cmp_HmrLoad(t) {');
expect(jsContents).toContain(
'import(/* @vite-ignore */\nnew URL("./@ng/component?c=test.ts%40Cmp&t=" + encodeURIComponent(t), import.meta.url).href)',
'import(/* @vite-ignore */\nnew URL("./@ng/component?c=" + id + "&t=" + encodeURIComponent(t), import.meta.url).href)',
);
expect(jsContents).toContain(
').then(m => m.default && i0.ɵɵreplaceMetadata(Cmp, m.default, [i0], ' +
'[Dep, transformValue, TOKEN, Component, Inject, ViewChild, Input]));',
'[Dep, transformValue, TOKEN, Component, Inject, ViewChild, Input], import.meta, id));',
);
expect(jsContents).toContain('Cmp_HmrLoad(Date.now());');
expect(jsContents).toContain(
'import.meta.hot && import.meta.hot.on("angular:component-update", ' +
'd => d.id === "test.ts%40Cmp" && Cmp_HmrLoad(d.timestamp)',
'd => d.id === id && Cmp_HmrLoad(d.timestamp)',
);
expect(hmrContents).toContain(
@@ -171,18 +172,19 @@ runInEachFileSystem(() => {
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain(`import * as i0 from "@angular/core";`);
expect(jsContents).toContain(`import * as i1 from "./dep";`);
expect(jsContents).toContain('const id = "test.ts%40Cmp";');
expect(jsContents).toContain('function Cmp_HmrLoad(t) {');
expect(jsContents).toContain(
'import(/* @vite-ignore */\nnew URL("./@ng/component?c=test.ts%40Cmp&t=" + encodeURIComponent(t), import.meta.url).href)',
'import(/* @vite-ignore */\nnew URL("./@ng/component?c=" + id + "&t=" + encodeURIComponent(t), import.meta.url).href)',
);
expect(jsContents).toContain(
').then(m => m.default && i0.ɵɵreplaceMetadata(Cmp, m.default, [i0, i1], ' +
'[DepModule, Component]));',
'[DepModule, Component], import.meta, id));',
);
expect(jsContents).toContain('Cmp_HmrLoad(Date.now());');
expect(jsContents).toContain(
'import.meta.hot && import.meta.hot.on("angular:component-update", ' +
'd => d.id === "test.ts%40Cmp" && Cmp_HmrLoad(d.timestamp)',
'd => d.id === id && Cmp_HmrLoad(d.timestamp)',
);
expect(hmrContents).toContain(
@@ -340,7 +342,9 @@ runInEachFileSystem(() => {
expect(jsContents).toContain('const Cmp_Defer_1_DepsFn = () => [Dep];');
expect(jsContents).toContain('function Cmp_Defer_0_Template(rf, ctx) { if (rf & 1) {');
expect(jsContents).toContain('i0.ɵɵdefer(1, 0, Cmp_Defer_1_DepsFn);');
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0], [Dep]));');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [Dep], import.meta, id));',
);
expect(jsContents).not.toContain('setClassMetadata');
expect(hmrContents).toContain(
@@ -422,7 +426,9 @@ runInEachFileSystem(() => {
const jsContents = env.getContents('test.js');
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain('dependencies: [Cmp]');
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0], [Component]));');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [Component], import.meta, id));',
);
expect(hmrContents).toContain(
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, Component) {',
);
@@ -445,7 +451,9 @@ runInEachFileSystem(() => {
const jsContents = env.getContents('test.js');
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).not.toContain('dependencies');
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0], [Component]));');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [Component], import.meta, id));',
);
expect(hmrContents).toContain(
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, Component) {',
);
@@ -471,7 +479,7 @@ runInEachFileSystem(() => {
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [providers, Component]));',
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [providers, Component], import.meta, id));',
);
expect(hmrContents).toContain(
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, providers, Component) {',
@@ -508,7 +516,7 @@ runInEachFileSystem(() => {
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Component]));',
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Component], import.meta, id));',
);
expect(hmrContents).toContain(
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, value, Component) {',
@@ -542,7 +550,7 @@ runInEachFileSystem(() => {
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Component]));',
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Component], import.meta, id));',
);
expect(hmrContents).toContain(
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, value, Component) {',
@@ -574,7 +582,7 @@ runInEachFileSystem(() => {
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [condition, providersA, providersB, Component]));',
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [condition, providersA, providersB, Component], import.meta, id));',
);
expect(hmrContents).toContain(
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, condition, providersA, providersB, Component) {',
@@ -608,7 +616,7 @@ runInEachFileSystem(() => {
const jsContents = env.getContents('test.js');
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, otherValue, Component]));',
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, otherValue, Component], import.meta, id));',
);
expect(jsContents).toContain('useFactory: () => [(value), ((((otherValue))))]');
expect(hmrContents).toContain(
@@ -646,7 +654,7 @@ runInEachFileSystem(() => {
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Optional, dep, Component]));',
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Optional, dep, Component], import.meta, id));',
);
expect(hmrContents).toContain(
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, value, Optional, dep, Component) {',
@@ -697,7 +705,9 @@ runInEachFileSystem(() => {
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain('dependencies: [Dep]');
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0], [Dep]));');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [Dep], import.meta, id));',
);
expect(hmrContents).toContain('function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, Dep) {');
});
@@ -741,7 +751,9 @@ runInEachFileSystem(() => {
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain('dependencies: [DepModule, i1.Dep]');
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0, i1], [DepModule]));');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0, i1], [DepModule], import.meta, id));',
);
expect(hmrContents).toContain('function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, DepModule) {');
});
@@ -797,7 +809,9 @@ runInEachFileSystem(() => {
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain('dependencies: [i1.Dep]');
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0, i1], []));');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0, i1], [], import.meta, id));',
);
expect(hmrContents).toContain('function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces) {');
});
@@ -834,7 +848,7 @@ runInEachFileSystem(() => {
const jsContents = env.getContents('test.js');
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, { one: 0, two: "2", three: 3 }, Component]));',
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, { one: 0, two: "2", three: 3 }, Component], import.meta, id));',
);
expect(hmrContents).toContain(
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, Foo, Component) {',
@@ -881,7 +895,7 @@ runInEachFileSystem(() => {
const jsContents = env.getContents('test.js');
const hmrContents = env.driveHmr('test.ts', 'Cmp');
expect(jsContents).toContain(
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, { one: 0, two: "2", three: 3 }, Component]));',
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, { one: 0, two: "2", three: 3 }, Component], import.meta, id));',
);
expect(hmrContents).toContain(
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, Foo, Component) {',
@@ -53,11 +53,10 @@ export interface R3HmrNamespaceDependency {
* @param meta HMR metadata extracted from the class.
*/
export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression {
const id = encodeURIComponent(`${meta.filePath}@${meta.className}`);
const urlPartial = `./@ng/component?c=${id}&t=`;
const moduleName = 'm';
const dataName = 'd';
const timestampName = 't';
const idName = 'id';
const importCallbackName = `${meta.className}_HmrLoad`;
const namespaces = meta.namespaceDependencies.map((dep) => {
return new o.ExternalExpr({moduleName: dep.moduleName, name: null});
@@ -66,7 +65,7 @@ export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression {
// m.default
const defaultRead = o.variable(moduleName).prop('default');
// ɵɵreplaceMetadata(Comp, m.default, [...namespaces], [...locals]);
// ɵɵreplaceMetadata(Comp, m.default, [...namespaces], [...locals], import.meta, id);
const replaceCall = o
.importExpr(R3.replaceMetadata)
.callFn([
@@ -74,14 +73,18 @@ export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression {
defaultRead,
o.literalArr(namespaces),
o.literalArr(meta.localDependencies.map((l) => l.runtimeRepresentation)),
o.variable('import').prop('meta'),
o.variable(idName),
]);
// (m) => m.default && ɵɵreplaceMetadata(...)
const replaceCallback = o.arrowFn([new o.FnParam(moduleName)], defaultRead.and(replaceCall));
// '<urlPartial>' + encodeURIComponent(t)
// '<url>?c=' + id + '&t=' + encodeURIComponent(t)
const urlValue = o
.literal(urlPartial)
.literal(`./@ng/component?c=`)
.plus(o.variable(idName))
.plus(o.literal('&t='))
.plus(o.variable('encodeURIComponent').callFn([o.variable(timestampName)]));
// import.meta.url
@@ -109,13 +112,13 @@ export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression {
o.StmtModifier.Final,
);
// (d) => d.id === <id> && Cmp_HmrLoad(d.timestamp)
// (d) => d.id === id && Cmp_HmrLoad(d.timestamp)
const updateCallback = o.arrowFn(
[new o.FnParam(dataName)],
o
.variable(dataName)
.prop('id')
.identical(o.literal(id))
.identical(o.variable(idName))
.and(o.variable(importCallbackName).callFn([o.variable(dataName).prop('timestamp')])),
);
@@ -139,6 +142,13 @@ export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression {
.arrowFn(
[],
[
// const id = <id>;
new o.DeclareVarStmt(
idName,
o.literal(encodeURIComponent(`${meta.filePath}@${meta.className}`)),
null,
o.StmtModifier.Final,
),
// function Cmp_HmrLoad() {...}.
importCallback,
// ngDevMode && Cmp_HmrLoad(Date.now());
+3
View File
@@ -51,6 +51,8 @@ import {NG_COMP_DEF} from './fields';
* @param applyMetadata Callback that will apply a new set of metadata on the `type` when invoked.
* @param environment Syntehtic namespace imports that need to be passed along to the callback.
* @param locals Local symbols from the source location that have to be exposed to the callback.
* @param id ID to the class being replaced. **Not** the same as the component definition ID.
* Optional since the ID might not be available internally.
* @codeGenApi
*/
export function ɵɵreplaceMetadata(
@@ -58,6 +60,7 @@ export function ɵɵreplaceMetadata(
applyMetadata: (...args: [Type<unknown>, unknown[], ...unknown[]]) => void,
namespaces: unknown[],
locals: unknown[],
id: string | null = null,
) {
ngDevMode && assertComponentDef(type);
const currentDef = getComponentDef(type)!;
@@ -2157,6 +2157,7 @@ describe('hot module replacement', () => {
},
[angularCoreEnv],
[],
'',
);
}