@absolutejs/absolute 0.14.0 → 0.15.1

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 (96) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/CLAUDE.md +11 -2
  3. package/LICENSE +74 -18
  4. package/README.md +4 -4
  5. package/THIRD_PARTY_NOTICES.md +61 -0
  6. package/dist/cli/index.js +24 -8
  7. package/dist/dev/client/cssUtils.ts +288 -0
  8. package/dist/dev/client/domDiff.ts +261 -0
  9. package/dist/dev/client/domState.ts +271 -0
  10. package/dist/dev/client/errorOverlay.ts +145 -0
  11. package/dist/dev/client/frameworkDetect.ts +63 -0
  12. package/dist/dev/client/handlers/html.ts +415 -0
  13. package/dist/dev/client/handlers/htmx.ts +248 -0
  14. package/dist/dev/client/handlers/react.ts +80 -0
  15. package/dist/dev/client/handlers/rebuild.ts +147 -0
  16. package/dist/dev/client/handlers/svelte.ts +129 -0
  17. package/dist/dev/client/handlers/vue.ts +254 -0
  18. package/dist/dev/client/headPatch.ts +213 -0
  19. package/dist/dev/client/hmrClient.ts +204 -0
  20. package/dist/dev/client/moduleVersions.ts +57 -0
  21. package/dist/dev/client/reactRefreshSetup.ts +21 -0
  22. package/dist/index.js +3028 -478
  23. package/dist/index.js.map +49 -19
  24. package/dist/{build → src/build}/compileSvelte.d.ts +2 -1
  25. package/dist/src/build/compileVue.d.ts +33 -0
  26. package/dist/src/build/generateReactIndexes.d.ts +1 -0
  27. package/dist/src/build/htmlScriptHMRPlugin.d.ts +13 -0
  28. package/dist/src/build/wrapHTMLScript.d.ts +24 -0
  29. package/dist/{core → src/core}/build.d.ts +2 -2
  30. package/dist/src/core/devBuild.d.ts +6 -0
  31. package/dist/{core → src/core}/index.d.ts +2 -1
  32. package/dist/src/core/lookup.d.ts +3 -0
  33. package/dist/{core → src/core}/pageHandlers.d.ts +4 -4
  34. package/dist/src/dev/assetStore.d.ts +12 -0
  35. package/dist/src/dev/buildHMRClient.d.ts +1 -0
  36. package/dist/src/dev/clientManager.d.ts +26 -0
  37. package/dist/src/dev/configResolver.d.ts +13 -0
  38. package/dist/src/dev/dependencyGraph.d.ts +13 -0
  39. package/dist/src/dev/fileHashTracker.d.ts +2 -0
  40. package/dist/src/dev/fileWatcher.d.ts +3 -0
  41. package/dist/src/dev/moduleMapper.d.ts +21 -0
  42. package/dist/src/dev/moduleVersionTracker.d.ts +7 -0
  43. package/dist/src/dev/pathUtils.d.ts +5 -0
  44. package/dist/src/dev/reactComponentClassifier.d.ts +2 -0
  45. package/dist/src/dev/rebuildTrigger.d.ts +10 -0
  46. package/dist/src/dev/simpleHTMLHMR.d.ts +4 -0
  47. package/dist/src/dev/simpleHTMXHMR.d.ts +4 -0
  48. package/dist/src/dev/simpleSvelteHMR.d.ts +1 -0
  49. package/dist/src/dev/simpleVueHMR.d.ts +1 -0
  50. package/dist/src/dev/webSocket.d.ts +9 -0
  51. package/dist/{index.d.ts → src/index.d.ts} +1 -0
  52. package/dist/src/plugins/hmr.d.ts +62 -0
  53. package/dist/{plugins → src/plugins}/index.d.ts +2 -1
  54. package/dist/{svelte → src/svelte}/renderToReadableStream.d.ts +3 -1
  55. package/dist/src/utils/getRegisterClientScript.d.ts +10 -0
  56. package/dist/{utils → src/utils}/index.d.ts +2 -0
  57. package/dist/src/utils/logger.d.ts +45 -0
  58. package/dist/src/utils/networking.d.ts +2 -0
  59. package/dist/src/utils/normalizePath.d.ts +9 -0
  60. package/dist/src/utils/registerClientScript.d.ts +51 -0
  61. package/dist/{types.d.ts → types/build.d.ts} +6 -0
  62. package/dist/types/client.d.ts +104 -0
  63. package/dist/types/index.d.ts +4 -0
  64. package/dist/types/messages.d.ts +138 -0
  65. package/dist/types/websocket.d.ts +6 -0
  66. package/eslint.config.mjs +5 -1
  67. package/package.json +19 -14
  68. package/tsconfig.build.json +1 -1
  69. package/types/build.ts +46 -0
  70. package/types/client.ts +109 -0
  71. package/types/index.ts +4 -0
  72. package/types/messages.ts +205 -0
  73. package/types/websocket.ts +12 -0
  74. package/types/window-globals.ts +53 -0
  75. package/dist/build/compileVue.d.ts +0 -5
  76. package/dist/build/generateReactIndexes.d.ts +0 -1
  77. package/dist/core/lookup.d.ts +0 -1
  78. package/dist/utils/networking.d.ts +0 -1
  79. /package/dist/{build → src/build}/generateManifest.d.ts +0 -0
  80. /package/dist/{build → src/build}/outputLogs.d.ts +0 -0
  81. /package/dist/{build → src/build}/scanEntryPoints.d.ts +0 -0
  82. /package/dist/{build → src/build}/updateAssetPaths.d.ts +0 -0
  83. /package/dist/{cli → src/cli}/index.d.ts +0 -0
  84. /package/dist/{constants.d.ts → src/constants.d.ts} +0 -0
  85. /package/dist/{plugins → src/plugins}/networking.d.ts +0 -0
  86. /package/dist/{plugins → src/plugins}/pageRouter.d.ts +0 -0
  87. /package/dist/{svelte → src/svelte}/renderToPipeableStream.d.ts +0 -0
  88. /package/dist/{svelte → src/svelte}/renderToString.d.ts +0 -0
  89. /package/dist/{utils → src/utils}/cleanup.d.ts +0 -0
  90. /package/dist/{utils → src/utils}/commonAncestor.d.ts +0 -0
  91. /package/dist/{utils → src/utils}/escapeScriptContent.d.ts +0 -0
  92. /package/dist/{utils → src/utils}/generateHeadElement.d.ts +0 -0
  93. /package/dist/{utils → src/utils}/getDurationString.d.ts +0 -0
  94. /package/dist/{utils → src/utils}/getEnv.d.ts +0 -0
  95. /package/dist/{utils → src/utils}/stringModifiers.d.ts +0 -0
  96. /package/dist/{utils → src/utils}/validateSafePath.d.ts +0 -0
@@ -0,0 +1,248 @@
1
+ /* HTMX HMR update handler */
2
+
3
+ import { patchDOMInPlace } from '../domDiff';
4
+ import {
5
+ saveDOMState,
6
+ restoreDOMState,
7
+ saveFormState,
8
+ restoreFormState,
9
+ saveScrollState,
10
+ restoreScrollState
11
+ } from '../domState';
12
+ import { processCSSLinks, waitForCSSAndUpdate } from '../cssUtils';
13
+ import { patchHeadInPlace } from '../headPatch';
14
+ import { detectCurrentFramework } from '../frameworkDetect';
15
+ import type { ScriptInfo } from '../../../../types/client';
16
+ import { hmrState } from '../../../../types/client';
17
+
18
+ export const handleHTMXUpdate = (message: {
19
+ data: {
20
+ html?: string | { body?: string; head?: string } | null;
21
+ };
22
+ }) => {
23
+ const htmxFrameworkCheck = detectCurrentFramework();
24
+ if (htmxFrameworkCheck !== 'htmx') return;
25
+
26
+ if (window.__REACT_ROOT__) {
27
+ window.__REACT_ROOT__ = undefined;
28
+ }
29
+
30
+ sessionStorage.setItem('__HMR_ACTIVE__', 'true');
31
+
32
+ const htmxDomState = saveDOMState(document.body);
33
+
34
+ let htmxBody: string | null = null;
35
+ let htmxHead: string | null = null;
36
+ if (typeof message.data.html === 'string') {
37
+ htmxBody = message.data.html;
38
+ } else if (message.data.html && typeof message.data.html === 'object') {
39
+ htmxBody = message.data.html.body || null;
40
+ htmxHead = message.data.html.head || null;
41
+ }
42
+
43
+ if (htmxBody) {
44
+ const capturedBody = htmxBody;
45
+
46
+ const updateHTMXBodyAfterCSS = function () {
47
+ updateHTMXBody(capturedBody, htmxDomState, document.body);
48
+ };
49
+
50
+ if (htmxHead) {
51
+ console.log('[HMR] Has htmxHead, patching head elements');
52
+
53
+ const doPatchHead = function () {
54
+ patchHeadInPlace(htmxHead!);
55
+ };
56
+ if (hmrState.isFirstHMRUpdate) {
57
+ console.log(
58
+ '[HMR] First update - adding head patch stabilization delay'
59
+ );
60
+ setTimeout(doPatchHead, 50);
61
+ } else {
62
+ doPatchHead();
63
+ }
64
+
65
+ console.log('[HMR] Processing CSS links');
66
+ const cssResult = processCSSLinks(htmxHead);
67
+
68
+ waitForCSSAndUpdate(cssResult, updateHTMXBodyAfterCSS);
69
+ } else {
70
+ updateHTMXBodyAfterCSS();
71
+ }
72
+ } else {
73
+ sessionStorage.removeItem('__HMR_ACTIVE__');
74
+ }
75
+ };
76
+
77
+ const updateHTMXBody = (
78
+ htmxBody: string,
79
+ htmxDomState: ReturnType<typeof saveDOMState>,
80
+ container: HTMLElement
81
+ ) => {
82
+ if (!container) return;
83
+
84
+ const countSpan = container.querySelector('#count');
85
+ const countValue = countSpan
86
+ ? parseInt(countSpan.textContent || '0', 10)
87
+ : 0;
88
+
89
+ const savedState = {
90
+ componentState: { count: countValue },
91
+ forms: saveFormState(),
92
+ scroll: saveScrollState()
93
+ };
94
+
95
+ const existingScripts = collectScripts(container);
96
+
97
+ const tempDiv = document.createElement('div');
98
+ tempDiv.innerHTML = htmxBody;
99
+
100
+ if (savedState.componentState.count !== undefined) {
101
+ const newCounterSpan = tempDiv.querySelector('#count');
102
+ if (newCounterSpan) {
103
+ newCounterSpan.textContent = String(
104
+ savedState.componentState.count
105
+ );
106
+ }
107
+ }
108
+
109
+ const patchedBody = tempDiv.innerHTML;
110
+ const newScripts = collectScriptsFromElement(tempDiv);
111
+ const scriptsChanged = didScriptsChange(existingScripts, newScripts);
112
+
113
+ const htmlStructureChanged = didHTMLStructureChange(container, tempDiv);
114
+
115
+ const hmrScript = container.querySelector('script[data-hmr-client]');
116
+
117
+ patchDOMInPlace(container, patchedBody);
118
+
119
+ if (hmrScript && !container.querySelector('script[data-hmr-client]')) {
120
+ container.appendChild(hmrScript);
121
+ }
122
+
123
+ requestAnimationFrame(function () {
124
+ restoreFormState(savedState.forms);
125
+ restoreScrollState(savedState.scroll);
126
+
127
+ const newCountSpan = container.querySelector('#count');
128
+ if (newCountSpan && savedState.componentState.count !== undefined) {
129
+ newCountSpan.textContent = String(savedState.componentState.count);
130
+ }
131
+
132
+ restoreDOMState(container, htmxDomState);
133
+
134
+ if (scriptsChanged || htmlStructureChanged) {
135
+ container
136
+ .querySelectorAll('[data-hmr-listeners-attached]')
137
+ .forEach(function (el) {
138
+ const cloned = el.cloneNode(true) as Element;
139
+ if (el.parentNode) {
140
+ el.parentNode.replaceChild(cloned, el);
141
+ }
142
+ cloned.removeAttribute('data-hmr-listeners-attached');
143
+ });
144
+
145
+ const scriptsInNewHTML = container.querySelectorAll('script[src]');
146
+ scriptsInNewHTML.forEach(function (script) {
147
+ if (!(script as Element).hasAttribute('data-hmr-client')) {
148
+ script.remove();
149
+ }
150
+ });
151
+
152
+ newScripts.forEach(function (scriptInfo) {
153
+ const newScript = document.createElement('script');
154
+ const separator = scriptInfo.src.includes('?') ? '&' : '?';
155
+ newScript.src = scriptInfo.src + separator + 't=' + Date.now();
156
+ newScript.type = scriptInfo.type;
157
+ container.appendChild(newScript);
158
+ });
159
+
160
+ const inlineScripts =
161
+ container.querySelectorAll('script:not([src])');
162
+ inlineScripts.forEach(function (script) {
163
+ if (!(script as Element).hasAttribute('data-hmr-client')) {
164
+ const newScript = document.createElement('script');
165
+ newScript.textContent = script.textContent || '';
166
+ newScript.type =
167
+ (script as HTMLScriptElement).type || 'text/javascript';
168
+ if (script.parentNode) {
169
+ script.parentNode.replaceChild(newScript, script);
170
+ }
171
+ }
172
+ });
173
+ }
174
+
175
+ if (window.htmx) {
176
+ window.htmx.process(container);
177
+ }
178
+ });
179
+ sessionStorage.removeItem('__HMR_ACTIVE__');
180
+ };
181
+
182
+ /* Shared helpers */
183
+
184
+ const collectScripts = (container: HTMLElement) => {
185
+ return Array.from(container.querySelectorAll('script[src]')).map(
186
+ function (script) {
187
+ return {
188
+ src: script.getAttribute('src') || '',
189
+ type: script.getAttribute('type') || 'text/javascript'
190
+ };
191
+ }
192
+ );
193
+ };
194
+
195
+ const collectScriptsFromElement = (el: HTMLElement) => {
196
+ return Array.from(el.querySelectorAll('script[src]')).map(
197
+ function (script) {
198
+ return {
199
+ src: script.getAttribute('src') || '',
200
+ type: script.getAttribute('type') || 'text/javascript'
201
+ };
202
+ }
203
+ );
204
+ };
205
+
206
+ const didScriptsChange = (
207
+ oldScripts: ScriptInfo[],
208
+ newScripts: ScriptInfo[]
209
+ ) => {
210
+ return (
211
+ oldScripts.length !== newScripts.length ||
212
+ oldScripts.some(function (oldScript, idx) {
213
+ const oldSrcBase = oldScript.src.split('?')[0]!.split('&')[0];
214
+ const newScript = newScripts[idx];
215
+ if (!newScript) return true;
216
+ const newSrcBase = newScript.src.split('?')[0]!.split('&')[0];
217
+ return oldSrcBase !== newSrcBase;
218
+ })
219
+ );
220
+ };
221
+
222
+ const normalizeHTMLForComparison = (element: HTMLElement) => {
223
+ const clone = element.cloneNode(true) as HTMLElement;
224
+ const scripts = clone.querySelectorAll('script');
225
+ scripts.forEach(function (script) {
226
+ if (script.parentNode) {
227
+ script.parentNode.removeChild(script);
228
+ }
229
+ });
230
+ const allElements = clone.querySelectorAll('*');
231
+ allElements.forEach(function (el) {
232
+ el.removeAttribute('data-hmr-listeners-attached');
233
+ });
234
+ if (clone.removeAttribute) {
235
+ clone.removeAttribute('data-hmr-listeners-attached');
236
+ }
237
+ return clone.innerHTML;
238
+ };
239
+
240
+ const didHTMLStructureChange = (
241
+ container: HTMLElement,
242
+ tempDiv: HTMLElement
243
+ ) => {
244
+ return (
245
+ normalizeHTMLForComparison(container) !==
246
+ normalizeHTMLForComparison(tempDiv)
247
+ );
248
+ };
@@ -0,0 +1,80 @@
1
+ /* React HMR update handler
2
+ Uses React Fast Refresh to hot-swap components while preserving state.
3
+ Code splitting ensures React lives in a shared chunk that stays cached,
4
+ so dynamic import of the rebuilt entry reuses the same React instance. */
5
+
6
+ import { detectCurrentFramework } from '../frameworkDetect';
7
+
8
+ export const handleReactUpdate = (message: {
9
+ data: {
10
+ hasCSSChanges?: boolean;
11
+ hasComponentChanges?: boolean;
12
+ manifest?: Record<string, string>;
13
+ primarySource?: string;
14
+ };
15
+ }) => {
16
+ const currentFramework = detectCurrentFramework();
17
+ if (currentFramework !== 'react') return;
18
+
19
+ const hasComponentChanges = message.data.hasComponentChanges !== false;
20
+ const hasCSSChanges = message.data.hasCSSChanges === true;
21
+ const cssPath =
22
+ message.data.manifest && message.data.manifest.ReactExampleCSS;
23
+
24
+ // CSS-only change: hot-swap the stylesheet link without reloading
25
+ if (!hasComponentChanges && hasCSSChanges && cssPath) {
26
+ reloadReactCSS(cssPath);
27
+ return;
28
+ }
29
+
30
+ // Component change: use React Fast Refresh to preserve state
31
+ const win = window as unknown as Record<string, unknown>;
32
+ const componentKey = win.__REACT_COMPONENT_KEY__ as string | undefined;
33
+ const newUrl = componentKey && message.data.manifest?.[componentKey];
34
+ const refreshRuntime = win.$RefreshRuntime$ as
35
+ | { performReactRefresh: () => void }
36
+ | undefined;
37
+
38
+ if (newUrl && refreshRuntime) {
39
+ import(newUrl + '?t=' + Date.now())
40
+ .then(() => {
41
+ refreshRuntime.performReactRefresh();
42
+ })
43
+ .catch((err) => {
44
+ console.warn(
45
+ '[HMR] React Fast Refresh failed, falling back to reload:',
46
+ err
47
+ );
48
+ window.location.reload();
49
+ });
50
+ return;
51
+ }
52
+
53
+ // Fallback: full page reload
54
+ window.location.reload();
55
+ };
56
+
57
+ const reloadReactCSS = (cssPath: string) => {
58
+ const existingCSSLinks = document.head.querySelectorAll(
59
+ 'link[rel="stylesheet"]'
60
+ );
61
+ existingCSSLinks.forEach(function (link) {
62
+ const href = (link as HTMLLinkElement).getAttribute('href');
63
+ if (href) {
64
+ const hrefBase = href.split('?')[0]!.split('/').pop() || '';
65
+ const cssPathBase = cssPath.split('?')[0]!.split('/').pop() || '';
66
+ if (
67
+ hrefBase === cssPathBase ||
68
+ href.includes('react-example') ||
69
+ cssPathBase.includes(hrefBase)
70
+ ) {
71
+ const newHref =
72
+ cssPath +
73
+ (cssPath.includes('?') ? '&' : '?') +
74
+ 't=' +
75
+ Date.now();
76
+ (link as HTMLLinkElement).href = newHref;
77
+ }
78
+ }
79
+ });
80
+ };
@@ -0,0 +1,147 @@
1
+ /* Rebuild, manifest, module-update, and error handlers */
2
+
3
+ import { hideErrorOverlay, showErrorOverlay } from '../errorOverlay';
4
+
5
+ export const handleManifest = (message: {
6
+ data: {
7
+ manifest?: Record<string, string>;
8
+ serverVersions?: Record<string, number>;
9
+ };
10
+ }) => {
11
+ window.__HMR_MANIFEST__ =
12
+ message.data.manifest ||
13
+ (message.data as unknown as Record<string, string>);
14
+
15
+ if (message.data.serverVersions) {
16
+ window.__HMR_SERVER_VERSIONS__ = message.data.serverVersions;
17
+ }
18
+
19
+ if (!window.__HMR_MODULE_VERSIONS__) {
20
+ window.__HMR_MODULE_VERSIONS__ = {};
21
+ }
22
+
23
+ window.__HMR_MODULE_UPDATES__ = [];
24
+ };
25
+
26
+ export const handleRebuildComplete = (message: {
27
+ data: {
28
+ affectedFrameworks?: string[];
29
+ manifest?: Record<string, string>;
30
+ };
31
+ }) => {
32
+ hideErrorOverlay();
33
+ if (window.__HMR_MANIFEST__) {
34
+ window.__HMR_MANIFEST__ = message.data.manifest;
35
+ }
36
+
37
+ if (
38
+ message.data.affectedFrameworks &&
39
+ !message.data.affectedFrameworks.includes('react') &&
40
+ !message.data.affectedFrameworks.includes('html') &&
41
+ !message.data.affectedFrameworks.includes('htmx') &&
42
+ !message.data.affectedFrameworks.includes('vue') &&
43
+ !message.data.affectedFrameworks.includes('svelte')
44
+ ) {
45
+ const url = new URL(window.location.href);
46
+ url.searchParams.set('_cb', Date.now().toString());
47
+ window.location.href = url.toString();
48
+ }
49
+ };
50
+
51
+ export const handleModuleUpdate = (message: {
52
+ data: {
53
+ framework?: string;
54
+ manifest?: Record<string, string>;
55
+ moduleVersions?: Record<string, number>;
56
+ serverVersions?: Record<string, number>;
57
+ };
58
+ }) => {
59
+ const hasHMRHandler =
60
+ message.data.framework === 'react' ||
61
+ message.data.framework === 'vue' ||
62
+ message.data.framework === 'svelte' ||
63
+ message.data.framework === 'html' ||
64
+ message.data.framework === 'htmx';
65
+
66
+ if (hasHMRHandler) {
67
+ if (message.data.serverVersions) {
68
+ const serverVersions = window.__HMR_SERVER_VERSIONS__ || {};
69
+ for (const key in message.data.serverVersions) {
70
+ if (
71
+ Object.prototype.hasOwnProperty.call(
72
+ message.data.serverVersions,
73
+ key
74
+ )
75
+ ) {
76
+ serverVersions[key] = message.data.serverVersions[key]!;
77
+ }
78
+ }
79
+ window.__HMR_SERVER_VERSIONS__ = serverVersions;
80
+ }
81
+ if (message.data.moduleVersions) {
82
+ const moduleVersions = window.__HMR_MODULE_VERSIONS__ || {};
83
+ for (const key in message.data.moduleVersions) {
84
+ if (
85
+ Object.prototype.hasOwnProperty.call(
86
+ message.data.moduleVersions,
87
+ key
88
+ )
89
+ ) {
90
+ moduleVersions[key] = message.data.moduleVersions[key]!;
91
+ }
92
+ }
93
+ window.__HMR_MODULE_VERSIONS__ = moduleVersions;
94
+ }
95
+ if (message.data.manifest) {
96
+ const manifest = window.__HMR_MANIFEST__ || {};
97
+ for (const key in message.data.manifest) {
98
+ if (
99
+ Object.prototype.hasOwnProperty.call(
100
+ message.data.manifest,
101
+ key
102
+ )
103
+ ) {
104
+ manifest[key] = message.data.manifest[key]!;
105
+ }
106
+ }
107
+ window.__HMR_MANIFEST__ = manifest;
108
+ }
109
+ if (!window.__HMR_MODULE_UPDATES__) {
110
+ window.__HMR_MODULE_UPDATES__ = [];
111
+ }
112
+ window.__HMR_MODULE_UPDATES__.push(message.data);
113
+ return;
114
+ }
115
+
116
+ window.location.reload();
117
+ };
118
+
119
+ export const handleRebuildError = (message: {
120
+ data: {
121
+ affectedFrameworks?: string[];
122
+ column?: number;
123
+ error?: string;
124
+ file?: string;
125
+ framework?: string;
126
+ line?: number;
127
+ lineText?: string;
128
+ };
129
+ }) => {
130
+ const errData = message.data || {};
131
+ showErrorOverlay({
132
+ column: errData.column,
133
+ file: errData.file,
134
+ framework:
135
+ errData.framework ||
136
+ (errData.affectedFrameworks && errData.affectedFrameworks[0]),
137
+ line: errData.line,
138
+ lineText: errData.lineText,
139
+ message: errData.error || 'Build failed'
140
+ });
141
+ };
142
+
143
+ export const handleFullReload = () => {
144
+ setTimeout(function () {
145
+ window.location.reload();
146
+ }, 200);
147
+ };
@@ -0,0 +1,129 @@
1
+ /* Svelte HMR update handler */
2
+
3
+ import {
4
+ saveDOMState,
5
+ restoreDOMState,
6
+ saveScrollState,
7
+ restoreScrollState
8
+ } from '../domState';
9
+ import { detectCurrentFramework, findIndexPath } from '../frameworkDetect';
10
+
11
+ /* Swap a stylesheet link by matching cssBaseName or framework name */
12
+ const swapStylesheet = (
13
+ cssUrl: string,
14
+ cssBaseName: string,
15
+ framework: string
16
+ ): void => {
17
+ let existingLink: HTMLLinkElement | null = null;
18
+ document
19
+ .querySelectorAll('link[rel="stylesheet"]')
20
+ .forEach(function (link) {
21
+ const href = (link as HTMLLinkElement).getAttribute('href') || '';
22
+ if (href.includes(cssBaseName) || href.includes(framework)) {
23
+ existingLink = link as HTMLLinkElement;
24
+ }
25
+ });
26
+
27
+ if (existingLink) {
28
+ const capturedExisting = existingLink as HTMLLinkElement;
29
+ const newLink = document.createElement('link');
30
+ newLink.rel = 'stylesheet';
31
+ newLink.href = cssUrl + '?t=' + Date.now();
32
+ newLink.onload = function () {
33
+ if (capturedExisting && capturedExisting.parentNode) {
34
+ capturedExisting.remove();
35
+ }
36
+ };
37
+ document.head.appendChild(newLink);
38
+ }
39
+ };
40
+
41
+ export const handleSvelteUpdate = (message: {
42
+ data: {
43
+ cssBaseName?: string;
44
+ cssUrl?: string;
45
+ html?: string;
46
+ manifest?: Record<string, string>;
47
+ sourceFile?: string;
48
+ updateType?: string;
49
+ };
50
+ }) => {
51
+ const svelteFrameworkCheck = detectCurrentFramework();
52
+ if (svelteFrameworkCheck !== 'svelte') return;
53
+
54
+ /* CSS-only update: hot-swap stylesheet, no remount needed */
55
+ if (message.data.updateType === 'css-only' && message.data.cssUrl) {
56
+ console.log('[HMR] Svelte CSS-only update');
57
+ swapStylesheet(
58
+ message.data.cssUrl,
59
+ message.data.cssBaseName || '',
60
+ 'svelte'
61
+ );
62
+ console.log('[HMR] Svelte CSS updated');
63
+ return;
64
+ }
65
+
66
+ /* Component update: preserve state, re-import (bootstrap handles unmount + mount) */
67
+ console.log('[HMR] Svelte update - remounting component');
68
+
69
+ /* Save DOM state and scroll position */
70
+ const domState = saveDOMState(document.body);
71
+ const scrollState = saveScrollState();
72
+
73
+ /* Extract state from DOM (Svelte 5 $state is not externally accessible) */
74
+ const preservedState: Record<string, unknown> = {};
75
+ const countButton = document.querySelector('button');
76
+ if (countButton && countButton.textContent) {
77
+ const countMatch = countButton.textContent.match(/count is (\d+)/i);
78
+ if (countMatch) {
79
+ preservedState.initialCount = parseInt(countMatch[1]!, 10);
80
+ }
81
+ }
82
+
83
+ /* Set preserved state on window + backup to sessionStorage */
84
+ window.__HMR_PRESERVED_STATE__ = preservedState;
85
+ try {
86
+ sessionStorage.setItem(
87
+ '__SVELTE_HMR_STATE__',
88
+ JSON.stringify(preservedState)
89
+ );
90
+ } catch (_err) {
91
+ /* ignore */
92
+ }
93
+ console.log(
94
+ '[HMR] Svelte state preserved:',
95
+ JSON.stringify(preservedState)
96
+ );
97
+
98
+ /* CSS pre-update: swap stylesheet BEFORE unmounting to prevent FOUC */
99
+ if (message.data.cssUrl) {
100
+ swapStylesheet(
101
+ message.data.cssUrl,
102
+ message.data.cssBaseName || '',
103
+ 'svelte'
104
+ );
105
+ }
106
+
107
+ const indexPath = findIndexPath(
108
+ message.data.manifest,
109
+ message.data.sourceFile,
110
+ 'svelte'
111
+ );
112
+ if (!indexPath) {
113
+ console.warn('[HMR] Svelte index path not found, reloading');
114
+ window.location.reload();
115
+ return;
116
+ }
117
+
118
+ const modulePath = indexPath + '?t=' + Date.now();
119
+ import(/* @vite-ignore */ modulePath)
120
+ .then(function () {
121
+ restoreDOMState(document.body, domState);
122
+ restoreScrollState(scrollState);
123
+ console.log('[HMR] Svelte component updated (state preserved)');
124
+ })
125
+ .catch(function (err: unknown) {
126
+ console.warn('[HMR] Svelte import failed, reloading:', err);
127
+ window.location.reload();
128
+ });
129
+ };