@absolutejs/absolute 0.20.0-beta.0 → 0.20.0-beta.2

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.
Files changed (91) hide show
  1. package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
  2. package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
  3. package/dist/angular/index.js +14 -1
  4. package/dist/angular/index.js.map +3 -3
  5. package/dist/angular/server.js +14 -1
  6. package/dist/angular/server.js.map +3 -3
  7. package/dist/build.js +206 -47
  8. package/dist/build.js.map +20 -19
  9. package/dist/cli/config/server.js +2 -2
  10. package/dist/cli/index.js +16506 -11326
  11. package/dist/dev/client/cssUtils.ts +98 -41
  12. package/dist/dev/client/handlers/html.ts +75 -15
  13. package/dist/dev/client/handlers/htmx.ts +43 -8
  14. package/dist/dev/client/handlers/react.ts +114 -32
  15. package/dist/dev/client/handlers/rebuild.ts +28 -8
  16. package/dist/dev/client/handlers/svelte.ts +33 -50
  17. package/dist/dev/client/handlers/vue.ts +36 -45
  18. package/dist/dev/client/hmrClient.ts +38 -6
  19. package/dist/dev/client/hmrTiming.ts +141 -0
  20. package/dist/dev/serverBootstrap.js +20 -11
  21. package/dist/index.js +953 -423
  22. package/dist/index.js.map +27 -23
  23. package/dist/mobile/browser.js.map +1 -1
  24. package/dist/mobile/index.js +2667 -260
  25. package/dist/mobile/index.js.map +27 -7
  26. package/dist/react/index.js +16 -3
  27. package/dist/react/index.js.map +4 -4
  28. package/dist/react/jsxDevRuntimeCompat.js +22 -2
  29. package/dist/react/jsxDevRuntimeCompat.js.map +5 -4
  30. package/dist/react/jsxRuntimeCompat.js +90 -0
  31. package/dist/react/jsxRuntimeCompat.js.map +10 -0
  32. package/dist/react/server.js +16 -3
  33. package/dist/react/server.js.map +4 -4
  34. package/dist/src/build/compileVue.d.ts +1 -1
  35. package/dist/src/build/resolveBuildMode.d.ts +7 -0
  36. package/dist/src/cli/scripts/dev.d.ts +4 -1
  37. package/dist/src/cli/scripts/mobile.d.ts +1 -0
  38. package/dist/src/cli/utils.d.ts +2 -2
  39. package/dist/src/core/build.d.ts +1 -1
  40. package/dist/src/dev/clientManager.d.ts +7 -0
  41. package/dist/src/dev/configResolver.d.ts +2 -0
  42. package/dist/src/dev/moduleMapper.d.ts +1 -1
  43. package/dist/src/dev/moduleServer.d.ts +2 -1
  44. package/dist/src/dev/pathUtils.d.ts +1 -1
  45. package/dist/src/dev/reactComponentClassifier.d.ts +1 -1
  46. package/dist/src/mobile/androidConformance.d.ts +42 -0
  47. package/dist/src/mobile/androidEmulatorController.d.ts +100 -0
  48. package/dist/src/mobile/androidNativeWatcher.d.ts +19 -0
  49. package/dist/src/mobile/androidRelease.d.ts +51 -0
  50. package/dist/src/mobile/androidWebView.d.ts +46 -0
  51. package/dist/src/mobile/associationFiles.d.ts +49 -0
  52. package/dist/src/mobile/buildMetadata.d.ts +10 -0
  53. package/dist/src/mobile/buildPipeline.d.ts +8 -0
  54. package/dist/src/mobile/buildRelease.d.ts +24 -0
  55. package/dist/src/mobile/capacitorBundle.d.ts +28 -0
  56. package/dist/src/mobile/capacitorProject.d.ts +9 -0
  57. package/dist/src/mobile/client.d.ts +1 -1
  58. package/dist/src/mobile/config.d.ts +17 -0
  59. package/dist/src/mobile/emulatorDoctor.d.ts +25 -0
  60. package/dist/src/mobile/emulatorInstaller.d.ts +41 -0
  61. package/dist/src/mobile/index.d.ts +14 -0
  62. package/dist/src/mobile/iosRelease.d.ts +61 -0
  63. package/dist/src/mobile/materializedBundle.d.ts +14 -0
  64. package/dist/src/mobile/nativeDeepLinks.d.ts +5 -0
  65. package/dist/src/mobile/releaseArtifact.d.ts +1 -0
  66. package/dist/src/mobile/releaseDoctor.d.ts +13 -0
  67. package/dist/src/mobile/releasePublisher.d.ts +130 -0
  68. package/dist/src/mobile/routeMatcher.d.ts +3 -0
  69. package/dist/src/mobile/routeMetadataTransform.d.ts +13 -0
  70. package/dist/src/mobile/shellBootstrap.d.ts +1 -0
  71. package/dist/src/mobile/transport.d.ts +31 -0
  72. package/dist/src/react/hooks/useMediaQuery.d.ts +1 -1
  73. package/dist/src/react/jsxDevRuntimeCompat.d.ts +1 -1
  74. package/dist/src/react/jsxRuntimeCompat.d.ts +7 -0
  75. package/dist/src/react/pageHandler.d.ts +3 -0
  76. package/dist/src/utils/loadConfig.d.ts +1 -0
  77. package/dist/src/utils/logger.d.ts +2 -0
  78. package/dist/src/utils/userAgentFunctions.d.ts +1 -1
  79. package/dist/svelte/index.js +14 -1
  80. package/dist/svelte/index.js.map +3 -3
  81. package/dist/svelte/server.js +14 -1
  82. package/dist/svelte/server.js.map +3 -3
  83. package/dist/types/build.d.ts +45 -0
  84. package/dist/types/cli.d.ts +2 -0
  85. package/dist/types/globals.d.ts +14 -0
  86. package/dist/types/messages.d.ts +12 -0
  87. package/dist/vue/index.js +14 -1
  88. package/dist/vue/index.js.map +3 -3
  89. package/dist/vue/server.js +14 -1
  90. package/dist/vue/server.js.map +3 -3
  91. package/package.json +35 -7
@@ -7,8 +7,20 @@ import {
7
7
  isRuntimeErrorOverlay,
8
8
  showErrorOverlay
9
9
  } from '../errorOverlay';
10
+ import { sendAbsoluteHmrTiming } from '../hmrTiming';
10
11
 
11
- export const handleFullReload = () => {
12
+ export const handleFullReload = (message?: {
13
+ data: { serverDuration?: number };
14
+ timestamp?: number;
15
+ }) => {
16
+ const clientStart = performance.now();
17
+ sendAbsoluteHmrTiming({
18
+ clientStart,
19
+ kind: 'full-reload',
20
+ outcome: 'reloaded',
21
+ serverMs: message?.data.serverDuration,
22
+ updateId: message?.timestamp
23
+ });
12
24
  setTimeout(() => {
13
25
  window.location.reload();
14
26
  }, REBUILD_RELOAD_DELAY_MS);
@@ -33,7 +45,18 @@ export const handleManifest = (message: {
33
45
  window.__HMR_MODULE_UPDATES__ = [];
34
46
  };
35
47
 
36
- const HMR_FRAMEWORKS = ['angular', 'react', 'vue', 'svelte', 'html', 'htmx'];
48
+ const HMR_FRAMEWORKS = [
49
+ 'angular',
50
+ 'assets',
51
+ 'ember',
52
+ 'html',
53
+ 'htmx',
54
+ 'react',
55
+ 'styles',
56
+ 'svelte',
57
+ 'tailwind',
58
+ 'vue'
59
+ ];
37
60
 
38
61
  const mergeRecord = (
39
62
  source: Record<string, string | number>,
@@ -115,12 +138,9 @@ export const handleRebuildComplete = (message: {
115
138
 
116
139
  if (
117
140
  message.data.affectedFrameworks &&
118
- !message.data.affectedFrameworks.includes('angular') &&
119
- !message.data.affectedFrameworks.includes('react') &&
120
- !message.data.affectedFrameworks.includes('html') &&
121
- !message.data.affectedFrameworks.includes('htmx') &&
122
- !message.data.affectedFrameworks.includes('vue') &&
123
- !message.data.affectedFrameworks.includes('svelte')
141
+ !message.data.affectedFrameworks.some((framework) =>
142
+ HMR_FRAMEWORKS.includes(framework)
143
+ )
124
144
  ) {
125
145
  const url = new URL(window.location.href);
126
146
  url.searchParams.set('_cb', Date.now().toString());
@@ -9,43 +9,13 @@ import {
9
9
  restoreScrollState
10
10
  } from '../domState';
11
11
  import { detectCurrentFramework, findIndexPath } from '../frameworkDetect';
12
+ import { sendAbsoluteHmrTiming } from '../hmrTiming';
13
+ import { swapCSSStylesheet } from '../cssUtils';
12
14
 
13
15
  type SvelteHmrWindow = Window & {
14
16
  __SVELTE_HMR_ACCEPT__?: Record<string, (mod: unknown) => void>;
15
17
  };
16
18
 
17
- /* Swap a stylesheet link by matching cssBaseName or framework name */
18
- const swapStylesheet = (
19
- cssUrl: string,
20
- cssBaseName: string,
21
- framework: string
22
- ) => {
23
- let existingLink: HTMLLinkElement | null = null;
24
- document
25
- .querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]')
26
- .forEach((link) => {
27
- const href = link.getAttribute('href') ?? '';
28
- if (href.includes(cssBaseName) || href.includes(framework)) {
29
- existingLink = link;
30
- }
31
- });
32
-
33
- if (!existingLink) {
34
- return;
35
- }
36
-
37
- const capturedExisting: HTMLLinkElement = existingLink;
38
- const newLink = document.createElement('link');
39
- newLink.rel = 'stylesheet';
40
- newLink.href = `${cssUrl}?t=${Date.now()}`;
41
- newLink.onload = () => {
42
- if (capturedExisting && capturedExisting.parentNode) {
43
- capturedExisting.remove();
44
- }
45
- };
46
- document.head.appendChild(newLink);
47
- };
48
-
49
19
  const extractCountFromDOM = () => {
50
20
  const countButton = document.querySelector('button');
51
21
  if (!countButton || !countButton.textContent) {
@@ -222,17 +192,27 @@ export const handleSvelteUpdate = (message: {
222
192
  sourceFile?: string;
223
193
  updateType?: string;
224
194
  };
195
+ timestamp?: number;
225
196
  }) => {
226
197
  const svelteFrameworkCheck = detectCurrentFramework();
227
198
  if (svelteFrameworkCheck !== 'svelte') return;
228
199
 
229
200
  /* CSS-only update: hot-swap stylesheet, no remount needed */
230
201
  if (message.data.updateType === 'css-only' && message.data.cssUrl) {
231
- swapStylesheet(
202
+ const clientStart = performance.now();
203
+ const cssBaseName = message.data.cssBaseName || '';
204
+ void swapCSSStylesheet(
232
205
  message.data.cssUrl,
233
- message.data.cssBaseName || '',
234
- 'svelte'
235
- );
206
+ (href) => href.includes(cssBaseName) || href.includes('svelte')
207
+ ).then((applied) => {
208
+ sendAbsoluteHmrTiming({
209
+ clientStart,
210
+ kind: 'css',
211
+ outcome: applied ? 'applied' : 'failed',
212
+ serverMs: message.data.serverDuration,
213
+ updateId: message.timestamp
214
+ });
215
+ });
236
216
 
237
217
  return;
238
218
  }
@@ -255,10 +235,10 @@ export const handleSvelteUpdate = (message: {
255
235
 
256
236
  /* CSS pre-update: swap stylesheet BEFORE importing to prevent FOUC */
257
237
  if (message.data.cssUrl) {
258
- swapStylesheet(
238
+ const cssBaseName = message.data.cssBaseName || '';
239
+ void swapCSSStylesheet(
259
240
  message.data.cssUrl,
260
- message.data.cssBaseName || '',
261
- 'svelte'
241
+ (href) => href.includes(cssBaseName) || href.includes('svelte')
262
242
  );
263
243
  }
264
244
 
@@ -340,23 +320,26 @@ export const handleSvelteUpdate = (message: {
340
320
  }
341
321
  window.__HMR_PRESERVED_STATE__ = undefined;
342
322
 
343
- if (
344
- window.__HMR_WS__ &&
345
- message.data.serverDuration !== undefined
346
- ) {
347
- const clientMs = Math.round(
348
- performance.now() - clientStart
349
- );
350
- const total = (message.data.serverDuration ?? 0) + clientMs;
351
- window.__HMR_WS__.send(
352
- JSON.stringify({ duration: total, type: 'hmr-timing' })
353
- );
323
+ if (message.data.serverDuration !== undefined) {
324
+ sendAbsoluteHmrTiming({
325
+ clientStart,
326
+ kind: 'component',
327
+ serverMs: message.data.serverDuration,
328
+ updateId: message.timestamp
329
+ });
354
330
  }
355
331
 
356
332
  return undefined;
357
333
  })
358
334
  .catch((err: unknown) => {
359
335
  console.warn('[HMR] Svelte HMR failed, reloading:', err);
336
+ sendAbsoluteHmrTiming({
337
+ clientStart,
338
+ kind: 'component',
339
+ outcome: 'reloaded',
340
+ serverMs: message.data.serverDuration,
341
+ updateId: message.timestamp
342
+ });
360
343
  window.location.reload();
361
344
  });
362
345
 
@@ -4,6 +4,8 @@ import type {} from '../../../types/globals';
4
4
  import type { VueComponentInstance, VueVNode } from '../../../types/vue';
5
5
  import { saveDOMState, restoreDOMState } from '../domState';
6
6
  import { detectCurrentFramework, findIndexPath } from '../frameworkDetect';
7
+ import { sendAbsoluteHmrTiming } from '../hmrTiming';
8
+ import { swapCSSStylesheet } from '../cssUtils';
7
9
 
8
10
  /* Collect reactive value from a setup state entry into the target record */
9
11
  const collectSetupValue = (
@@ -67,38 +69,6 @@ const extractChildComponentState = (
67
69
  walkVNode(instance.subTree, state);
68
70
  };
69
71
 
70
- /* Find an existing stylesheet link matching the given base name */
71
- const findMatchingStylesheetLink = (cssBaseName: string) => {
72
- let found: HTMLLinkElement | null = null;
73
- document
74
- .querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]')
75
- .forEach((link) => {
76
- const href = link.getAttribute('href') ?? '';
77
- if (cssBaseName && href.includes(cssBaseName)) {
78
- found = link;
79
- }
80
- });
81
-
82
- return found;
83
- };
84
-
85
- /* Swap a stylesheet link with a new one, removing the old on load */
86
- const swapStylesheet = (cssUrl: string, cssBaseName: string) => {
87
- const existingLink = findMatchingStylesheetLink(cssBaseName);
88
- if (!existingLink) return;
89
-
90
- const capturedExisting: HTMLLinkElement = existingLink;
91
- const newLink = document.createElement('link');
92
- newLink.rel = 'stylesheet';
93
- newLink.href = `${cssUrl}?t=${Date.now()}`;
94
- newLink.onload = function () {
95
- if (capturedExisting && capturedExisting.parentNode) {
96
- capturedExisting.remove();
97
- }
98
- };
99
- document.head.appendChild(newLink);
100
- };
101
-
102
72
  /* Extract Vue reactive state from app instance */
103
73
  const extractVueAppState = (vuePreservedState: Record<string, unknown>) => {
104
74
  if (!window.__VUE_APP__ || !window.__VUE_APP__._instance) return;
@@ -163,12 +133,26 @@ export const handleVueUpdate = (message: {
163
133
  sourceFile?: string;
164
134
  updateType?: string;
165
135
  };
136
+ timestamp?: number;
166
137
  }) => {
167
138
  const vueFrameworkCheck = detectCurrentFramework();
168
139
  if (vueFrameworkCheck !== 'vue') return;
169
140
 
170
141
  if (message.data.updateType === 'css-only' && message.data.cssUrl) {
171
- swapStylesheet(message.data.cssUrl, message.data.cssBaseName || '');
142
+ const clientStart = performance.now();
143
+ const cssBaseName = message.data.cssBaseName || '';
144
+ void swapCSSStylesheet(
145
+ message.data.cssUrl,
146
+ (href) => Boolean(cssBaseName) && href.includes(cssBaseName)
147
+ ).then((applied) => {
148
+ sendAbsoluteHmrTiming({
149
+ clientStart,
150
+ kind: 'css',
151
+ outcome: applied ? 'applied' : 'failed',
152
+ serverMs: message.data.serverDuration,
153
+ updateId: message.timestamp
154
+ });
155
+ });
172
156
 
173
157
  return;
174
158
  }
@@ -224,17 +208,13 @@ export const handleVueUpdate = (message: {
224
208
  }
225
209
  sessionStorage.removeItem('__HMR_ACTIVE__');
226
210
 
227
- if (
228
- window.__HMR_WS__ &&
229
- message.data.serverDuration !== undefined
230
- ) {
231
- const clientMs = Math.round(
232
- performance.now() - clientStart
233
- );
234
- const total = (message.data.serverDuration ?? 0) + clientMs;
235
- window.__HMR_WS__.send(
236
- JSON.stringify({ duration: total, type: 'hmr-timing' })
237
- );
211
+ if (message.data.serverDuration !== undefined) {
212
+ sendAbsoluteHmrTiming({
213
+ clientStart,
214
+ kind: 'component',
215
+ serverMs: message.data.serverDuration,
216
+ updateId: message.timestamp
217
+ });
238
218
  }
239
219
 
240
220
  return undefined;
@@ -242,6 +222,13 @@ export const handleVueUpdate = (message: {
242
222
  .catch((err: unknown) => {
243
223
  console.warn('[HMR] Vue HMR failed, reloading:', err);
244
224
  sessionStorage.removeItem('__HMR_ACTIVE__');
225
+ sendAbsoluteHmrTiming({
226
+ clientStart,
227
+ kind: 'component',
228
+ outcome: 'reloaded',
229
+ serverMs: message.data.serverDuration,
230
+ updateId: message.timestamp
231
+ });
245
232
  window.location.reload();
246
233
  });
247
234
 
@@ -250,7 +237,11 @@ export const handleVueUpdate = (message: {
250
237
 
251
238
  /* CSS pre-update: swap stylesheet BEFORE unmounting to prevent FOUC */
252
239
  if (message.data.cssUrl) {
253
- swapStylesheet(message.data.cssUrl, message.data.cssBaseName || '');
240
+ const cssBaseName = message.data.cssBaseName || '';
241
+ void swapCSSStylesheet(
242
+ message.data.cssUrl,
243
+ (href) => Boolean(cssBaseName) && href.includes(cssBaseName)
244
+ );
254
245
  }
255
246
 
256
247
  /* Unmount old Vue app but keep DOM visually intact during async import.
@@ -12,6 +12,11 @@ import {
12
12
  WEBSOCKET_NORMAL_CLOSURE
13
13
  } from './constants';
14
14
  import { detectCurrentFramework } from './frameworkDetect';
15
+ import {
16
+ absoluteHmrClientTarget,
17
+ restoreAbsoluteHmrApply,
18
+ sendAbsoluteHmrTiming
19
+ } from './hmrTiming';
15
20
  import { hideErrorOverlay, showErrorOverlay } from './errorOverlay';
16
21
  import {
17
22
  dispatchAngularComponentRemount,
@@ -39,6 +44,8 @@ const isStringRecord = (value: unknown): value is Record<string, string> =>
39
44
  isRecord(value) &&
40
45
  Object.values(value).every((entry) => typeof entry === 'string');
41
46
 
47
+ restoreAbsoluteHmrApply();
48
+
42
49
  /* Lightweight "server disconnected" banner. When the dev server is
43
50
  * genuinely down (process restarting or crashed) the browser would
44
51
  * otherwise show its own blank "site can't be reached" page — this keeps
@@ -77,6 +84,7 @@ const hideConnectionLostBanner = () => {
77
84
 
78
85
  // Initialize HMR globals
79
86
  if (typeof window !== 'undefined') {
87
+ window.__ABS_HMR_TARGET__ = absoluteHmrClientTarget();
80
88
  installAngularRemountGlobal();
81
89
  if (!window.__HMR_MANIFEST__) {
82
90
  window.__HMR_MANIFEST__ = {};
@@ -119,6 +127,7 @@ const hmrUpdateTypes = new Set([
119
127
  'angular:component-update',
120
128
  'angular:component-remount',
121
129
  'angular:rebootstrap',
130
+ 'angular-update',
122
131
  'react-update',
123
132
  'html-update',
124
133
  'htmx-update',
@@ -134,6 +143,7 @@ type HMRMessage = {
134
143
  affectedFrameworks?: string[];
135
144
  column?: number;
136
145
  error?: string;
146
+ fastRefreshSupported?: boolean;
137
147
  file?: string;
138
148
  framework?: string;
139
149
  hasCSSChanges?: boolean;
@@ -149,9 +159,23 @@ type HMRMessage = {
149
159
  serverDuration?: number;
150
160
  serverVersions?: Record<string, number>;
151
161
  };
162
+ timestamp?: number;
152
163
  type: string;
153
164
  };
154
165
 
166
+ const handleStylesheetUpdate = (message: HMRMessage) => {
167
+ const clientStart = performance.now();
168
+ void reloadCSSStylesheets(message.data.manifest ?? {}).then((applied) => {
169
+ sendAbsoluteHmrTiming({
170
+ clientStart,
171
+ kind: 'css',
172
+ outcome: applied ? 'applied' : 'failed',
173
+ serverMs: message.data.serverDuration,
174
+ updateId: message.timestamp
175
+ });
176
+ });
177
+ };
178
+
155
179
  const handleHMRMessage = (message: HMRMessage) => {
156
180
  if (hmrUpdateTypes.has(message.type)) {
157
181
  hmrState.isHMRUpdating = true;
@@ -208,12 +232,13 @@ const handleHMRMessage = (message: HMRMessage) => {
208
232
  const { data } = message;
209
233
  if (isRecord(data)) {
210
234
  const id = Reflect.get(data, 'id');
211
- const timestamp = Reflect.get(data, 'timestamp');
212
235
  if (typeof id !== 'string') break;
213
236
  dispatchAngularComponentUpdate({
214
237
  id,
215
238
  timestamp:
216
- typeof timestamp === 'number' ? timestamp : Date.now()
239
+ typeof message.timestamp === 'number'
240
+ ? message.timestamp
241
+ : Date.now()
217
242
  });
218
243
  }
219
244
  break;
@@ -230,12 +255,13 @@ const handleHMRMessage = (message: HMRMessage) => {
230
255
  const { data } = message;
231
256
  if (isRecord(data)) {
232
257
  const id = Reflect.get(data, 'id');
233
- const timestamp = Reflect.get(data, 'timestamp');
234
258
  if (typeof id !== 'string') break;
235
259
  dispatchAngularComponentRemount({
236
260
  id,
237
261
  timestamp:
238
- typeof timestamp === 'number' ? timestamp : Date.now()
262
+ typeof message.timestamp === 'number'
263
+ ? message.timestamp
264
+ : Date.now()
239
265
  });
240
266
  }
241
267
  break;
@@ -278,12 +304,17 @@ const handleHMRMessage = (message: HMRMessage) => {
278
304
  handleRebuildError(message);
279
305
  break;
280
306
  case 'full-reload':
281
- handleFullReload();
307
+ handleFullReload(message);
282
308
  break;
283
309
  case 'pong':
284
310
  break;
285
311
  case 'style-update':
286
- reloadCSSStylesheets(message.data.manifest ?? {});
312
+ handleStylesheetUpdate(message);
313
+ break;
314
+ case 'angular-update':
315
+ if (detectCurrentFramework() === 'angular') {
316
+ handleStylesheetUpdate(message);
317
+ }
287
318
  break;
288
319
  default:
289
320
  break;
@@ -311,6 +342,7 @@ if (!(window.__HMR_WS__ && window.__HMR_WS__.readyState === WebSocket.OPEN)) {
311
342
  wsc.send(
312
343
  JSON.stringify({
313
344
  framework: currentFramework,
345
+ target: absoluteHmrClientTarget(),
314
346
  type: 'ready'
315
347
  })
316
348
  );
@@ -0,0 +1,141 @@
1
+ import type {} from '../../types/globals';
2
+ import type {
3
+ HMRApplyKind,
4
+ HMRApplyOutcome,
5
+ HMRClientTarget
6
+ } from '../../../types/messages';
7
+
8
+ const MAX_RETAINED_HMR_APPLIES = 50;
9
+ const HMR_APPLY_STORAGE_KEY = '__absolute_hmr_last_apply__';
10
+
11
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
12
+ typeof value === 'object' && value !== null;
13
+
14
+ const isHmrTarget = (value: unknown): value is HMRClientTarget =>
15
+ value === 'capacitor-android' ||
16
+ value === 'capacitor-ios' ||
17
+ value === 'capacitor-native' ||
18
+ value === 'web';
19
+
20
+ const isHmrKind = (value: unknown): value is HMRApplyKind =>
21
+ value === 'component' ||
22
+ value === 'css' ||
23
+ value === 'full-reload' ||
24
+ value === 'html' ||
25
+ value === 'htmx' ||
26
+ value === 'script';
27
+
28
+ const isHmrOutcome = (value: unknown): value is HMRApplyOutcome =>
29
+ value === 'applied' || value === 'failed' || value === 'reloaded';
30
+
31
+ type HmrApply = NonNullable<Window['__ABS_HMR_LAST_APPLY__']>;
32
+
33
+ const isHmrApply = (value: unknown): value is HmrApply =>
34
+ isRecord(value) &&
35
+ typeof value.clientMs === 'number' &&
36
+ typeof value.duration === 'number' &&
37
+ (value.kind === undefined || isHmrKind(value.kind)) &&
38
+ isHmrOutcome(value.outcome) &&
39
+ typeof value.serverMs === 'number' &&
40
+ isHmrTarget(value.target) &&
41
+ (value.updateId === undefined || typeof value.updateId === 'number');
42
+
43
+ const getSessionStorage = () => {
44
+ const storage = Reflect.get(window, 'sessionStorage');
45
+ if (!isRecord(storage)) return undefined;
46
+
47
+ return storage;
48
+ };
49
+
50
+ const persistHmrApply = (timing: HmrApply) => {
51
+ try {
52
+ const storage = getSessionStorage();
53
+ const setItem = storage && Reflect.get(storage, 'setItem');
54
+ if (typeof setItem !== 'function') return;
55
+ Reflect.apply(setItem, storage, [
56
+ HMR_APPLY_STORAGE_KEY,
57
+ JSON.stringify(timing)
58
+ ]);
59
+ } catch {
60
+ // Storage may be unavailable in sandboxed or privacy-restricted documents.
61
+ }
62
+ };
63
+
64
+ export const absoluteHmrClientTarget = () => {
65
+ const currentLocation = Reflect.get(globalThis, 'location');
66
+ if (typeof currentLocation === 'object' && currentLocation !== null) {
67
+ const search = Reflect.get(currentLocation, 'search');
68
+ if (typeof search === 'string') {
69
+ const marker = new URLSearchParams(search).get('__absolute_target');
70
+ if (marker === 'capacitor-android') return marker;
71
+ if (marker === 'capacitor-ios') return marker;
72
+ }
73
+ }
74
+ const capacitor = Reflect.get(globalThis, 'Capacitor');
75
+ if (!isRecord(capacitor)) return 'web';
76
+ const getPlatform = Reflect.get(capacitor, 'getPlatform');
77
+ const isNativePlatform = Reflect.get(capacitor, 'isNativePlatform');
78
+ const platform =
79
+ typeof getPlatform === 'function'
80
+ ? Reflect.apply(getPlatform, capacitor, [])
81
+ : undefined;
82
+ if (platform === 'android') return 'capacitor-android';
83
+ if (platform === 'ios') return 'capacitor-ios';
84
+ if (
85
+ typeof isNativePlatform === 'function' &&
86
+ Reflect.apply(isNativePlatform, capacitor, []) === true
87
+ ) {
88
+ return 'capacitor-native';
89
+ }
90
+
91
+ return 'web';
92
+ };
93
+
94
+ export const restoreAbsoluteHmrApply = () => {
95
+ try {
96
+ const storage = getSessionStorage();
97
+ const getItem = storage && Reflect.get(storage, 'getItem');
98
+ if (typeof getItem !== 'function') return;
99
+ const serialized = Reflect.apply(getItem, storage, [
100
+ HMR_APPLY_STORAGE_KEY
101
+ ]);
102
+ if (typeof serialized !== 'string') return;
103
+ const timing: unknown = JSON.parse(serialized);
104
+ if (!isHmrApply(timing)) return;
105
+ window.__ABS_HMR_LAST_APPLY__ = timing;
106
+ window.__ABS_HMR_APPLIES__ = [timing];
107
+ } catch {
108
+ // A malformed or inaccessible entry should never affect HMR startup.
109
+ }
110
+ };
111
+
112
+ type SendHmrTimingOptions = {
113
+ clientStart: number;
114
+ kind?: HMRApplyKind;
115
+ outcome?: HMRApplyOutcome;
116
+ serverMs?: number;
117
+ updateId?: number;
118
+ };
119
+
120
+ export const sendAbsoluteHmrTiming = (options: SendHmrTimingOptions) => {
121
+ const clientMs = Math.round(performance.now() - options.clientStart);
122
+ const serverMs = options.serverMs ?? 0;
123
+ const timing = {
124
+ clientMs,
125
+ duration: serverMs + clientMs,
126
+ kind: options.kind,
127
+ outcome: options.outcome ?? 'applied',
128
+ serverMs,
129
+ target: absoluteHmrClientTarget(),
130
+ updateId: options.updateId
131
+ } satisfies NonNullable<Window['__ABS_HMR_LAST_APPLY__']>;
132
+ window.__ABS_HMR_LAST_APPLY__ = timing;
133
+ const applies = (window.__ABS_HMR_APPLIES__ ??= []);
134
+ applies.push(timing);
135
+ if (applies.length > MAX_RETAINED_HMR_APPLIES) {
136
+ applies.splice(0, applies.length - MAX_RETAINED_HMR_APPLIES);
137
+ }
138
+ persistHmrApply(timing);
139
+ if (!window.__HMR_WS__) return;
140
+ window.__HMR_WS__.send(JSON.stringify({ ...timing, type: 'hmr-timing' }));
141
+ };
@@ -18,19 +18,28 @@ var removeEntryCopy = (path) => {
18
18
  unlinkSync(path);
19
19
  } catch {}
20
20
  };
21
- for (const name of readdirSync(entryDir)) {
22
- if (!name.startsWith(copyPrefix) || !name.endsWith(entryExtension)) {
23
- continue;
21
+ var isHotReevaluation = globalThis.__absoluteEntryCopies !== undefined;
22
+ if (!isHotReevaluation) {
23
+ for (const name of readdirSync(entryDir)) {
24
+ if (!name.startsWith(copyPrefix) || !name.endsWith(entryExtension)) {
25
+ continue;
26
+ }
27
+ removeEntryCopy(join(entryDir, name));
24
28
  }
25
- removeEntryCopy(join(entryDir, name));
26
29
  }
27
- var bootstrapCopy = join(entryDir, `${copyPrefix}${process.pid}-bootstrap${entryExtension}`);
30
+ var bootstrapSequence = (globalThis.__absoluteEntryBootstrapSequence ?? 0) + 1;
31
+ globalThis.__absoluteEntryBootstrapSequence = bootstrapSequence;
32
+ var bootstrapCopy = join(entryDir, `${copyPrefix}${process.pid}-bootstrap-${bootstrapSequence}${entryExtension}`);
28
33
  copyFileSync(entryPath, bootstrapCopy);
29
- var entryCopies = new Set([bootstrapCopy]);
34
+ var entryCopies = globalThis.__absoluteEntryCopies ?? new Set;
35
+ entryCopies.add(bootstrapCopy);
30
36
  globalThis.__absoluteEntryCopies = entryCopies;
31
- process.on("exit", () => {
32
- for (const path of entryCopies) {
33
- removeEntryCopy(path);
34
- }
35
- });
37
+ if (!globalThis.__absoluteEntryCleanupRegistered) {
38
+ globalThis.__absoluteEntryCleanupRegistered = true;
39
+ process.on("exit", () => {
40
+ for (const path of entryCopies) {
41
+ removeEntryCopy(path);
42
+ }
43
+ });
44
+ }
36
45
  await import(bootstrapCopy);