@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.
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/angular/index.js +14 -1
- package/dist/angular/index.js.map +3 -3
- package/dist/angular/server.js +14 -1
- package/dist/angular/server.js.map +3 -3
- package/dist/build.js +206 -47
- package/dist/build.js.map +20 -19
- package/dist/cli/config/server.js +2 -2
- package/dist/cli/index.js +16506 -11326
- package/dist/dev/client/cssUtils.ts +98 -41
- package/dist/dev/client/handlers/html.ts +75 -15
- package/dist/dev/client/handlers/htmx.ts +43 -8
- package/dist/dev/client/handlers/react.ts +114 -32
- package/dist/dev/client/handlers/rebuild.ts +28 -8
- package/dist/dev/client/handlers/svelte.ts +33 -50
- package/dist/dev/client/handlers/vue.ts +36 -45
- package/dist/dev/client/hmrClient.ts +38 -6
- package/dist/dev/client/hmrTiming.ts +141 -0
- package/dist/dev/serverBootstrap.js +20 -11
- package/dist/index.js +953 -423
- package/dist/index.js.map +27 -23
- package/dist/mobile/browser.js.map +1 -1
- package/dist/mobile/index.js +2667 -260
- package/dist/mobile/index.js.map +27 -7
- package/dist/react/index.js +16 -3
- package/dist/react/index.js.map +4 -4
- package/dist/react/jsxDevRuntimeCompat.js +22 -2
- package/dist/react/jsxDevRuntimeCompat.js.map +5 -4
- package/dist/react/jsxRuntimeCompat.js +90 -0
- package/dist/react/jsxRuntimeCompat.js.map +10 -0
- package/dist/react/server.js +16 -3
- package/dist/react/server.js.map +4 -4
- package/dist/src/build/compileVue.d.ts +1 -1
- package/dist/src/build/resolveBuildMode.d.ts +7 -0
- package/dist/src/cli/scripts/dev.d.ts +4 -1
- package/dist/src/cli/scripts/mobile.d.ts +1 -0
- package/dist/src/cli/utils.d.ts +2 -2
- package/dist/src/core/build.d.ts +1 -1
- package/dist/src/dev/clientManager.d.ts +7 -0
- package/dist/src/dev/configResolver.d.ts +2 -0
- package/dist/src/dev/moduleMapper.d.ts +1 -1
- package/dist/src/dev/moduleServer.d.ts +2 -1
- package/dist/src/dev/pathUtils.d.ts +1 -1
- package/dist/src/dev/reactComponentClassifier.d.ts +1 -1
- package/dist/src/mobile/androidConformance.d.ts +42 -0
- package/dist/src/mobile/androidEmulatorController.d.ts +100 -0
- package/dist/src/mobile/androidNativeWatcher.d.ts +19 -0
- package/dist/src/mobile/androidRelease.d.ts +51 -0
- package/dist/src/mobile/androidWebView.d.ts +46 -0
- package/dist/src/mobile/associationFiles.d.ts +49 -0
- package/dist/src/mobile/buildMetadata.d.ts +10 -0
- package/dist/src/mobile/buildPipeline.d.ts +8 -0
- package/dist/src/mobile/buildRelease.d.ts +24 -0
- package/dist/src/mobile/capacitorBundle.d.ts +28 -0
- package/dist/src/mobile/capacitorProject.d.ts +9 -0
- package/dist/src/mobile/client.d.ts +1 -1
- package/dist/src/mobile/config.d.ts +17 -0
- package/dist/src/mobile/emulatorDoctor.d.ts +25 -0
- package/dist/src/mobile/emulatorInstaller.d.ts +41 -0
- package/dist/src/mobile/index.d.ts +14 -0
- package/dist/src/mobile/iosRelease.d.ts +61 -0
- package/dist/src/mobile/materializedBundle.d.ts +14 -0
- package/dist/src/mobile/nativeDeepLinks.d.ts +5 -0
- package/dist/src/mobile/releaseArtifact.d.ts +1 -0
- package/dist/src/mobile/releaseDoctor.d.ts +13 -0
- package/dist/src/mobile/releasePublisher.d.ts +130 -0
- package/dist/src/mobile/routeMatcher.d.ts +3 -0
- package/dist/src/mobile/routeMetadataTransform.d.ts +13 -0
- package/dist/src/mobile/shellBootstrap.d.ts +1 -0
- package/dist/src/mobile/transport.d.ts +31 -0
- package/dist/src/react/hooks/useMediaQuery.d.ts +1 -1
- package/dist/src/react/jsxDevRuntimeCompat.d.ts +1 -1
- package/dist/src/react/jsxRuntimeCompat.d.ts +7 -0
- package/dist/src/react/pageHandler.d.ts +3 -0
- package/dist/src/utils/loadConfig.d.ts +1 -0
- package/dist/src/utils/logger.d.ts +2 -0
- package/dist/src/utils/userAgentFunctions.d.ts +1 -1
- package/dist/svelte/index.js +14 -1
- package/dist/svelte/index.js.map +3 -3
- package/dist/svelte/server.js +14 -1
- package/dist/svelte/server.js.map +3 -3
- package/dist/types/build.d.ts +45 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/globals.d.ts +14 -0
- package/dist/types/messages.d.ts +12 -0
- package/dist/vue/index.js +14 -1
- package/dist/vue/index.js.map +3 -3
- package/dist/vue/server.js +14 -1
- package/dist/vue/server.js.map +3 -3
- package/package.json +35 -7
|
@@ -149,34 +149,90 @@ const findManifestHref = (
|
|
|
149
149
|
return null;
|
|
150
150
|
};
|
|
151
151
|
|
|
152
|
+
const replaceStylesheetLink = (
|
|
153
|
+
existingLink: HTMLLinkElement,
|
|
154
|
+
newHref: string
|
|
155
|
+
) => {
|
|
156
|
+
const replacement = existingLink.cloneNode(true);
|
|
157
|
+
if (!(replacement instanceof HTMLLinkElement)) {
|
|
158
|
+
return Promise.resolve(false);
|
|
159
|
+
}
|
|
160
|
+
const { promise, resolve } = Promise.withResolvers<boolean>();
|
|
161
|
+
let settled = false;
|
|
162
|
+
const finish = (applied: boolean) => {
|
|
163
|
+
if (settled) return;
|
|
164
|
+
settled = true;
|
|
165
|
+
if (applied) existingLink.remove();
|
|
166
|
+
else replacement.remove();
|
|
167
|
+
resolve(applied);
|
|
168
|
+
};
|
|
169
|
+
replacement.onload = () => finish(true);
|
|
170
|
+
replacement.onerror = () => finish(false);
|
|
171
|
+
replacement.href = newHref;
|
|
172
|
+
existingLink.after(replacement);
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
if (replacement.sheet) finish(true);
|
|
175
|
+
}, CSS_SHEET_READY_TIMEOUT_MS);
|
|
176
|
+
setTimeout(
|
|
177
|
+
() => finish(Boolean(replacement.sheet)),
|
|
178
|
+
CSS_MAX_PARSE_TIMEOUT_MS
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
return promise;
|
|
182
|
+
};
|
|
183
|
+
|
|
152
184
|
const updateStylesheetLink = (
|
|
153
185
|
link: Element,
|
|
154
186
|
manifest: Record<string, string>
|
|
155
187
|
) => {
|
|
156
|
-
if (!(link instanceof HTMLLinkElement)) return;
|
|
188
|
+
if (!(link instanceof HTMLLinkElement)) return Promise.resolve(true);
|
|
157
189
|
const href = link.getAttribute('href');
|
|
158
|
-
if (!href || href.includes('htmx.min.js')) return;
|
|
190
|
+
if (!href || href.includes('htmx.min.js')) return Promise.resolve(true);
|
|
159
191
|
|
|
160
192
|
let newHref: string | null = null;
|
|
161
193
|
if (manifest) {
|
|
162
194
|
const baseName = getCSSBaseName(href);
|
|
163
195
|
newHref = findManifestHref(manifest, baseName);
|
|
164
196
|
}
|
|
197
|
+
const currentUrl = new URL(href, window.location.href);
|
|
198
|
+
if (!newHref && currentUrl.origin !== window.location.origin) {
|
|
199
|
+
return Promise.resolve(true);
|
|
200
|
+
}
|
|
165
201
|
|
|
202
|
+
let replacementHref: string;
|
|
166
203
|
if (newHref && newHref !== href) {
|
|
167
|
-
|
|
204
|
+
replacementHref = `${newHref}?t=${Date.now()}`;
|
|
168
205
|
} else {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
link.href = url.toString();
|
|
206
|
+
currentUrl.searchParams.set('t', Date.now().toString());
|
|
207
|
+
replacementHref = currentUrl.toString();
|
|
172
208
|
}
|
|
209
|
+
|
|
210
|
+
return replaceStylesheetLink(link, replacementHref);
|
|
173
211
|
};
|
|
174
212
|
|
|
175
213
|
export const reloadCSSStylesheets = (manifest: Record<string, string>) => {
|
|
176
|
-
const stylesheets =
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
214
|
+
const stylesheets = Array.from(
|
|
215
|
+
document.querySelectorAll('link[rel="stylesheet"]')
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
return Promise.all(
|
|
219
|
+
stylesheets.map((link) => updateStylesheetLink(link, manifest))
|
|
220
|
+
).then((results) => results.every(Boolean));
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const swapCSSStylesheet = (
|
|
224
|
+
cssUrl: string,
|
|
225
|
+
matches: (href: string) => boolean
|
|
226
|
+
) => {
|
|
227
|
+
const existingLink = Array.from(
|
|
228
|
+
document.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]')
|
|
229
|
+
).find((link) => matches(link.getAttribute('href') ?? ''));
|
|
230
|
+
if (!existingLink) return Promise.resolve(false);
|
|
231
|
+
|
|
232
|
+
return replaceStylesheetLink(
|
|
233
|
+
existingLink,
|
|
234
|
+
`${cssUrl}${cssUrl.includes('?') ? '&' : '?'}t=${Date.now()}`
|
|
235
|
+
);
|
|
180
236
|
};
|
|
181
237
|
|
|
182
238
|
const createCSSLoadPromise = (
|
|
@@ -268,44 +324,45 @@ const chainRAF = (depth: number, callback: () => void) => {
|
|
|
268
324
|
activates new CSS, removes old CSS. Handles first-update delay. */
|
|
269
325
|
export const waitForCSSAndUpdate = (
|
|
270
326
|
cssResult: CSSUpdateResult,
|
|
271
|
-
updateBody: () => void
|
|
327
|
+
updateBody: () => void | Promise<void>
|
|
272
328
|
) => {
|
|
273
329
|
const { linksToActivate, linksToRemove, linksToWaitFor } = cssResult;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
330
|
+
const { promise, reject, resolve } = Promise.withResolvers<void>();
|
|
331
|
+
const doUpdate = () => {
|
|
332
|
+
chainRAF(RAF_BATCH_COUNT, () => {
|
|
333
|
+
let updateResult: void | Promise<void>;
|
|
334
|
+
try {
|
|
335
|
+
updateResult = updateBody();
|
|
336
|
+
activateLinks(linksToActivate);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
reject(error);
|
|
339
|
+
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
void Promise.resolve(updateResult).then(
|
|
343
|
+
() => {
|
|
281
344
|
requestAnimationFrame(() => {
|
|
282
345
|
removeLinks(linksToRemove);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
346
|
+
hmrState.isFirstHMRUpdate = false;
|
|
347
|
+
resolve();
|
|
286
348
|
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return undefined;
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const doUpdate = function () {
|
|
297
|
-
chainRAF(RAF_BATCH_COUNT, () => {
|
|
298
|
-
updateBody();
|
|
299
|
-
requestAnimationFrame(() => {
|
|
300
|
-
removeLinks(linksToRemove);
|
|
301
|
-
});
|
|
349
|
+
},
|
|
350
|
+
(error: unknown) => reject(error)
|
|
351
|
+
);
|
|
302
352
|
});
|
|
303
353
|
};
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
354
|
+
const scheduleUpdate = () => {
|
|
355
|
+
if (hmrState.isFirstHMRUpdate || linksToWaitFor.length > 0) {
|
|
356
|
+
setTimeout(doUpdate, DOM_UPDATE_DELAY_MS);
|
|
357
|
+
} else {
|
|
358
|
+
doUpdate();
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
if (linksToWaitFor.length > 0) {
|
|
362
|
+
void Promise.all(linksToWaitFor).then(scheduleUpdate, reject);
|
|
308
363
|
} else {
|
|
309
|
-
|
|
364
|
+
scheduleUpdate();
|
|
310
365
|
}
|
|
366
|
+
|
|
367
|
+
return promise;
|
|
311
368
|
};
|
|
@@ -17,6 +17,7 @@ import { detectCurrentFramework } from '../frameworkDetect';
|
|
|
17
17
|
import type { ScriptInfo } from '../../../types/client';
|
|
18
18
|
import { restoreDOMChanges, snapshotDOMChanges } from '../domTracker';
|
|
19
19
|
import { hmrState } from '../hmrState';
|
|
20
|
+
import { sendAbsoluteHmrTiming } from '../hmrTiming';
|
|
20
21
|
|
|
21
22
|
const parseHTMLMessage = (
|
|
22
23
|
html: string | { body?: string; head?: string } | null | undefined
|
|
@@ -57,11 +58,10 @@ const handleHTMLBodyWithHead = (
|
|
|
57
58
|
|
|
58
59
|
const cssResult = processCSSLinks(htmlHead);
|
|
59
60
|
|
|
60
|
-
const updateBodyAfterCSS = () =>
|
|
61
|
+
const updateBodyAfterCSS = () =>
|
|
61
62
|
updateHTMLBody(htmlBody, htmlDomState, document.body);
|
|
62
|
-
};
|
|
63
63
|
|
|
64
|
-
waitForCSSAndUpdate(cssResult, updateBodyAfterCSS);
|
|
64
|
+
return waitForCSSAndUpdate(cssResult, updateBodyAfterCSS);
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
const handleHTMLBodyWithoutHead = (
|
|
@@ -72,17 +72,21 @@ const handleHTMLBodyWithoutHead = (
|
|
|
72
72
|
if (!container) {
|
|
73
73
|
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
74
74
|
|
|
75
|
-
return;
|
|
75
|
+
return Promise.resolve();
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
updateHTMLBodyDirect(htmlBody, htmlDomState, container);
|
|
78
|
+
const updated = updateHTMLBodyDirect(htmlBody, htmlDomState, container);
|
|
79
79
|
restoreDOMState(container, htmlDomState);
|
|
80
|
+
|
|
81
|
+
return updated;
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
export const handleHTMLUpdate = (message: {
|
|
83
85
|
data: {
|
|
84
86
|
html?: string | { body?: string; head?: string } | null;
|
|
87
|
+
serverDuration?: number;
|
|
85
88
|
};
|
|
89
|
+
timestamp?: number;
|
|
86
90
|
}) => {
|
|
87
91
|
const htmlFrameworkCheck = detectCurrentFramework();
|
|
88
92
|
if (htmlFrameworkCheck !== 'html') {
|
|
@@ -94,6 +98,7 @@ export const handleHTMLUpdate = (message: {
|
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
sessionStorage.setItem('__HMR_ACTIVE__', 'true');
|
|
101
|
+
const clientStart = performance.now();
|
|
97
102
|
|
|
98
103
|
const htmlDomState = saveDOMState(document.body);
|
|
99
104
|
const { body: htmlBody, head: htmlHead } = parseHTMLMessage(
|
|
@@ -102,18 +107,48 @@ export const handleHTMLUpdate = (message: {
|
|
|
102
107
|
|
|
103
108
|
if (!htmlBody) {
|
|
104
109
|
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
110
|
+
sendAbsoluteHmrTiming({
|
|
111
|
+
clientStart,
|
|
112
|
+
kind: 'html',
|
|
113
|
+
outcome: 'failed',
|
|
114
|
+
serverMs: message.data.serverDuration,
|
|
115
|
+
updateId: message.timestamp
|
|
116
|
+
});
|
|
105
117
|
|
|
106
118
|
return;
|
|
107
119
|
}
|
|
108
120
|
|
|
109
|
-
|
|
110
|
-
handleHTMLBodyWithHead(htmlBody, htmlHead, htmlDomState)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
const update = htmlHead
|
|
122
|
+
? handleHTMLBodyWithHead(htmlBody, htmlHead, htmlDomState)
|
|
123
|
+
: handleHTMLBodyWithoutHead(htmlBody, htmlDomState);
|
|
124
|
+
void update.then(
|
|
125
|
+
() => {
|
|
126
|
+
sendAbsoluteHmrTiming({
|
|
127
|
+
clientStart,
|
|
128
|
+
kind: 'html',
|
|
129
|
+
serverMs: message.data.serverDuration,
|
|
130
|
+
updateId: message.timestamp
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
() => {
|
|
134
|
+
sendAbsoluteHmrTiming({
|
|
135
|
+
clientStart,
|
|
136
|
+
kind: 'html',
|
|
137
|
+
outcome: 'reloaded',
|
|
138
|
+
serverMs: message.data.serverDuration,
|
|
139
|
+
updateId: message.timestamp
|
|
140
|
+
});
|
|
141
|
+
window.location.reload();
|
|
142
|
+
}
|
|
143
|
+
);
|
|
114
144
|
};
|
|
115
145
|
export const handleScriptUpdate = (message: {
|
|
116
|
-
data: {
|
|
146
|
+
data: {
|
|
147
|
+
framework?: string;
|
|
148
|
+
scriptPath?: string;
|
|
149
|
+
serverDuration?: number;
|
|
150
|
+
};
|
|
151
|
+
timestamp?: number;
|
|
117
152
|
}) => {
|
|
118
153
|
const scriptFramework = message.data.framework;
|
|
119
154
|
const currentFw = detectCurrentFramework();
|
|
@@ -139,13 +174,30 @@ export const handleScriptUpdate = (message: {
|
|
|
139
174
|
});
|
|
140
175
|
|
|
141
176
|
const cacheBustedPath = `${scriptPath}?t=${Date.now()}`;
|
|
177
|
+
const clientStart = performance.now();
|
|
142
178
|
import(cacheBustedPath)
|
|
143
|
-
.then(() =>
|
|
179
|
+
.then(() => {
|
|
180
|
+
sendAbsoluteHmrTiming({
|
|
181
|
+
clientStart,
|
|
182
|
+
kind: 'script',
|
|
183
|
+
serverMs: message.data.serverDuration,
|
|
184
|
+
updateId: message.timestamp
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return true;
|
|
188
|
+
})
|
|
144
189
|
.catch((err: unknown) => {
|
|
145
190
|
console.error(
|
|
146
191
|
'[HMR] Script hot-reload failed, falling back to page reload:',
|
|
147
192
|
err
|
|
148
193
|
);
|
|
194
|
+
sendAbsoluteHmrTiming({
|
|
195
|
+
clientStart,
|
|
196
|
+
kind: 'script',
|
|
197
|
+
outcome: 'reloaded',
|
|
198
|
+
serverMs: message.data.serverDuration,
|
|
199
|
+
updateId: message.timestamp
|
|
200
|
+
});
|
|
149
201
|
window.location.reload();
|
|
150
202
|
});
|
|
151
203
|
};
|
|
@@ -172,8 +224,9 @@ const updateHTMLBody = (
|
|
|
172
224
|
container: HTMLElement
|
|
173
225
|
) => {
|
|
174
226
|
if (!container) {
|
|
175
|
-
return;
|
|
227
|
+
return Promise.resolve();
|
|
176
228
|
}
|
|
229
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
177
230
|
|
|
178
231
|
const savedState = saveHTMLState();
|
|
179
232
|
const domSnapshot = snapshotDOMChanges(container);
|
|
@@ -222,8 +275,11 @@ const updateHTMLBody = (
|
|
|
222
275
|
}
|
|
223
276
|
reExecuteScripts(container, newScripts, oldInlineScripts);
|
|
224
277
|
}
|
|
278
|
+
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
279
|
+
resolve();
|
|
225
280
|
});
|
|
226
|
-
|
|
281
|
+
|
|
282
|
+
return promise;
|
|
227
283
|
};
|
|
228
284
|
|
|
229
285
|
const replaceInlineScript = (script: Element) => {
|
|
@@ -245,6 +301,7 @@ const updateHTMLBodyDirect = (
|
|
|
245
301
|
htmlDomState: ReturnType<typeof saveDOMState>,
|
|
246
302
|
container: HTMLElement
|
|
247
303
|
) => {
|
|
304
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
248
305
|
const savedState = saveHTMLState();
|
|
249
306
|
const domSnapshot = snapshotDOMChanges(container);
|
|
250
307
|
|
|
@@ -282,8 +339,11 @@ const updateHTMLBodyDirect = (
|
|
|
282
339
|
});
|
|
283
340
|
|
|
284
341
|
reExecuteInlineScripts(container, oldInlineScripts);
|
|
342
|
+
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
343
|
+
resolve();
|
|
285
344
|
});
|
|
286
|
-
|
|
345
|
+
|
|
346
|
+
return promise;
|
|
287
347
|
};
|
|
288
348
|
|
|
289
349
|
/* Shared helpers for HTML body updates */
|
|
@@ -16,6 +16,7 @@ import { patchHeadInPlace } from '../headPatch';
|
|
|
16
16
|
import { detectCurrentFramework } from '../frameworkDetect';
|
|
17
17
|
import { type HTMXSavedState, type ScriptInfo } from '../../../types/client';
|
|
18
18
|
import { hmrState } from '../hmrState';
|
|
19
|
+
import { sendAbsoluteHmrTiming } from '../hmrTiming';
|
|
19
20
|
|
|
20
21
|
const parseHTMXMessage = (
|
|
21
22
|
html: string | { body?: string; head?: string } | null | undefined
|
|
@@ -52,23 +53,26 @@ const handleHTMXBodyUpdate = (
|
|
|
52
53
|
htmxHead: string | null,
|
|
53
54
|
htmxDomState: ReturnType<typeof saveDOMState>
|
|
54
55
|
) => {
|
|
55
|
-
const updateHTMXBodyAfterCSS = () =>
|
|
56
|
+
const updateHTMXBodyAfterCSS = () =>
|
|
56
57
|
updateHTMXBody(htmxBody, htmxDomState, document.body);
|
|
57
|
-
};
|
|
58
58
|
|
|
59
59
|
if (htmxHead) {
|
|
60
60
|
applyHeadPatch(htmxHead);
|
|
61
|
+
|
|
61
62
|
const cssResult = processCSSLinks(htmxHead);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
updateHTMXBodyAfterCSS();
|
|
63
|
+
|
|
64
|
+
return waitForCSSAndUpdate(cssResult, updateHTMXBodyAfterCSS);
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
return updateHTMXBodyAfterCSS();
|
|
66
68
|
};
|
|
67
69
|
|
|
68
70
|
export const handleHTMXUpdate = (message: {
|
|
69
71
|
data: {
|
|
70
72
|
html?: string | { body?: string; head?: string } | null;
|
|
73
|
+
serverDuration?: number;
|
|
71
74
|
};
|
|
75
|
+
timestamp?: number;
|
|
72
76
|
}) => {
|
|
73
77
|
const htmxFrameworkCheck = detectCurrentFramework();
|
|
74
78
|
if (htmxFrameworkCheck !== 'htmx') return;
|
|
@@ -78,6 +82,7 @@ export const handleHTMXUpdate = (message: {
|
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
sessionStorage.setItem('__HMR_ACTIVE__', 'true');
|
|
85
|
+
const clientStart = performance.now();
|
|
81
86
|
|
|
82
87
|
const htmxDomState = saveDOMState(document.body);
|
|
83
88
|
const { body: htmxBody, head: htmxHead } = parseHTMXMessage(
|
|
@@ -86,11 +91,37 @@ export const handleHTMXUpdate = (message: {
|
|
|
86
91
|
|
|
87
92
|
if (!htmxBody) {
|
|
88
93
|
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
94
|
+
sendAbsoluteHmrTiming({
|
|
95
|
+
clientStart,
|
|
96
|
+
kind: 'htmx',
|
|
97
|
+
outcome: 'failed',
|
|
98
|
+
serverMs: message.data.serverDuration,
|
|
99
|
+
updateId: message.timestamp
|
|
100
|
+
});
|
|
89
101
|
|
|
90
102
|
return;
|
|
91
103
|
}
|
|
92
104
|
|
|
93
|
-
handleHTMXBodyUpdate(htmxBody, htmxHead, htmxDomState)
|
|
105
|
+
void handleHTMXBodyUpdate(htmxBody, htmxHead, htmxDomState).then(
|
|
106
|
+
() => {
|
|
107
|
+
sendAbsoluteHmrTiming({
|
|
108
|
+
clientStart,
|
|
109
|
+
kind: 'htmx',
|
|
110
|
+
serverMs: message.data.serverDuration,
|
|
111
|
+
updateId: message.timestamp
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
() => {
|
|
115
|
+
sendAbsoluteHmrTiming({
|
|
116
|
+
clientStart,
|
|
117
|
+
kind: 'htmx',
|
|
118
|
+
outcome: 'reloaded',
|
|
119
|
+
serverMs: message.data.serverDuration,
|
|
120
|
+
updateId: message.timestamp
|
|
121
|
+
});
|
|
122
|
+
window.location.reload();
|
|
123
|
+
}
|
|
124
|
+
);
|
|
94
125
|
};
|
|
95
126
|
|
|
96
127
|
const cloneHmrListenerElements = (container: HTMLElement) => {
|
|
@@ -169,7 +200,8 @@ const updateHTMXBody = (
|
|
|
169
200
|
htmxDomState: ReturnType<typeof saveDOMState>,
|
|
170
201
|
container: HTMLElement
|
|
171
202
|
) => {
|
|
172
|
-
if (!container) return;
|
|
203
|
+
if (!container) return Promise.resolve();
|
|
204
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
173
205
|
|
|
174
206
|
const countSpan = container.querySelector('#count');
|
|
175
207
|
const countValue = countSpan
|
|
@@ -218,8 +250,11 @@ const updateHTMXBody = (
|
|
|
218
250
|
if (window.htmx) {
|
|
219
251
|
window.htmx.process(container);
|
|
220
252
|
}
|
|
253
|
+
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
254
|
+
resolve();
|
|
221
255
|
});
|
|
222
|
-
|
|
256
|
+
|
|
257
|
+
return promise;
|
|
223
258
|
};
|
|
224
259
|
|
|
225
260
|
/* Shared helpers */
|
|
@@ -6,9 +6,18 @@ import type {} from '../../../types/globals';
|
|
|
6
6
|
|
|
7
7
|
import { hideErrorOverlay } from '../errorOverlay';
|
|
8
8
|
import { detectCurrentFramework } from '../frameworkDetect';
|
|
9
|
+
import { sendAbsoluteHmrTiming } from '../hmrTiming';
|
|
10
|
+
import { swapCSSStylesheet } from '../cssUtils';
|
|
11
|
+
|
|
12
|
+
const reloadReactPage = () => {
|
|
13
|
+
const url = new URL(window.location.href);
|
|
14
|
+
url.searchParams.set('__absolute_hmr', Date.now().toString());
|
|
15
|
+
window.location.replace(url.href);
|
|
16
|
+
};
|
|
9
17
|
|
|
10
18
|
export const handleReactUpdate = (message: {
|
|
11
19
|
data: {
|
|
20
|
+
fastRefreshSupported?: boolean;
|
|
12
21
|
hasCSSChanges?: boolean;
|
|
13
22
|
hasComponentChanges?: boolean;
|
|
14
23
|
manifest?: Record<string, string>;
|
|
@@ -16,6 +25,7 @@ export const handleReactUpdate = (message: {
|
|
|
16
25
|
primarySource?: string;
|
|
17
26
|
serverDuration?: number;
|
|
18
27
|
};
|
|
28
|
+
timestamp?: number;
|
|
19
29
|
}) => {
|
|
20
30
|
const currentFramework = detectCurrentFramework();
|
|
21
31
|
if (currentFramework !== 'react') return;
|
|
@@ -24,52 +34,99 @@ export const handleReactUpdate = (message: {
|
|
|
24
34
|
const hasCSSChanges = message.data.hasCSSChanges === true;
|
|
25
35
|
const cssPath =
|
|
26
36
|
message.data.manifest && message.data.manifest.ReactExampleCSS;
|
|
27
|
-
|
|
28
37
|
if (!hasComponentChanges && hasCSSChanges && cssPath) {
|
|
29
|
-
|
|
38
|
+
const clientStart = performance.now();
|
|
39
|
+
void reloadReactCSS(cssPath).then((applied) => {
|
|
40
|
+
sendAbsoluteHmrTiming({
|
|
41
|
+
clientStart,
|
|
42
|
+
kind: 'css',
|
|
43
|
+
outcome: applied ? 'applied' : 'failed',
|
|
44
|
+
serverMs: message.data.serverDuration,
|
|
45
|
+
updateId: message.timestamp
|
|
46
|
+
});
|
|
47
|
+
});
|
|
30
48
|
|
|
31
49
|
return;
|
|
32
50
|
}
|
|
51
|
+
if (message.data.fastRefreshSupported === false) {
|
|
52
|
+
const { pageModuleUrl } = message.data;
|
|
53
|
+
const remount = window.__ABS_REACT_REMOUNT__;
|
|
54
|
+
if (pageModuleUrl && remount) {
|
|
55
|
+
applyRemountImport(
|
|
56
|
+
pageModuleUrl,
|
|
57
|
+
remount,
|
|
58
|
+
message.data.serverDuration,
|
|
59
|
+
message.timestamp
|
|
60
|
+
);
|
|
61
|
+
} else {
|
|
62
|
+
const clientStart = performance.now();
|
|
63
|
+
sendAbsoluteHmrTiming({
|
|
64
|
+
clientStart,
|
|
65
|
+
kind: 'component',
|
|
66
|
+
outcome: 'reloaded',
|
|
67
|
+
serverMs: message.data.serverDuration,
|
|
68
|
+
updateId: message.timestamp
|
|
69
|
+
});
|
|
70
|
+
reloadReactPage();
|
|
71
|
+
}
|
|
33
72
|
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
34
75
|
const refreshRuntime = window.$RefreshRuntime$;
|
|
35
76
|
const { serverDuration } = message.data;
|
|
36
77
|
const { pageModuleUrl } = message.data;
|
|
37
78
|
|
|
38
79
|
if (pageModuleUrl && refreshRuntime) {
|
|
39
|
-
applyRefreshImport(
|
|
80
|
+
applyRefreshImport(
|
|
81
|
+
pageModuleUrl,
|
|
82
|
+
refreshRuntime,
|
|
83
|
+
serverDuration,
|
|
84
|
+
message.timestamp
|
|
85
|
+
);
|
|
40
86
|
|
|
41
87
|
return;
|
|
42
88
|
}
|
|
43
89
|
|
|
44
90
|
// No module URL — shouldn't happen, but reload as safety fallback
|
|
91
|
+
const clientStart = performance.now();
|
|
92
|
+
sendAbsoluteHmrTiming({
|
|
93
|
+
clientStart,
|
|
94
|
+
kind: 'component',
|
|
95
|
+
outcome: 'reloaded',
|
|
96
|
+
serverMs: message.data.serverDuration,
|
|
97
|
+
updateId: message.timestamp
|
|
98
|
+
});
|
|
45
99
|
window.location.reload();
|
|
46
100
|
};
|
|
47
101
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
102
|
+
const finishUpdate = (
|
|
103
|
+
clientStart: number,
|
|
104
|
+
serverDuration?: number,
|
|
105
|
+
updateId?: number
|
|
106
|
+
) => {
|
|
107
|
+
sendAbsoluteHmrTiming({
|
|
108
|
+
clientStart,
|
|
109
|
+
kind: 'component',
|
|
110
|
+
serverMs: serverDuration,
|
|
111
|
+
updateId
|
|
112
|
+
});
|
|
56
113
|
if (window.__ERROR_BOUNDARY__) {
|
|
57
114
|
window.__ERROR_BOUNDARY__.reset();
|
|
58
|
-
} else {
|
|
59
|
-
hideErrorOverlay();
|
|
60
115
|
}
|
|
116
|
+
hideErrorOverlay();
|
|
61
117
|
};
|
|
62
118
|
|
|
63
119
|
const applyRefreshImport = (
|
|
64
120
|
moduleUrl: string,
|
|
65
121
|
refreshRuntime: { performReactRefresh: () => unknown },
|
|
66
|
-
serverDuration?: number
|
|
122
|
+
serverDuration?: number,
|
|
123
|
+
updateId?: number
|
|
67
124
|
) => {
|
|
68
125
|
const clientStart = performance.now();
|
|
69
126
|
import(`${moduleUrl}?t=${Date.now()}`)
|
|
70
127
|
.then(() => {
|
|
71
128
|
refreshRuntime.performReactRefresh();
|
|
72
|
-
|
|
129
|
+
finishUpdate(clientStart, serverDuration, updateId);
|
|
73
130
|
|
|
74
131
|
return undefined;
|
|
75
132
|
})
|
|
@@ -78,31 +135,56 @@ const applyRefreshImport = (
|
|
|
78
135
|
'[HMR] React Fast Refresh failed, falling back to reload:',
|
|
79
136
|
err
|
|
80
137
|
);
|
|
138
|
+
sendAbsoluteHmrTiming({
|
|
139
|
+
clientStart,
|
|
140
|
+
kind: 'component',
|
|
141
|
+
outcome: 'reloaded',
|
|
142
|
+
serverMs: serverDuration,
|
|
143
|
+
updateId
|
|
144
|
+
});
|
|
81
145
|
window.location.reload();
|
|
82
146
|
});
|
|
83
147
|
};
|
|
84
148
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
149
|
+
const applyRemountImport = (
|
|
150
|
+
moduleUrl: string,
|
|
151
|
+
remount: (module: Record<string, unknown>) => void,
|
|
152
|
+
serverDuration?: number,
|
|
153
|
+
updateId?: number
|
|
154
|
+
) => {
|
|
155
|
+
const clientStart = performance.now();
|
|
156
|
+
import(`${moduleUrl}?t=${Date.now()}`)
|
|
157
|
+
.then((module) => {
|
|
158
|
+
remount(module);
|
|
159
|
+
finishUpdate(clientStart, serverDuration, updateId);
|
|
160
|
+
|
|
161
|
+
return undefined;
|
|
162
|
+
})
|
|
163
|
+
.catch((err) => {
|
|
164
|
+
console.warn(
|
|
165
|
+
'[HMR] React remount failed, falling back to reload:',
|
|
166
|
+
err
|
|
167
|
+
);
|
|
168
|
+
sendAbsoluteHmrTiming({
|
|
169
|
+
clientStart,
|
|
170
|
+
kind: 'component',
|
|
171
|
+
outcome: 'reloaded',
|
|
172
|
+
serverMs: serverDuration,
|
|
173
|
+
updateId
|
|
174
|
+
});
|
|
175
|
+
reloadReactPage();
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const reloadReactCSS = (cssPath: string) =>
|
|
180
|
+
swapCSSStylesheet(cssPath, (href) => {
|
|
94
181
|
const hrefBase = (href.split('?')[0] ?? '').split('/').pop() ?? '';
|
|
95
182
|
const cssPathBase =
|
|
96
183
|
(cssPath.split('?')[0] ?? '').split('/').pop() ?? '';
|
|
97
|
-
|
|
184
|
+
|
|
185
|
+
return (
|
|
98
186
|
hrefBase === cssPathBase ||
|
|
99
187
|
href.includes('react-example') ||
|
|
100
188
|
cssPathBase.includes(hrefBase)
|
|
101
|
-
)
|
|
102
|
-
const newHref = `${
|
|
103
|
-
cssPath + (cssPath.includes('?') ? '&' : '?')
|
|
104
|
-
}t=${Date.now()}`;
|
|
105
|
-
link.href = newHref;
|
|
106
|
-
}
|
|
189
|
+
);
|
|
107
190
|
});
|
|
108
|
-
};
|