@absolutejs/absolute 0.19.0-beta.748 → 0.19.0-beta.749
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/browser.js +1 -1
- package/dist/angular/browser.js.map +3 -3
- package/dist/angular/index.js +12 -1
- package/dist/angular/index.js.map +4 -4
- package/dist/angular/server.js +12 -1
- package/dist/angular/server.js.map +3 -3
- package/dist/build.js +12 -1
- package/dist/build.js.map +3 -3
- package/dist/dev/client/handlers/angular.ts +51 -8
- package/dist/dev/client/handlers/angularRuntime.ts +78 -0
- package/dist/index.js +12 -1
- package/dist/index.js.map +3 -3
- package/dist/src/angular/preserveAcrossHmr.d.ts +5 -2
- package/dist/types/globals.d.ts +6 -0
- package/package.json +1 -1
|
@@ -50,6 +50,7 @@ type AngularHmrApi = {
|
|
|
50
50
|
applyUpdate: (id: string, newCtor: unknown) => boolean;
|
|
51
51
|
getRegistry?: () => Map<string, unknown>;
|
|
52
52
|
refresh: () => void;
|
|
53
|
+
hasPageExportsChanged?: (sourceId: string) => boolean;
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
type ViewTransitionDocument = Document & {
|
|
@@ -383,6 +384,19 @@ const attemptFastPatch = async (
|
|
|
383
384
|
try {
|
|
384
385
|
const newModule = await import(`${indexPath}?t=${Date.now()}`);
|
|
385
386
|
|
|
387
|
+
// Page-level `routes` / `providers` changed? Those values are read
|
|
388
|
+
// once during `bootstrapApplication`; an in-place component patch
|
|
389
|
+
// won't re-wire the running router or root injector. The chunk
|
|
390
|
+
// records its current fingerprint each time it evaluates (initial
|
|
391
|
+
// bootstrap + every fast-patch import), so a change between the
|
|
392
|
+
// previous and current evaluation means we need to fall back to a
|
|
393
|
+
// full re-bootstrap.
|
|
394
|
+
if (hmr.hasPageExportsChanged?.(sourceFile)) {
|
|
395
|
+
console.warn = origWarn;
|
|
396
|
+
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
|
|
386
400
|
// NG0912 warnings fire during `applyUpdate` (Angular re-registers
|
|
387
401
|
// the new component class while the old one is still live). Keep
|
|
388
402
|
// the suppression active through the patch, restore right before
|
|
@@ -697,18 +711,46 @@ const handleFullUpdate = async (message: HMRMessage) => {
|
|
|
697
711
|
await runWithViewTransition(doUpdate);
|
|
698
712
|
};
|
|
699
713
|
|
|
700
|
-
/* Snapshot every WeakRef-tracked instance's
|
|
701
|
-
into the shared cache. The runtime helper
|
|
702
|
-
from `@absolutejs/absolute/angular`
|
|
703
|
-
constructor of the next-bootstrapped
|
|
704
|
-
cache. We talk to the same `globalThis`-
|
|
705
|
-
helper rather than importing it directly so
|
|
706
|
-
doesn't pull in the Angular runtime.
|
|
714
|
+
/* Snapshot every WeakRef-tracked instance's *preservable* own
|
|
715
|
+
properties into the shared cache. The runtime helper
|
|
716
|
+
`preserveAcrossHmr(this)` from `@absolutejs/absolute/angular`
|
|
717
|
+
populates the tracker, and on the constructor of the next-bootstrapped
|
|
718
|
+
instance reads back from the cache. We talk to the same `globalThis`-
|
|
719
|
+
anchored Map/Set as that helper rather than importing it directly so
|
|
720
|
+
this dev-client bundle doesn't pull in the Angular runtime.
|
|
721
|
+
|
|
722
|
+
Only primitives, plain `{}` objects, and arrays of those are
|
|
723
|
+
preserved. Class instances (HttpClient, BehaviorSubject, etc.) are
|
|
724
|
+
*not* preserved because the new instance must get those from its own
|
|
725
|
+
injector — restoring an OLD-injector reference onto a NEW instance
|
|
726
|
+
would corrupt the new app. */
|
|
707
727
|
type HmrPreserveScope = typeof globalThis & {
|
|
708
728
|
__ABS_HMR_INSTANCE_STATE__?: Map<string, Record<string, unknown>>;
|
|
709
729
|
__ABS_HMR_TRACKED_INSTANCES__?: Set<WeakRef<object>>;
|
|
710
730
|
};
|
|
711
731
|
|
|
732
|
+
const isPreservableValue = (value: unknown, depth = 0): boolean => {
|
|
733
|
+
if (depth > 8) return false;
|
|
734
|
+
if (value === null || value === undefined) return true;
|
|
735
|
+
const t = typeof value;
|
|
736
|
+
if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint')
|
|
737
|
+
return true;
|
|
738
|
+
if (t === 'function' || t === 'symbol') return false;
|
|
739
|
+
if (Array.isArray(value)) {
|
|
740
|
+
return value.every((item) => isPreservableValue(item, depth + 1));
|
|
741
|
+
}
|
|
742
|
+
if (t === 'object') {
|
|
743
|
+
const proto = Object.getPrototypeOf(value);
|
|
744
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
745
|
+
|
|
746
|
+
return Object.values(value as object).every((v) =>
|
|
747
|
+
isPreservableValue(v, depth + 1)
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return false;
|
|
752
|
+
};
|
|
753
|
+
|
|
712
754
|
const captureHmrPreservedInstanceStates = () => {
|
|
713
755
|
const scope = globalThis as HmrPreserveScope;
|
|
714
756
|
const tracker = scope.__ABS_HMR_TRACKED_INSTANCES__;
|
|
@@ -729,7 +771,8 @@ const captureHmrPreservedInstanceStates = () => {
|
|
|
729
771
|
|
|
730
772
|
const props: Record<string, unknown> = {};
|
|
731
773
|
for (const prop of Object.keys(instance)) {
|
|
732
|
-
|
|
774
|
+
const value = (instance as Record<string, unknown>)[prop];
|
|
775
|
+
if (isPreservableValue(value)) props[prop] = value;
|
|
733
776
|
}
|
|
734
777
|
cache.set(key, props);
|
|
735
778
|
}
|
|
@@ -238,11 +238,89 @@ const angularHmrStats: AngularHmrStats = {
|
|
|
238
238
|
|
|
239
239
|
const getAngularHmrStats = () => angularHmrStats;
|
|
240
240
|
|
|
241
|
+
/* Page-level export fingerprints — detect when `routes` or `providers`
|
|
242
|
+
change in a page file so the HMR fast-patch can fall back to a full
|
|
243
|
+
re-bootstrap. Without this, a component-level fast-patch silently
|
|
244
|
+
succeeds while the route/provider change is left dangling — the
|
|
245
|
+
running router/injector still uses the values from the initial
|
|
246
|
+
bootstrap. The page chunk template calls `recordPageExports` on every
|
|
247
|
+
evaluation (initial bootstrap and HMR re-imports); the fast-patch
|
|
248
|
+
handler then checks `hasPageExportsChanged` to decide whether to
|
|
249
|
+
force a full re-bootstrap.
|
|
250
|
+
Function references are treated as opaque — they change on every
|
|
251
|
+
module reload but the static config (`path`, `pathMatch`, provider
|
|
252
|
+
token, useValue, etc.) is what we care about. */
|
|
253
|
+
|
|
254
|
+
type PageFingerprint = {
|
|
255
|
+
routes: string | null;
|
|
256
|
+
providers: string | null;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
type PageExportRecord = {
|
|
260
|
+
current: PageFingerprint;
|
|
261
|
+
previous: PageFingerprint | null;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const pageExportRecords = ((
|
|
265
|
+
globalThis as { __ABS_HMR_PAGE_EXPORTS__?: Map<string, PageExportRecord> }
|
|
266
|
+
).__ABS_HMR_PAGE_EXPORTS__ ??= new Map<string, PageExportRecord>());
|
|
267
|
+
|
|
268
|
+
const fingerprint = (value: unknown, depth = 0): string => {
|
|
269
|
+
if (depth > 6) return '~deep~';
|
|
270
|
+
if (value === null) return 'null';
|
|
271
|
+
if (value === undefined) return 'undef';
|
|
272
|
+
if (typeof value === 'function') return 'fn';
|
|
273
|
+
if (typeof value === 'symbol') return value.toString();
|
|
274
|
+
if (Array.isArray(value)) {
|
|
275
|
+
return (
|
|
276
|
+
'[' + value.map((v) => fingerprint(v, depth + 1)).join(',') + ']'
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
if (typeof value === 'object') {
|
|
280
|
+
const obj = value as Record<string, unknown>;
|
|
281
|
+
const entries = Object.entries(obj)
|
|
282
|
+
.map(([k, v]): [string, string] => [k, fingerprint(v, depth + 1)])
|
|
283
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
284
|
+
|
|
285
|
+
return '{' + entries.map(([k, v]) => `${k}:${v}`).join(',') + '}';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return JSON.stringify(value);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const recordPageExports = (
|
|
292
|
+
sourceId: string,
|
|
293
|
+
routes: unknown,
|
|
294
|
+
providers: unknown
|
|
295
|
+
) => {
|
|
296
|
+
const next: PageFingerprint = {
|
|
297
|
+
routes: routes === undefined ? null : fingerprint(routes),
|
|
298
|
+
providers: providers === undefined ? null : fingerprint(providers)
|
|
299
|
+
};
|
|
300
|
+
const existing = pageExportRecords.get(sourceId);
|
|
301
|
+
pageExportRecords.set(sourceId, {
|
|
302
|
+
current: next,
|
|
303
|
+
previous: existing?.current ?? null
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const hasPageExportsChanged = (sourceId: string): boolean => {
|
|
308
|
+
const record = pageExportRecords.get(sourceId);
|
|
309
|
+
if (!record || !record.previous) return false;
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
record.previous.routes !== record.current.routes ||
|
|
313
|
+
record.previous.providers !== record.current.providers
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
|
|
241
317
|
export const installAngularHMRRuntime = () => {
|
|
242
318
|
if (typeof window === 'undefined') return;
|
|
243
319
|
window.__ANGULAR_HMR__ = {
|
|
244
320
|
applyUpdate,
|
|
245
321
|
getStats: getAngularHmrStats,
|
|
322
|
+
hasPageExportsChanged,
|
|
323
|
+
recordPageExports,
|
|
246
324
|
refresh,
|
|
247
325
|
register,
|
|
248
326
|
getRegistry: () => componentRegistry
|
package/dist/index.js
CHANGED
|
@@ -44594,6 +44594,17 @@ var absoluteHttpTransferCacheOptions = {
|
|
|
44594
44594
|
// classes without needing a separate build artifact.
|
|
44595
44595
|
export * from '${normalizedImportPath}';
|
|
44596
44596
|
|
|
44597
|
+
// Record this evaluation's \`routes\` and \`providers\` exports for the
|
|
44598
|
+
// HMR fast-patch to compare against on the next reload. If they change
|
|
44599
|
+
// (a new route was added, a provider was edited), fast-patch falls back
|
|
44600
|
+
// to a full re-bootstrap because those values are consumed once at
|
|
44601
|
+
// bootstrap and won't propagate to the running router/injector via an
|
|
44602
|
+
// in-place component patch.
|
|
44603
|
+
if (typeof window !== 'undefined' && window.__ANGULAR_HMR__ && typeof window.__ANGULAR_HMR__.recordPageExports === 'function') {
|
|
44604
|
+
var __abs_hmr_routes = Reflect.get(pageModule, 'routes');
|
|
44605
|
+
window.__ANGULAR_HMR__.recordPageExports('${resolvedEntry}', __abs_hmr_routes, maybePageProviders);
|
|
44606
|
+
}
|
|
44607
|
+
|
|
44597
44608
|
// Re-Bootstrap HMR with View Transitions API.
|
|
44598
44609
|
// Skipped during fast-patch: the HMR client sets
|
|
44599
44610
|
// window.__ANGULAR_HMR_FAST_PATCH__ = true before \`import()\`-ing this
|
|
@@ -58549,5 +58560,5 @@ export {
|
|
|
58549
58560
|
ANGULAR_INIT_TIMEOUT_MS
|
|
58550
58561
|
};
|
|
58551
58562
|
|
|
58552
|
-
//# debugId=
|
|
58563
|
+
//# debugId=101D5DB96668BF8564756E2164756E21
|
|
58553
58564
|
//# sourceMappingURL=index.js.map
|