Skip to content

Commit 6e99930

Browse files
crisbetoatscott
authored andcommitted
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
1 parent 27c39f5 commit 6e99930

File tree

4 files changed

+55
-27
lines changed

4 files changed

+55
-27
lines changed

packages/compiler-cli/test/ngtsc/hmr_spec.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,19 @@ runInEachFileSystem(() => {
104104
const hmrContents = env.driveHmr('test.ts', 'Cmp');
105105

106106
expect(jsContents).toContain(`import * as i0 from "@angular/core";`);
107+
expect(jsContents).toContain('const id = "test.ts%40Cmp";');
107108
expect(jsContents).toContain('function Cmp_HmrLoad(t) {');
108109
expect(jsContents).toContain(
109-
'import(/* @vite-ignore */\nnew URL("./@ng/component?c=test.ts%40Cmp&t=" + encodeURIComponent(t), import.meta.url).href)',
110+
'import(/* @vite-ignore */\nnew URL("./@ng/component?c=" + id + "&t=" + encodeURIComponent(t), import.meta.url).href)',
110111
);
111112
expect(jsContents).toContain(
112113
').then(m => m.default && i0.ɵɵreplaceMetadata(Cmp, m.default, [i0], ' +
113-
'[Dep, transformValue, TOKEN, Component, Inject, ViewChild, Input]));',
114+
'[Dep, transformValue, TOKEN, Component, Inject, ViewChild, Input], import.meta, id));',
114115
);
115116
expect(jsContents).toContain('Cmp_HmrLoad(Date.now());');
116117
expect(jsContents).toContain(
117118
'import.meta.hot && import.meta.hot.on("angular:component-update", ' +
118-
'd => d.id === "test.ts%40Cmp" && Cmp_HmrLoad(d.timestamp)',
119+
'd => d.id === id && Cmp_HmrLoad(d.timestamp)',
119120
);
120121

121122
expect(hmrContents).toContain(
@@ -171,18 +172,19 @@ runInEachFileSystem(() => {
171172
const hmrContents = env.driveHmr('test.ts', 'Cmp');
172173
expect(jsContents).toContain(`import * as i0 from "@angular/core";`);
173174
expect(jsContents).toContain(`import * as i1 from "./dep";`);
175+
expect(jsContents).toContain('const id = "test.ts%40Cmp";');
174176
expect(jsContents).toContain('function Cmp_HmrLoad(t) {');
175177
expect(jsContents).toContain(
176-
'import(/* @vite-ignore */\nnew URL("./@ng/component?c=test.ts%40Cmp&t=" + encodeURIComponent(t), import.meta.url).href)',
178+
'import(/* @vite-ignore */\nnew URL("./@ng/component?c=" + id + "&t=" + encodeURIComponent(t), import.meta.url).href)',
177179
);
178180
expect(jsContents).toContain(
179181
').then(m => m.default && i0.ɵɵreplaceMetadata(Cmp, m.default, [i0, i1], ' +
180-
'[DepModule, Component]));',
182+
'[DepModule, Component], import.meta, id));',
181183
);
182184
expect(jsContents).toContain('Cmp_HmrLoad(Date.now());');
183185
expect(jsContents).toContain(
184186
'import.meta.hot && import.meta.hot.on("angular:component-update", ' +
185-
'd => d.id === "test.ts%40Cmp" && Cmp_HmrLoad(d.timestamp)',
187+
'd => d.id === id && Cmp_HmrLoad(d.timestamp)',
186188
);
187189

188190
expect(hmrContents).toContain(
@@ -340,7 +342,9 @@ runInEachFileSystem(() => {
340342
expect(jsContents).toContain('const Cmp_Defer_1_DepsFn = () => [Dep];');
341343
expect(jsContents).toContain('function Cmp_Defer_0_Template(rf, ctx) { if (rf & 1) {');
342344
expect(jsContents).toContain('i0.ɵɵdefer(1, 0, Cmp_Defer_1_DepsFn);');
343-
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0], [Dep]));');
345+
expect(jsContents).toContain(
346+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [Dep], import.meta, id));',
347+
);
344348
expect(jsContents).not.toContain('setClassMetadata');
345349

346350
expect(hmrContents).toContain(
@@ -422,7 +426,9 @@ runInEachFileSystem(() => {
422426
const jsContents = env.getContents('test.js');
423427
const hmrContents = env.driveHmr('test.ts', 'Cmp');
424428
expect(jsContents).toContain('dependencies: [Cmp]');
425-
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0], [Component]));');
429+
expect(jsContents).toContain(
430+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [Component], import.meta, id));',
431+
);
426432
expect(hmrContents).toContain(
427433
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, Component) {',
428434
);
@@ -445,7 +451,9 @@ runInEachFileSystem(() => {
445451
const jsContents = env.getContents('test.js');
446452
const hmrContents = env.driveHmr('test.ts', 'Cmp');
447453
expect(jsContents).not.toContain('dependencies');
448-
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0], [Component]));');
454+
expect(jsContents).toContain(
455+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [Component], import.meta, id));',
456+
);
449457
expect(hmrContents).toContain(
450458
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, Component) {',
451459
);
@@ -471,7 +479,7 @@ runInEachFileSystem(() => {
471479
const hmrContents = env.driveHmr('test.ts', 'Cmp');
472480

473481
expect(jsContents).toContain(
474-
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [providers, Component]));',
482+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [providers, Component], import.meta, id));',
475483
);
476484
expect(hmrContents).toContain(
477485
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, providers, Component) {',
@@ -508,7 +516,7 @@ runInEachFileSystem(() => {
508516
const hmrContents = env.driveHmr('test.ts', 'Cmp');
509517

510518
expect(jsContents).toContain(
511-
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Component]));',
519+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Component], import.meta, id));',
512520
);
513521
expect(hmrContents).toContain(
514522
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, value, Component) {',
@@ -542,7 +550,7 @@ runInEachFileSystem(() => {
542550
const hmrContents = env.driveHmr('test.ts', 'Cmp');
543551

544552
expect(jsContents).toContain(
545-
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Component]));',
553+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Component], import.meta, id));',
546554
);
547555
expect(hmrContents).toContain(
548556
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, value, Component) {',
@@ -574,7 +582,7 @@ runInEachFileSystem(() => {
574582
const hmrContents = env.driveHmr('test.ts', 'Cmp');
575583

576584
expect(jsContents).toContain(
577-
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [condition, providersA, providersB, Component]));',
585+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [condition, providersA, providersB, Component], import.meta, id));',
578586
);
579587
expect(hmrContents).toContain(
580588
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, condition, providersA, providersB, Component) {',
@@ -608,7 +616,7 @@ runInEachFileSystem(() => {
608616
const jsContents = env.getContents('test.js');
609617
const hmrContents = env.driveHmr('test.ts', 'Cmp');
610618
expect(jsContents).toContain(
611-
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, otherValue, Component]));',
619+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, otherValue, Component], import.meta, id));',
612620
);
613621
expect(jsContents).toContain('useFactory: () => [(value), ((((otherValue))))]');
614622
expect(hmrContents).toContain(
@@ -646,7 +654,7 @@ runInEachFileSystem(() => {
646654
const hmrContents = env.driveHmr('test.ts', 'Cmp');
647655

648656
expect(jsContents).toContain(
649-
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Optional, dep, Component]));',
657+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, Optional, dep, Component], import.meta, id));',
650658
);
651659
expect(hmrContents).toContain(
652660
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, value, Optional, dep, Component) {',
@@ -697,7 +705,9 @@ runInEachFileSystem(() => {
697705
const hmrContents = env.driveHmr('test.ts', 'Cmp');
698706

699707
expect(jsContents).toContain('dependencies: [Dep]');
700-
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0], [Dep]));');
708+
expect(jsContents).toContain(
709+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [Dep], import.meta, id));',
710+
);
701711
expect(hmrContents).toContain('function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, Dep) {');
702712
});
703713

@@ -741,7 +751,9 @@ runInEachFileSystem(() => {
741751
const hmrContents = env.driveHmr('test.ts', 'Cmp');
742752

743753
expect(jsContents).toContain('dependencies: [DepModule, i1.Dep]');
744-
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0, i1], [DepModule]));');
754+
expect(jsContents).toContain(
755+
'ɵɵreplaceMetadata(Cmp, m.default, [i0, i1], [DepModule], import.meta, id));',
756+
);
745757
expect(hmrContents).toContain('function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, DepModule) {');
746758
});
747759

@@ -797,7 +809,9 @@ runInEachFileSystem(() => {
797809
const hmrContents = env.driveHmr('test.ts', 'Cmp');
798810

799811
expect(jsContents).toContain('dependencies: [i1.Dep]');
800-
expect(jsContents).toContain('ɵɵreplaceMetadata(Cmp, m.default, [i0, i1], []));');
812+
expect(jsContents).toContain(
813+
'ɵɵreplaceMetadata(Cmp, m.default, [i0, i1], [], import.meta, id));',
814+
);
801815
expect(hmrContents).toContain('function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces) {');
802816
});
803817

@@ -834,7 +848,7 @@ runInEachFileSystem(() => {
834848
const jsContents = env.getContents('test.js');
835849
const hmrContents = env.driveHmr('test.ts', 'Cmp');
836850
expect(jsContents).toContain(
837-
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, { one: 0, two: "2", three: 3 }, Component]));',
851+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, { one: 0, two: "2", three: 3 }, Component], import.meta, id));',
838852
);
839853
expect(hmrContents).toContain(
840854
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, Foo, Component) {',
@@ -881,7 +895,7 @@ runInEachFileSystem(() => {
881895
const jsContents = env.getContents('test.js');
882896
const hmrContents = env.driveHmr('test.ts', 'Cmp');
883897
expect(jsContents).toContain(
884-
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, { one: 0, two: "2", three: 3 }, Component]));',
898+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, { one: 0, two: "2", three: 3 }, Component], import.meta, id));',
885899
);
886900
expect(hmrContents).toContain(
887901
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, Foo, Component) {',

packages/compiler/src/render3/r3_hmr_compiler.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,10 @@ export interface R3HmrNamespaceDependency {
5353
* @param meta HMR metadata extracted from the class.
5454
*/
5555
export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression {
56-
const id = encodeURIComponent(`${meta.filePath}@${meta.className}`);
57-
const urlPartial = `./@ng/component?c=${id}&t=`;
5856
const moduleName = 'm';
5957
const dataName = 'd';
6058
const timestampName = 't';
59+
const idName = 'id';
6160
const importCallbackName = `${meta.className}_HmrLoad`;
6261
const namespaces = meta.namespaceDependencies.map((dep) => {
6362
return new o.ExternalExpr({moduleName: dep.moduleName, name: null});
@@ -66,22 +65,26 @@ export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression {
6665
// m.default
6766
const defaultRead = o.variable(moduleName).prop('default');
6867

69-
// ɵɵreplaceMetadata(Comp, m.default, [...namespaces], [...locals]);
68+
// ɵɵreplaceMetadata(Comp, m.default, [...namespaces], [...locals], import.meta, id);
7069
const replaceCall = o
7170
.importExpr(R3.replaceMetadata)
7271
.callFn([
7372
meta.type,
7473
defaultRead,
7574
o.literalArr(namespaces),
7675
o.literalArr(meta.localDependencies.map((l) => l.runtimeRepresentation)),
76+
o.variable('import').prop('meta'),
77+
o.variable(idName),
7778
]);
7879

7980
// (m) => m.default && ɵɵreplaceMetadata(...)
8081
const replaceCallback = o.arrowFn([new o.FnParam(moduleName)], defaultRead.and(replaceCall));
8182

82-
// '<urlPartial>' + encodeURIComponent(t)
83+
// '<url>?c=' + id + '&t=' + encodeURIComponent(t)
8384
const urlValue = o
84-
.literal(urlPartial)
85+
.literal(`./@ng/component?c=`)
86+
.plus(o.variable(idName))
87+
.plus(o.literal('&t='))
8588
.plus(o.variable('encodeURIComponent').callFn([o.variable(timestampName)]));
8689

8790
// import.meta.url
@@ -109,13 +112,13 @@ export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression {
109112
o.StmtModifier.Final,
110113
);
111114

112-
// (d) => d.id === <id> && Cmp_HmrLoad(d.timestamp)
115+
// (d) => d.id === id && Cmp_HmrLoad(d.timestamp)
113116
const updateCallback = o.arrowFn(
114117
[new o.FnParam(dataName)],
115118
o
116119
.variable(dataName)
117120
.prop('id')
118-
.identical(o.literal(id))
121+
.identical(o.variable(idName))
119122
.and(o.variable(importCallbackName).callFn([o.variable(dataName).prop('timestamp')])),
120123
);
121124

@@ -139,6 +142,13 @@ export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression {
139142
.arrowFn(
140143
[],
141144
[
145+
// const id = <id>;
146+
new o.DeclareVarStmt(
147+
idName,
148+
o.literal(encodeURIComponent(`${meta.filePath}@${meta.className}`)),
149+
null,
150+
o.StmtModifier.Final,
151+
),
142152
// function Cmp_HmrLoad() {...}.
143153
importCallback,
144154
// ngDevMode && Cmp_HmrLoad(Date.now());

packages/core/src/render3/hmr.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,16 @@ import {NG_COMP_DEF} from './fields';
5151
* @param applyMetadata Callback that will apply a new set of metadata on the `type` when invoked.
5252
* @param environment Syntehtic namespace imports that need to be passed along to the callback.
5353
* @param locals Local symbols from the source location that have to be exposed to the callback.
54+
* @param id ID to the class being replaced. **Not** the same as the component definition ID.
55+
* Optional since the ID might not be available internally.
5456
* @codeGenApi
5557
*/
5658
export function ɵɵreplaceMetadata(
5759
type: Type<unknown>,
5860
applyMetadata: (...args: [Type<unknown>, unknown[], ...unknown[]]) => void,
5961
namespaces: unknown[],
6062
locals: unknown[],
63+
id: string | null = null,
6164
) {
6265
ngDevMode && assertComponentDef(type);
6366
const currentDef = getComponentDef(type)!;

packages/core/test/acceptance/hmr_spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2157,6 +2157,7 @@ describe('hot module replacement', () => {
21572157
},
21582158
[angularCoreEnv],
21592159
[],
2160+
'',
21602161
);
21612162
}
21622163

0 commit comments

Comments
 (0)