@absolutejs/absolute 0.19.0-beta.866 → 0.19.0-beta.868
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/build.js +76 -9
- package/dist/build.js.map +5 -5
- package/dist/dev/client/handlers/angularHmrShim.ts +9 -1
- package/dist/dev/client/handlers/angularRemount.ts +267 -0
- package/dist/dev/client/handlers/angularRemountWiring.ts +55 -0
- package/dist/dev/client/hmrClient.ts +29 -1
- package/dist/index.js +76 -9
- package/dist/index.js.map +5 -5
- package/dist/src/dev/angular/fastHmrCompiler.d.ts +1 -0
- package/dist/src/dev/angular/vendor/lview/lViewOps.d.ts +17 -0
- package/dist/src/dev/angular/vendor/lview/slotConstants.d.ts +24 -0
- package/package.json +1 -1
|
@@ -13,7 +13,9 @@ import type {} from '../../../types/globals';
|
|
|
13
13
|
* shim setup synchronous + at module scope so it's installed during
|
|
14
14
|
* `hmrClient.ts`'s import-evaluation pass, before page chunks load. */
|
|
15
15
|
|
|
16
|
-
export type AngularHmrEvent =
|
|
16
|
+
export type AngularHmrEvent =
|
|
17
|
+
| 'angular:component-update'
|
|
18
|
+
| 'angular:component-remount';
|
|
17
19
|
export type AngularComponentUpdate = {
|
|
18
20
|
id: string;
|
|
19
21
|
timestamp: number;
|
|
@@ -75,3 +77,9 @@ export const dispatchAngularComponentUpdate = (
|
|
|
75
77
|
) => {
|
|
76
78
|
globalThis.__angularHmr?.dispatch('angular:component-update', data);
|
|
77
79
|
};
|
|
80
|
+
|
|
81
|
+
export const dispatchAngularComponentRemount = (
|
|
82
|
+
data: AngularComponentUpdate
|
|
83
|
+
) => {
|
|
84
|
+
globalThis.__angularHmr?.dispatch('angular:component-remount', data);
|
|
85
|
+
};
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import type {} from '../../../types/globals';
|
|
2
|
+
/* Per-component Tier 1 remount.
|
|
3
|
+
*
|
|
4
|
+
* When fastHmr reports a structural change for a component class,
|
|
5
|
+
* full app rebootstrap loses all sibling component state. Instead, we
|
|
6
|
+
* remount only the affected components: destroy each live instance +
|
|
7
|
+
* recreate at the same DOM host with the new factory.
|
|
8
|
+
*
|
|
9
|
+
* This uses public `createComponent` for the heavy lifting (it runs
|
|
10
|
+
* the new constructor, sets up DI, fires lifecycle hooks, renders the
|
|
11
|
+
* template, attaches change detection). We supplement with vendored
|
|
12
|
+
* LView slot manipulation to (a) find each live instance's parent
|
|
13
|
+
* LView slot and (b) splice the freshly-created LView into that slot
|
|
14
|
+
* so it participates in the parent's view tree instead of being a
|
|
15
|
+
* detached root.
|
|
16
|
+
*
|
|
17
|
+
* Caveats baked into this approach:
|
|
18
|
+
* • The new LView starts as a "root" view (createComponent attaches
|
|
19
|
+
* it to ApplicationRef). After splice, it's a child of the
|
|
20
|
+
* original parent. We need to detach from ApplicationRef so it's
|
|
21
|
+
* not double-tracked.
|
|
22
|
+
* • Old @Input bindings from the parent are NOT re-applied. The
|
|
23
|
+
* parent's template flow runs at parent-CD time and wires inputs
|
|
24
|
+
* then; until then the new instance sees default values. In
|
|
25
|
+
* practice this matches Tier 1 rebootstrap behavior — no worse.
|
|
26
|
+
* • Old projection content (ng-content) doesn't transfer. If the
|
|
27
|
+
* parent injected a child via ng-content, the new instance has an
|
|
28
|
+
* empty projection slot until parent re-renders. Logged as a
|
|
29
|
+
* known limitation in ANGULAR_PER_COMPONENT_REMOUNT_RESEARCH.md. */
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
CHILD_HEAD,
|
|
33
|
+
CHILD_TAIL,
|
|
34
|
+
CONTEXT,
|
|
35
|
+
HOST,
|
|
36
|
+
PARENT,
|
|
37
|
+
T_HOST,
|
|
38
|
+
TVIEW
|
|
39
|
+
} from '../../angular/vendor/lview/slotConstants';
|
|
40
|
+
import {
|
|
41
|
+
executeOnDestroys,
|
|
42
|
+
isLView,
|
|
43
|
+
markLViewDestroyed,
|
|
44
|
+
processCleanups,
|
|
45
|
+
replaceLViewInTree,
|
|
46
|
+
type LView,
|
|
47
|
+
type TView,
|
|
48
|
+
type TNode
|
|
49
|
+
} from '../../angular/vendor/lview/lViewOps';
|
|
50
|
+
|
|
51
|
+
type AngularCoreNamespace = {
|
|
52
|
+
createComponent: (
|
|
53
|
+
type: unknown,
|
|
54
|
+
options: {
|
|
55
|
+
hostElement?: Element;
|
|
56
|
+
environmentInjector: unknown;
|
|
57
|
+
}
|
|
58
|
+
) => {
|
|
59
|
+
instance: unknown;
|
|
60
|
+
hostView: { _lView?: LView; detectChanges?: () => void };
|
|
61
|
+
destroy: () => void;
|
|
62
|
+
};
|
|
63
|
+
ApplicationRef?: unknown;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type ComponentClass = new (...args: unknown[]) => unknown;
|
|
67
|
+
|
|
68
|
+
type LiveInstance = {
|
|
69
|
+
host: Element;
|
|
70
|
+
oldLView: LView;
|
|
71
|
+
parentLView: LView;
|
|
72
|
+
slotIndex: number;
|
|
73
|
+
tNode: TNode;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/* Walk the DOM looking for elements whose component instance is of
|
|
77
|
+
* `Class`. Each match resolves to its parent LView + slot index via
|
|
78
|
+
* the LContext stored on the host element under `__ngContext__`.
|
|
79
|
+
*
|
|
80
|
+
* We walk DOM (not Angular's TRACKED_LVIEWS map) because (a)
|
|
81
|
+
* TRACKED_LVIEWS isn't exported and (b) the DOM walk is bounded by
|
|
82
|
+
* page size, which is fast enough for HMR. */
|
|
83
|
+
const findLiveInstances = (Class: ComponentClass): LiveInstance[] => {
|
|
84
|
+
const results: LiveInstance[] = [];
|
|
85
|
+
const elements = document.querySelectorAll('*');
|
|
86
|
+
for (const el of Array.from(elements)) {
|
|
87
|
+
const ctx = (el as unknown as Record<string, unknown>).__ngContext__;
|
|
88
|
+
if (typeof ctx !== 'object' || ctx === null) continue;
|
|
89
|
+
const lContext = ctx as { lView?: LView; nodeIndex?: number };
|
|
90
|
+
if (!lContext.lView || lContext.nodeIndex === undefined) continue;
|
|
91
|
+
|
|
92
|
+
const slot = lContext.lView[lContext.nodeIndex];
|
|
93
|
+
if (!isLView(slot)) continue;
|
|
94
|
+
const ownLView = slot as LView;
|
|
95
|
+
const instance = ownLView[CONTEXT];
|
|
96
|
+
if (!(instance instanceof Class)) continue;
|
|
97
|
+
|
|
98
|
+
const tNode = ownLView[T_HOST] as TNode | null;
|
|
99
|
+
const host = ownLView[HOST] as Element | null;
|
|
100
|
+
if (!tNode || !host) continue;
|
|
101
|
+
|
|
102
|
+
// Avoid double-recording the same LView (multiple DOM elements
|
|
103
|
+
// can land in the same component, all sharing __ngContext__)
|
|
104
|
+
if (results.some((r) => r.oldLView === ownLView)) continue;
|
|
105
|
+
|
|
106
|
+
results.push({
|
|
107
|
+
host,
|
|
108
|
+
oldLView: ownLView,
|
|
109
|
+
parentLView: lContext.lView,
|
|
110
|
+
slotIndex: lContext.nodeIndex,
|
|
111
|
+
tNode
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return results;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/* Run a public `createComponent` call to instantiate `Class` at
|
|
118
|
+
* `hostElement`. Pulls ApplicationRef + EnvironmentInjector through
|
|
119
|
+
* the live app's injector exposed on `window.__ANGULAR_APP__`. */
|
|
120
|
+
const createFreshAt = (
|
|
121
|
+
Class: ComponentClass,
|
|
122
|
+
hostElement: Element,
|
|
123
|
+
core: AngularCoreNamespace
|
|
124
|
+
): {
|
|
125
|
+
instance: unknown;
|
|
126
|
+
newLView: LView;
|
|
127
|
+
componentRef: ReturnType<AngularCoreNamespace['createComponent']>;
|
|
128
|
+
} | null => {
|
|
129
|
+
const w = window as unknown as {
|
|
130
|
+
__ANGULAR_APP__?: { injector: unknown };
|
|
131
|
+
};
|
|
132
|
+
const envInjector = w.__ANGULAR_APP__?.injector;
|
|
133
|
+
if (!envInjector) return null;
|
|
134
|
+
|
|
135
|
+
const ref = core.createComponent(Class, {
|
|
136
|
+
hostElement,
|
|
137
|
+
environmentInjector: envInjector
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const newLView = ref.hostView._lView;
|
|
141
|
+
if (!newLView) {
|
|
142
|
+
// Should never happen — _lView is always populated by Angular's
|
|
143
|
+
// internal createComponent path. If it is missing, our slot
|
|
144
|
+
// constants might be off; bail to caller for fallback.
|
|
145
|
+
ref.destroy();
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { instance: ref.instance, newLView, componentRef: ref };
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/* Splice `newLView` into `parentLView` at `slotIndex`, replacing
|
|
153
|
+
* `oldLView`. After the splice, the new LView lives in the parent's
|
|
154
|
+
* view tree; the old one is detached. */
|
|
155
|
+
const spliceLViewIntoParent = (
|
|
156
|
+
target: LiveInstance,
|
|
157
|
+
newLView: LView
|
|
158
|
+
): void => {
|
|
159
|
+
const { parentLView, oldLView, slotIndex, tNode } = target;
|
|
160
|
+
replaceLViewInTree(parentLView, oldLView, newLView, slotIndex);
|
|
161
|
+
newLView[PARENT] = parentLView;
|
|
162
|
+
newLView[T_HOST] = tNode;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/* Fire onDestroy + cleanup on the OLD LView so subscriptions, event
|
|
166
|
+
* listeners, and `inject(DestroyRef).onDestroy(...)` callbacks all
|
|
167
|
+
* fire. Then mark the LView as destroyed so any subsequent
|
|
168
|
+
* tree-walk skips it. */
|
|
169
|
+
const teardownOldLView = (oldLView: LView): void => {
|
|
170
|
+
const oldTView = oldLView[TVIEW] as TView | null;
|
|
171
|
+
if (oldTView) {
|
|
172
|
+
executeOnDestroys(oldTView, oldLView);
|
|
173
|
+
processCleanups(oldTView, oldLView);
|
|
174
|
+
}
|
|
175
|
+
markLViewDestroyed(oldLView);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/* The fresh ComponentRef is registered as a root view on
|
|
179
|
+
* ApplicationRef. We don't want it tracked there — its parent in the
|
|
180
|
+
* view tree is the original parent LView. Detach it. */
|
|
181
|
+
const detachFromApplicationRoot = (
|
|
182
|
+
componentRef: { hostView: unknown },
|
|
183
|
+
core: AngularCoreNamespace
|
|
184
|
+
): void => {
|
|
185
|
+
if (!core.ApplicationRef) return;
|
|
186
|
+
const w = window as unknown as {
|
|
187
|
+
__ANGULAR_APP__?: { detachView?: (view: unknown) => void };
|
|
188
|
+
};
|
|
189
|
+
w.__ANGULAR_APP__?.detachView?.(componentRef.hostView);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export type RemountResult = {
|
|
193
|
+
className: string;
|
|
194
|
+
remounted: number;
|
|
195
|
+
skipped: number;
|
|
196
|
+
error?: string;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/* Public entry. Called by the bundle's HMR listener block when an
|
|
200
|
+
* `angular:component-remount` event arrives for this class.
|
|
201
|
+
*
|
|
202
|
+
* applyMetadata is the surgical module's default export — it patches
|
|
203
|
+
* `Class.ɵcmp` with the new component definition. We call it BEFORE
|
|
204
|
+
* createComponent so the fresh instance picks up the new template,
|
|
205
|
+
* dependencies, etc.
|
|
206
|
+
*
|
|
207
|
+
* locals + namespaces match `ɵɵreplaceMetadata`'s contract — passed
|
|
208
|
+
* through to applyMetadata. We're not using ɵɵreplaceMetadata here
|
|
209
|
+
* (it preserves instance state, defeating the point), but we mirror
|
|
210
|
+
* the calling convention so bundle-level code stays consistent. */
|
|
211
|
+
export const remountComponentClass = async (
|
|
212
|
+
Class: ComponentClass,
|
|
213
|
+
applyMetadata: (
|
|
214
|
+
Class: unknown,
|
|
215
|
+
namespaces: unknown[],
|
|
216
|
+
...locals: unknown[]
|
|
217
|
+
) => void,
|
|
218
|
+
namespaces: unknown[],
|
|
219
|
+
locals: unknown[],
|
|
220
|
+
core: AngularCoreNamespace,
|
|
221
|
+
className: string
|
|
222
|
+
): Promise<RemountResult> => {
|
|
223
|
+
try {
|
|
224
|
+
applyMetadata.apply(null, [Class, namespaces, ...locals]);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
return {
|
|
227
|
+
className,
|
|
228
|
+
error: `applyMetadata threw: ${(err as Error).message}`,
|
|
229
|
+
remounted: 0,
|
|
230
|
+
skipped: 0
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const targets = findLiveInstances(Class);
|
|
235
|
+
if (targets.length === 0) {
|
|
236
|
+
return { className, remounted: 0, skipped: 0 };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let remounted = 0;
|
|
240
|
+
let skipped = 0;
|
|
241
|
+
|
|
242
|
+
for (const target of targets) {
|
|
243
|
+
try {
|
|
244
|
+
const fresh = createFreshAt(Class, target.host, core);
|
|
245
|
+
if (!fresh) {
|
|
246
|
+
skipped++;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
spliceLViewIntoParent(target, fresh.newLView);
|
|
251
|
+
detachFromApplicationRoot(fresh.componentRef, core);
|
|
252
|
+
teardownOldLView(target.oldLView);
|
|
253
|
+
|
|
254
|
+
fresh.componentRef.hostView.detectChanges?.();
|
|
255
|
+
remounted++;
|
|
256
|
+
} catch (err) {
|
|
257
|
+
console.error(
|
|
258
|
+
`[absolutejs] remount of ${className} failed at`,
|
|
259
|
+
target.host,
|
|
260
|
+
err
|
|
261
|
+
);
|
|
262
|
+
skipped++;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { className, remounted, skipped };
|
|
267
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type {} from '../../../types/globals';
|
|
2
|
+
/* Wire `globalThis.__absAngularRemount` so the injected
|
|
3
|
+
* `__ng_hmr_remount` blocks (in `hmrInjectionPlugin.ts`) can call into
|
|
4
|
+
* the shared remount implementation. The bundle's listener captures
|
|
5
|
+
* the class via closure and the metadata via dynamic import; everything
|
|
6
|
+
* else is generic, so the implementation is shared rather than baked
|
|
7
|
+
* into every component bundle. */
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
remountComponentClass,
|
|
11
|
+
type RemountResult
|
|
12
|
+
} from './angularRemount';
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
// eslint-disable-next-line no-var
|
|
16
|
+
var __absAngularRemount:
|
|
17
|
+
| ((
|
|
18
|
+
Class: new (...args: unknown[]) => unknown,
|
|
19
|
+
applyMetadata: (
|
|
20
|
+
Class: unknown,
|
|
21
|
+
namespaces: unknown[],
|
|
22
|
+
...locals: unknown[]
|
|
23
|
+
) => void,
|
|
24
|
+
namespaces: unknown[],
|
|
25
|
+
locals: unknown[],
|
|
26
|
+
core: {
|
|
27
|
+
createComponent: (
|
|
28
|
+
type: unknown,
|
|
29
|
+
options: {
|
|
30
|
+
hostElement?: Element;
|
|
31
|
+
environmentInjector: unknown;
|
|
32
|
+
}
|
|
33
|
+
) => {
|
|
34
|
+
instance: unknown;
|
|
35
|
+
hostView: {
|
|
36
|
+
_lView?: unknown[];
|
|
37
|
+
detectChanges?: () => void;
|
|
38
|
+
};
|
|
39
|
+
destroy: () => void;
|
|
40
|
+
};
|
|
41
|
+
ApplicationRef?: unknown;
|
|
42
|
+
},
|
|
43
|
+
className: string
|
|
44
|
+
) => Promise<RemountResult>)
|
|
45
|
+
| undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let installed = false;
|
|
49
|
+
|
|
50
|
+
export const installAngularRemountGlobal = (): void => {
|
|
51
|
+
if (installed) return;
|
|
52
|
+
if (typeof globalThis === 'undefined') return;
|
|
53
|
+
globalThis.__absAngularRemount = remountComponentClass;
|
|
54
|
+
installed = true;
|
|
55
|
+
};
|
|
@@ -13,7 +13,11 @@ import {
|
|
|
13
13
|
} from './constants';
|
|
14
14
|
import { detectCurrentFramework } from './frameworkDetect';
|
|
15
15
|
import { hideErrorOverlay, showErrorOverlay } from './errorOverlay';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
dispatchAngularComponentRemount,
|
|
18
|
+
dispatchAngularComponentUpdate
|
|
19
|
+
} from './handlers/angularHmrShim';
|
|
20
|
+
import { installAngularRemountGlobal } from './handlers/angularRemountWiring';
|
|
17
21
|
import { handleReactUpdate } from './handlers/react';
|
|
18
22
|
import { handleHTMLUpdate, handleScriptUpdate } from './handlers/html';
|
|
19
23
|
import { handleHTMXUpdate } from './handlers/htmx';
|
|
@@ -30,6 +34,7 @@ import {
|
|
|
30
34
|
|
|
31
35
|
// Initialize HMR globals
|
|
32
36
|
if (typeof window !== 'undefined') {
|
|
37
|
+
installAngularRemountGlobal();
|
|
33
38
|
if (!window.__HMR_MANIFEST__) {
|
|
34
39
|
window.__HMR_MANIFEST__ = {};
|
|
35
40
|
}
|
|
@@ -69,6 +74,7 @@ window.addEventListener('unhandledrejection', (evt) => {
|
|
|
69
74
|
|
|
70
75
|
const hmrUpdateTypes = new Set([
|
|
71
76
|
'angular:component-update',
|
|
77
|
+
'angular:component-remount',
|
|
72
78
|
'angular:rebootstrap',
|
|
73
79
|
'react-update',
|
|
74
80
|
'html-update',
|
|
@@ -169,6 +175,28 @@ const handleHMRMessage = (message: HMRMessage) => {
|
|
|
169
175
|
}
|
|
170
176
|
break;
|
|
171
177
|
}
|
|
178
|
+
case 'angular:component-remount': {
|
|
179
|
+
// Tier 1a per-component remount. Structural change
|
|
180
|
+
// detected in fastHmr — the existing instance lacks new
|
|
181
|
+
// fields / DI / providers, so we destroy + recreate just
|
|
182
|
+
// this component (vs. full app rebootstrap). The injected
|
|
183
|
+
// `__ng_hmr_remount` listener handles the splice via the
|
|
184
|
+
// `__absAngularRemount` global wired in
|
|
185
|
+
// `installAngularRemountGlobal`.
|
|
186
|
+
const data = message.data as
|
|
187
|
+
| { id?: string; timestamp?: number }
|
|
188
|
+
| undefined;
|
|
189
|
+
if (data && typeof data.id === 'string') {
|
|
190
|
+
dispatchAngularComponentRemount({
|
|
191
|
+
id: data.id,
|
|
192
|
+
timestamp:
|
|
193
|
+
typeof data.timestamp === 'number'
|
|
194
|
+
? data.timestamp
|
|
195
|
+
: Date.now()
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
172
200
|
case 'angular:rebootstrap': {
|
|
173
201
|
// Tier 1 fallback. The user's edit changed structure
|
|
174
202
|
// the surgical path can't safely apply
|
package/dist/index.js
CHANGED
|
@@ -9858,7 +9858,6 @@ var ENTITY_DECORATOR_RE, buildHmrTail = (className, encodedIdLiteral) => `
|
|
|
9858
9858
|
]);
|
|
9859
9859
|
if (!u || typeof u.default !== 'function') return;
|
|
9860
9860
|
if (${className}.\u0275cmp && typeof core.\u0275\u0275replaceMetadata === 'function') {
|
|
9861
|
-
console.log('[abs-hmr] \u0275\u0275replaceMetadata for ${className}, def before:', !!${className}.\u0275cmp);
|
|
9862
9861
|
try {
|
|
9863
9862
|
core.\u0275\u0275replaceMetadata(
|
|
9864
9863
|
${className},
|
|
@@ -9868,7 +9867,6 @@ var ENTITY_DECORATOR_RE, buildHmrTail = (className, encodedIdLiteral) => `
|
|
|
9868
9867
|
import.meta,
|
|
9869
9868
|
__ng_hmr_id
|
|
9870
9869
|
);
|
|
9871
|
-
console.log('[abs-hmr] \u0275\u0275replaceMetadata returned for ${className}');
|
|
9872
9870
|
} catch (err) {
|
|
9873
9871
|
console.error('[abs-hmr] \u0275\u0275replaceMetadata threw for ${className}:', err);
|
|
9874
9872
|
}
|
|
@@ -9878,12 +9876,50 @@ var ENTITY_DECORATOR_RE, buildHmrTail = (className, encodedIdLiteral) => `
|
|
|
9878
9876
|
u.default(${className}, [core]);
|
|
9879
9877
|
}
|
|
9880
9878
|
};
|
|
9879
|
+
// Tier 1 remount: structural changes (new ctor params / new field
|
|
9880
|
+
// initializers / new providers) make a CONTEXT-preserving replace
|
|
9881
|
+
// unsafe \u2014 the existing instance lacks the new fields. The remount
|
|
9882
|
+
// path tears down the live LView and re-creates it via public
|
|
9883
|
+
// \`createComponent\` against the same host, so the new constructor
|
|
9884
|
+
// runs with fresh DI, new lifecycle hooks fire, and the splice
|
|
9885
|
+
// logic puts the result back in the parent's view tree. The
|
|
9886
|
+
// shared implementation is on \`globalThis.__absAngularRemount\` \u2014
|
|
9887
|
+
// installed by hmrClient.ts's import-time wiring.
|
|
9888
|
+
const __ng_hmr_remount = async (t) => {
|
|
9889
|
+
const [u, core] = await Promise.all([
|
|
9890
|
+
import('/@ng/component?c=' + encodeURIComponent(__ng_hmr_id) + '&t=' + t),
|
|
9891
|
+
import('@angular/core')
|
|
9892
|
+
]);
|
|
9893
|
+
if (!u || typeof u.default !== 'function') return;
|
|
9894
|
+
if (typeof globalThis.__absAngularRemount === 'function' && ${className}.\u0275cmp) {
|
|
9895
|
+
try {
|
|
9896
|
+
await globalThis.__absAngularRemount(
|
|
9897
|
+
${className},
|
|
9898
|
+
u.default,
|
|
9899
|
+
[core],
|
|
9900
|
+
[],
|
|
9901
|
+
core,
|
|
9902
|
+
${JSON.stringify(className)}
|
|
9903
|
+
);
|
|
9904
|
+
} catch (err) {
|
|
9905
|
+
console.error('[abs-hmr] remount threw for ${className}:', err);
|
|
9906
|
+
}
|
|
9907
|
+
} else {
|
|
9908
|
+
// No remount helper installed (older absolutejs runtime, or
|
|
9909
|
+
// non-component entity). Fall back to surgical replace.
|
|
9910
|
+
u.default(${className}, [core]);
|
|
9911
|
+
}
|
|
9912
|
+
};
|
|
9913
|
+
|
|
9881
9914
|
if (typeof globalThis !== 'undefined' &&
|
|
9882
9915
|
globalThis.__angularHmr &&
|
|
9883
9916
|
typeof globalThis.__angularHmr.on === 'function') {
|
|
9884
9917
|
globalThis.__angularHmr.on('angular:component-update', (d) => {
|
|
9885
9918
|
if (d && d.id === __ng_hmr_id) __ng_hmr_load(d.timestamp);
|
|
9886
9919
|
});
|
|
9920
|
+
globalThis.__angularHmr.on('angular:component-remount', (d) => {
|
|
9921
|
+
if (d && d.id === __ng_hmr_id) __ng_hmr_remount(d.timestamp);
|
|
9922
|
+
});
|
|
9887
9923
|
}
|
|
9888
9924
|
}
|
|
9889
9925
|
`, createAngularHmrInjectionPlugin = (params) => {
|
|
@@ -18931,7 +18967,12 @@ ${block}
|
|
|
18931
18967
|
if (!moduleText) {
|
|
18932
18968
|
return fail("unexpected-error", `buildSimpleEntityModule returned null for ${className}`);
|
|
18933
18969
|
}
|
|
18934
|
-
return {
|
|
18970
|
+
return {
|
|
18971
|
+
componentSource: sourceFile,
|
|
18972
|
+
fingerprintChanged: false,
|
|
18973
|
+
moduleText,
|
|
18974
|
+
ok: true
|
|
18975
|
+
};
|
|
18935
18976
|
}
|
|
18936
18977
|
if (inheritsDecoratedClass(classNode)) {
|
|
18937
18978
|
return fail("inherits-decorated-class");
|
|
@@ -18986,9 +19027,7 @@ ${block}
|
|
|
18986
19027
|
const fingerprintId = encodeURIComponent(`${projectRelPath}@${className}`);
|
|
18987
19028
|
const currentFingerprint = extractFingerprint(classNode, className, decoratorMeta, inputs, outputs, sourceFile, componentDir);
|
|
18988
19029
|
const cachedFingerprint = fingerprintCache.get(fingerprintId);
|
|
18989
|
-
|
|
18990
|
-
return fail("structural-change", `fingerprint changed for ${className}; escalate to Tier 1`);
|
|
18991
|
-
}
|
|
19030
|
+
const fingerprintChanged = cachedFingerprint !== undefined && !fingerprintsEqual(cachedFingerprint, currentFingerprint);
|
|
18992
19031
|
const sourceFileObj = new compiler.ParseSourceFile(tsSource, componentFilePath);
|
|
18993
19032
|
const zeroLoc = new compiler.ParseLocation(sourceFileObj, 0, 0, 0);
|
|
18994
19033
|
const typeSourceSpan = new compiler.ParseSourceSpan(zeroLoc, zeroLoc);
|
|
@@ -19093,7 +19132,12 @@ ${block}
|
|
|
19093
19132
|
}
|
|
19094
19133
|
}
|
|
19095
19134
|
fingerprintCache.set(fingerprintId, currentFingerprint);
|
|
19096
|
-
return {
|
|
19135
|
+
return {
|
|
19136
|
+
componentSource: sourceFile,
|
|
19137
|
+
fingerprintChanged,
|
|
19138
|
+
moduleText,
|
|
19139
|
+
ok: true
|
|
19140
|
+
};
|
|
19097
19141
|
} catch (err) {
|
|
19098
19142
|
return fail("unexpected-error", String(err));
|
|
19099
19143
|
}
|
|
@@ -19893,6 +19937,7 @@ var moduleServerPromise, getModuleServer = () => moduleServerPromise, runSequent
|
|
|
19893
19937
|
const { tryFastHmr: tryFastHmr2 } = await Promise.resolve().then(() => (init_fastHmrCompiler(), exports_fastHmrCompiler));
|
|
19894
19938
|
const queue = [];
|
|
19895
19939
|
const queueIds = new Set;
|
|
19940
|
+
let anyFingerprintChanged = false;
|
|
19896
19941
|
for (const editedFile of userEdited) {
|
|
19897
19942
|
const owners = resolveOwningComponents2({
|
|
19898
19943
|
changedFilePath: editedFile,
|
|
@@ -19900,6 +19945,7 @@ var moduleServerPromise, getModuleServer = () => moduleServerPromise, runSequent
|
|
|
19900
19945
|
});
|
|
19901
19946
|
if (owners.length === 0 && (editedFile.endsWith(".component.ts") || editedFile.endsWith(".directive.ts") || editedFile.endsWith(".pipe.ts") || editedFile.endsWith(".service.ts"))) {
|
|
19902
19947
|
return {
|
|
19948
|
+
kind: "rebootstrap",
|
|
19903
19949
|
reason: `no Angular-decorated class found in ${editedFile}`,
|
|
19904
19950
|
tier: 1
|
|
19905
19951
|
};
|
|
@@ -19915,14 +19961,21 @@ var moduleServerPromise, getModuleServer = () => moduleServerPromise, runSequent
|
|
|
19915
19961
|
});
|
|
19916
19962
|
if (!result.ok) {
|
|
19917
19963
|
return {
|
|
19964
|
+
kind: "rebootstrap",
|
|
19918
19965
|
reason: `${className}: ${result.reason}${result.detail ? ` (${result.detail})` : ""}`,
|
|
19919
19966
|
tier: 1
|
|
19920
19967
|
};
|
|
19921
19968
|
}
|
|
19969
|
+
if (result.fingerprintChanged) {
|
|
19970
|
+
anyFingerprintChanged = true;
|
|
19971
|
+
}
|
|
19922
19972
|
queueIds.add(id);
|
|
19923
19973
|
queue.push({ className, id });
|
|
19924
19974
|
}
|
|
19925
19975
|
}
|
|
19976
|
+
if (anyFingerprintChanged) {
|
|
19977
|
+
return { kind: "remount", queue, tier: 1 };
|
|
19978
|
+
}
|
|
19926
19979
|
return { queue, tier: 0 };
|
|
19927
19980
|
}, broadcastSurgical = (state, queue) => {
|
|
19928
19981
|
const timestamp = Date.now();
|
|
@@ -19933,6 +19986,15 @@ var moduleServerPromise, getModuleServer = () => moduleServerPromise, runSequent
|
|
|
19933
19986
|
});
|
|
19934
19987
|
logInfo(`[ng-hmr broadcast] ${className}`);
|
|
19935
19988
|
}
|
|
19989
|
+
}, broadcastRemount = (state, queue) => {
|
|
19990
|
+
const timestamp = Date.now();
|
|
19991
|
+
for (const { id, className } of queue) {
|
|
19992
|
+
broadcastToClients(state, {
|
|
19993
|
+
data: { id, timestamp },
|
|
19994
|
+
type: "angular:component-remount"
|
|
19995
|
+
});
|
|
19996
|
+
logInfo(`[ng-hmr tier-1 remount] ${className}`);
|
|
19997
|
+
}
|
|
19936
19998
|
}, broadcastRebootstrap = async (state, reason) => {
|
|
19937
19999
|
logInfo(`[ng-hmr tier-1 rebootstrap] ${reason}`);
|
|
19938
20000
|
broadcastToClients(state, {
|
|
@@ -19996,7 +20058,12 @@ var moduleServerPromise, getModuleServer = () => moduleServerPromise, runSequent
|
|
|
19996
20058
|
runBundle().catch((err) => {
|
|
19997
20059
|
logWarn(`[ng-hmr async bundle] rebuild failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
19998
20060
|
});
|
|
19999
|
-
} else {
|
|
20061
|
+
} else if (verdict.tier === 1 && verdict.kind === "remount") {
|
|
20062
|
+
broadcastRemount(state, verdict.queue);
|
|
20063
|
+
runBundle().catch((err) => {
|
|
20064
|
+
logWarn(`[ng-hmr async bundle] rebuild failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
20065
|
+
});
|
|
20066
|
+
} else if (verdict.tier === 1 && verdict.kind === "rebootstrap") {
|
|
20000
20067
|
await runBundle();
|
|
20001
20068
|
await broadcastRebootstrap(state, verdict.reason);
|
|
20002
20069
|
}
|
|
@@ -30291,5 +30358,5 @@ export {
|
|
|
30291
30358
|
ANGULAR_INIT_TIMEOUT_MS
|
|
30292
30359
|
};
|
|
30293
30360
|
|
|
30294
|
-
//# debugId=
|
|
30361
|
+
//# debugId=F19AC49C80B89ABF64756E2164756E21
|
|
30295
30362
|
//# sourceMappingURL=index.js.map
|