@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,254 @@
1
+ /* Vue HMR update handler */
2
+
3
+ import { saveDOMState, restoreDOMState } from '../domState';
4
+ import { detectCurrentFramework, findIndexPath } from '../frameworkDetect';
5
+
6
+ /* Local Vue internal types (avoids importing Vue) */
7
+ interface VueVNode {
8
+ children?: VueVNode[];
9
+ component?: VueComponentInstance;
10
+ }
11
+
12
+ interface VueComponentInstance {
13
+ setupState?: Record<string, unknown>;
14
+ subTree?: VueVNode;
15
+ }
16
+
17
+ /* Extract state from child Vue component instances recursively */
18
+ const extractChildComponentState = (
19
+ instance: VueComponentInstance,
20
+ state: Record<string, unknown>
21
+ ): void => {
22
+ if (!instance || !instance.subTree) return;
23
+
24
+ const walkVNode = (vnode: VueVNode | undefined): void => {
25
+ if (!vnode) return;
26
+
27
+ if (vnode.component && vnode.component.setupState) {
28
+ const childState = vnode.component.setupState;
29
+ const keys = Object.keys(childState);
30
+ for (let idx = 0; idx < keys.length; idx++) {
31
+ const key = keys[idx]!;
32
+ const value = childState[key];
33
+ if (
34
+ value &&
35
+ typeof value === 'object' &&
36
+ 'value' in (value as Record<string, unknown>)
37
+ ) {
38
+ state[key] = (value as { value: unknown }).value;
39
+ } else if (typeof value !== 'function') {
40
+ state[key] = value;
41
+ }
42
+ }
43
+ }
44
+
45
+ if (vnode.children && Array.isArray(vnode.children)) {
46
+ for (let jdx = 0; jdx < vnode.children.length; jdx++) {
47
+ walkVNode(vnode.children[jdx]);
48
+ }
49
+ }
50
+ if (vnode.component && vnode.component.subTree) {
51
+ walkVNode(vnode.component.subTree);
52
+ }
53
+ };
54
+
55
+ walkVNode(instance.subTree);
56
+ };
57
+
58
+ export const handleVueUpdate = (message: {
59
+ data: {
60
+ cssBaseName?: string;
61
+ cssUrl?: string;
62
+ html?: string;
63
+ manifest?: Record<string, string>;
64
+ sourceFile?: string;
65
+ updateType?: string;
66
+ };
67
+ }) => {
68
+ const vueFrameworkCheck = detectCurrentFramework();
69
+ if (vueFrameworkCheck !== 'vue') return;
70
+
71
+ if (message.data.updateType === 'css-only' && message.data.cssUrl) {
72
+ console.log('[HMR] Vue CSS-only update (state preserved)');
73
+ const cssBaseName = message.data.cssBaseName || '';
74
+ let existingLink: HTMLLinkElement | null = null;
75
+ document
76
+ .querySelectorAll('link[rel="stylesheet"]')
77
+ .forEach(function (link) {
78
+ const href =
79
+ (link as HTMLLinkElement).getAttribute('href') || '';
80
+ if (href.includes(cssBaseName) || href.includes('vue')) {
81
+ existingLink = link as HTMLLinkElement;
82
+ }
83
+ });
84
+
85
+ if (existingLink) {
86
+ const capturedExisting = existingLink as HTMLLinkElement;
87
+ const newLink = document.createElement('link');
88
+ newLink.rel = 'stylesheet';
89
+ newLink.href = message.data.cssUrl + '?t=' + Date.now();
90
+ newLink.onload = function () {
91
+ if (capturedExisting && capturedExisting.parentNode) {
92
+ capturedExisting.remove();
93
+ }
94
+ console.log('[HMR] Vue CSS updated');
95
+ };
96
+ document.head.appendChild(newLink);
97
+ }
98
+ return;
99
+ }
100
+
101
+ console.log('[HMR] Vue update - remounting component');
102
+ sessionStorage.setItem('__HMR_ACTIVE__', 'true');
103
+
104
+ const vueRoot = document.getElementById('root');
105
+ const vueDomState = vueRoot ? saveDOMState(vueRoot) : null;
106
+
107
+ /* Extract Vue reactive state from app instance (not DOM) */
108
+ const vuePreservedState: Record<string, unknown> = {};
109
+
110
+ if (window.__VUE_APP__ && window.__VUE_APP__._instance) {
111
+ const instance = window.__VUE_APP__._instance;
112
+
113
+ if (instance.setupState) {
114
+ const setupKeys = Object.keys(instance.setupState);
115
+ for (let idx = 0; idx < setupKeys.length; idx++) {
116
+ const key = setupKeys[idx]!;
117
+ const value = instance.setupState[key];
118
+ if (
119
+ value &&
120
+ typeof value === 'object' &&
121
+ 'value' in (value as Record<string, unknown>)
122
+ ) {
123
+ vuePreservedState[key] = (
124
+ value as { value: unknown }
125
+ ).value;
126
+ } else if (typeof value !== 'function') {
127
+ vuePreservedState[key] = value;
128
+ }
129
+ }
130
+ }
131
+
132
+ extractChildComponentState(
133
+ instance as VueComponentInstance,
134
+ vuePreservedState
135
+ );
136
+ }
137
+
138
+ /* DOM fallback if app instance not available */
139
+ if (Object.keys(vuePreservedState).length === 0) {
140
+ const countButton = document.querySelector('button');
141
+ if (countButton && countButton.textContent) {
142
+ const countMatch = countButton.textContent.match(/count is (\d+)/i);
143
+ if (countMatch) {
144
+ vuePreservedState.initialCount = parseInt(countMatch[1]!, 10);
145
+ }
146
+ }
147
+ }
148
+
149
+ /* Map count -> initialCount for prop-based state (used by CountButton) */
150
+ if (
151
+ vuePreservedState.count !== undefined &&
152
+ vuePreservedState.initialCount === undefined
153
+ ) {
154
+ vuePreservedState.initialCount = vuePreservedState.count;
155
+ }
156
+
157
+ /* Backup to sessionStorage for resilience */
158
+ try {
159
+ sessionStorage.setItem(
160
+ '__VUE_HMR_STATE__',
161
+ JSON.stringify(vuePreservedState)
162
+ );
163
+ } catch (_err) {
164
+ /* ignore */
165
+ }
166
+
167
+ window.__HMR_PRESERVED_STATE__ = vuePreservedState;
168
+ console.log(
169
+ '[HMR] Vue state preserved:',
170
+ JSON.stringify(vuePreservedState)
171
+ );
172
+
173
+ /* CSS pre-update: swap stylesheet BEFORE unmounting to prevent FOUC */
174
+ if (message.data.cssUrl) {
175
+ const vueCssBaseName = message.data.cssBaseName || '';
176
+ let vueExistingLink: HTMLLinkElement | null = null;
177
+ document
178
+ .querySelectorAll('link[rel="stylesheet"]')
179
+ .forEach(function (link) {
180
+ const href =
181
+ (link as HTMLLinkElement).getAttribute('href') || '';
182
+ if (href.includes(vueCssBaseName) || href.includes('vue')) {
183
+ vueExistingLink = link as HTMLLinkElement;
184
+ }
185
+ });
186
+ if (vueExistingLink) {
187
+ const capturedVueLink = vueExistingLink as HTMLLinkElement;
188
+ const vueCssLink = document.createElement('link');
189
+ vueCssLink.rel = 'stylesheet';
190
+ vueCssLink.href = message.data.cssUrl + '?t=' + Date.now();
191
+ vueCssLink.onload = function () {
192
+ if (capturedVueLink && capturedVueLink.parentNode) {
193
+ capturedVueLink.remove();
194
+ }
195
+ };
196
+ document.head.appendChild(vueCssLink);
197
+ }
198
+ }
199
+
200
+ /* Unmount old Vue app */
201
+ if (window.__VUE_APP__) {
202
+ window.__VUE_APP__.unmount();
203
+ window.__VUE_APP__ = null;
204
+ }
205
+
206
+ const newHTML = message.data.html;
207
+ if (!newHTML) {
208
+ window.location.reload();
209
+ return;
210
+ }
211
+
212
+ const tempDiv = document.createElement('div');
213
+ tempDiv.innerHTML = newHTML;
214
+ const newRootDiv = tempDiv.querySelector('#root');
215
+ let innerContent = newRootDiv ? newRootDiv.innerHTML : newHTML;
216
+
217
+ /* Pre-apply preserved state to HTML (prevents flicker showing count=0) */
218
+ if (vuePreservedState.initialCount !== undefined) {
219
+ innerContent = innerContent.replace(
220
+ /count is 0/g,
221
+ 'count is ' + vuePreservedState.initialCount
222
+ );
223
+ }
224
+
225
+ if (vueRoot) {
226
+ vueRoot.innerHTML = innerContent;
227
+ }
228
+
229
+ const indexPath = findIndexPath(
230
+ message.data.manifest,
231
+ message.data.sourceFile,
232
+ 'vue'
233
+ );
234
+ if (!indexPath) {
235
+ console.warn('[HMR] Vue index path not found, reloading');
236
+ window.location.reload();
237
+ return;
238
+ }
239
+
240
+ const modulePath = indexPath + '?t=' + Date.now();
241
+ import(/* @vite-ignore */ modulePath)
242
+ .then(function () {
243
+ if (vueRoot && vueDomState) {
244
+ restoreDOMState(vueRoot, vueDomState);
245
+ }
246
+ sessionStorage.removeItem('__HMR_ACTIVE__');
247
+ console.log('[HMR] Vue updated (state preserved)');
248
+ })
249
+ .catch(function (err: unknown) {
250
+ console.warn('[HMR] Vue import failed:', err);
251
+ sessionStorage.removeItem('__HMR_ACTIVE__');
252
+ window.location.reload();
253
+ });
254
+ };
@@ -0,0 +1,213 @@
1
+ /* Head element patching for HMR updates (title, meta, favicon, etc.) */
2
+
3
+ const getHeadElementKey = (el: Element) => {
4
+ const tag = el.tagName.toLowerCase();
5
+
6
+ if (tag === 'title') return 'title';
7
+ if (tag === 'meta' && el.hasAttribute('charset')) return 'meta:charset';
8
+ if (tag === 'meta' && el.hasAttribute('name'))
9
+ return 'meta:name:' + el.getAttribute('name');
10
+ if (tag === 'meta' && el.hasAttribute('property'))
11
+ return 'meta:property:' + el.getAttribute('property');
12
+ if (tag === 'meta' && el.hasAttribute('http-equiv'))
13
+ return 'meta:http-equiv:' + el.getAttribute('http-equiv');
14
+
15
+ if (tag === 'link') {
16
+ const rel = (el.getAttribute('rel') || '').toLowerCase();
17
+ if (
18
+ rel === 'icon' ||
19
+ rel === 'shortcut icon' ||
20
+ rel === 'apple-touch-icon'
21
+ )
22
+ return 'link:icon:' + rel;
23
+ if (rel === 'stylesheet') return null;
24
+ if (rel === 'preconnect')
25
+ return 'link:preconnect:' + (el.getAttribute('href') || '');
26
+ if (rel === 'preload')
27
+ return 'link:preload:' + (el.getAttribute('href') || '');
28
+ if (rel === 'canonical') return 'link:canonical';
29
+ if (rel === 'dns-prefetch')
30
+ return 'link:dns-prefetch:' + (el.getAttribute('href') || '');
31
+ }
32
+
33
+ if (tag === 'script' && el.hasAttribute('data-hmr-id'))
34
+ return 'script:hmr:' + el.getAttribute('data-hmr-id');
35
+ if (tag === 'script') return null;
36
+ if (tag === 'base') return 'base';
37
+
38
+ return null;
39
+ };
40
+
41
+ const shouldPreserveElement = (el: Element) => {
42
+ if (el.hasAttribute('data-hmr-import-map')) return true;
43
+ if (el.hasAttribute('data-hmr-client')) return true;
44
+ if (el.hasAttribute('data-react-refresh-setup')) return true;
45
+
46
+ const attrs = Array.from(el.attributes);
47
+ for (let idx = 0; idx < attrs.length; idx++) {
48
+ if (attrs[idx]!.name.startsWith('data-hmr-')) return true;
49
+ }
50
+
51
+ if (el.tagName === 'SCRIPT') {
52
+ const src = el.getAttribute('src') || '';
53
+ if (src.includes('htmx.min.js') || src.includes('htmx.js')) return true;
54
+ }
55
+
56
+ return false;
57
+ };
58
+
59
+ const updateHeadElement = (oldEl: Element, newEl: Element, key: string) => {
60
+ const tag = oldEl.tagName.toLowerCase();
61
+
62
+ if (tag === 'title') {
63
+ const newTitle = newEl.textContent || '';
64
+ if (oldEl.textContent !== newTitle) {
65
+ oldEl.textContent = newTitle;
66
+ document.title = newTitle;
67
+ console.log('[HMR] Updated title to:', newTitle);
68
+ }
69
+ return;
70
+ }
71
+
72
+ if (tag === 'meta') {
73
+ const newContent = newEl.getAttribute('content');
74
+ const oldContent = oldEl.getAttribute('content');
75
+ if (oldContent !== newContent && newContent !== null) {
76
+ oldEl.setAttribute('content', newContent);
77
+ console.log('[HMR] Updated meta', key, 'to:', newContent);
78
+ }
79
+ if (newEl.hasAttribute('charset')) {
80
+ const newCharset = newEl.getAttribute('charset');
81
+ if (oldEl.getAttribute('charset') !== newCharset) {
82
+ oldEl.setAttribute('charset', newCharset!);
83
+ }
84
+ }
85
+ return;
86
+ }
87
+
88
+ if (tag === 'link') {
89
+ const rel = (oldEl.getAttribute('rel') || '').toLowerCase();
90
+ const newHref = newEl.getAttribute('href');
91
+ const oldHref = oldEl.getAttribute('href');
92
+
93
+ if (
94
+ rel === 'icon' ||
95
+ rel === 'shortcut icon' ||
96
+ rel === 'apple-touch-icon'
97
+ ) {
98
+ if (newHref && oldHref) {
99
+ const oldBase = oldHref.split('?')[0];
100
+ const newBase = newHref.split('?')[0];
101
+ if (oldBase !== newBase) {
102
+ const cacheBustedHref =
103
+ newHref +
104
+ (newHref.includes('?') ? '&' : '?') +
105
+ 't=' +
106
+ Date.now();
107
+ oldEl.setAttribute('href', cacheBustedHref);
108
+ console.log('[HMR] Updated favicon to:', newBase);
109
+ }
110
+ }
111
+ } else if (newHref && oldHref !== newHref) {
112
+ oldEl.setAttribute('href', newHref);
113
+ console.log('[HMR] Updated link', rel, 'to:', newHref);
114
+ }
115
+
116
+ const attrsToCheck = ['type', 'sizes', 'crossorigin', 'as', 'media'];
117
+ attrsToCheck.forEach(function (attr) {
118
+ const newVal = newEl.getAttribute(attr);
119
+ const oldVal = oldEl.getAttribute(attr);
120
+ if (newVal !== null && oldVal !== newVal) {
121
+ oldEl.setAttribute(attr, newVal);
122
+ } else if (newVal === null && oldVal !== null) {
123
+ oldEl.removeAttribute(attr);
124
+ }
125
+ });
126
+ return;
127
+ }
128
+
129
+ if (tag === 'base') {
130
+ const newHref = newEl.getAttribute('href');
131
+ const newTarget = newEl.getAttribute('target');
132
+ if (newHref && oldEl.getAttribute('href') !== newHref) {
133
+ oldEl.setAttribute('href', newHref);
134
+ }
135
+ if (newTarget && oldEl.getAttribute('target') !== newTarget) {
136
+ oldEl.setAttribute('target', newTarget);
137
+ }
138
+ return;
139
+ }
140
+ };
141
+
142
+ const addHeadElement = (newEl: Element, key: string) => {
143
+ const clone = newEl.cloneNode(true) as Element;
144
+ clone.setAttribute('data-hmr-source', 'patched');
145
+
146
+ const tag = newEl.tagName.toLowerCase();
147
+ const head = document.head;
148
+ let insertBefore: Node | null = null;
149
+
150
+ if (tag === 'title') {
151
+ insertBefore = head.firstChild;
152
+ } else if (tag === 'meta') {
153
+ const firstLink = head.querySelector('link');
154
+ const firstScript = head.querySelector('script');
155
+ insertBefore = firstLink || firstScript;
156
+ } else if (tag === 'link') {
157
+ const firstScript = head.querySelector('script');
158
+ insertBefore = firstScript;
159
+ }
160
+
161
+ if (insertBefore) {
162
+ head.insertBefore(clone, insertBefore);
163
+ } else {
164
+ head.appendChild(clone);
165
+ }
166
+ console.log('[HMR] Added head element:', key);
167
+ };
168
+
169
+ export const patchHeadInPlace = (newHeadHTML: string) => {
170
+ if (!newHeadHTML) return;
171
+
172
+ const tempDiv = document.createElement('div');
173
+ tempDiv.innerHTML = newHeadHTML;
174
+
175
+ const existingMap = new Map<string, Element>();
176
+ const newMap = new Map<string, Element>();
177
+
178
+ Array.from(document.head.children).forEach(function (el) {
179
+ if (shouldPreserveElement(el)) return;
180
+ const key = getHeadElementKey(el);
181
+ if (key) {
182
+ existingMap.set(key, el);
183
+ }
184
+ });
185
+
186
+ Array.from(tempDiv.children).forEach(function (el) {
187
+ const key = getHeadElementKey(el);
188
+ if (key) {
189
+ newMap.set(key, el);
190
+ }
191
+ });
192
+
193
+ newMap.forEach(function (newEl, key) {
194
+ const existingEl = existingMap.get(key);
195
+ if (existingEl) {
196
+ updateHeadElement(existingEl, newEl, key);
197
+ } else {
198
+ addHeadElement(newEl, key);
199
+ }
200
+ });
201
+
202
+ existingMap.forEach(function (existingEl, key) {
203
+ if (!newMap.has(key)) {
204
+ if (!shouldPreserveElement(existingEl)) {
205
+ const tag = existingEl.tagName.toLowerCase();
206
+ const rel = existingEl.getAttribute('rel') || '';
207
+ if (tag === 'link' && rel === 'stylesheet') return;
208
+ existingEl.remove();
209
+ console.log('[HMR] Removed head element:', key);
210
+ }
211
+ }
212
+ });
213
+ };
@@ -0,0 +1,204 @@
1
+ /* AbsoluteJS HMR Client - Entry point
2
+ Initializes WebSocket connection, dispatches messages to framework handlers */
3
+
4
+ import '../../../types/client'; // Window global type extensions
5
+
6
+ import { hmrState } from '../../../types/client';
7
+ import { detectCurrentFramework } from './frameworkDetect';
8
+ import { handleReactUpdate } from './handlers/react';
9
+ import { handleHTMLUpdate, handleScriptUpdate } from './handlers/html';
10
+ import { handleHTMXUpdate } from './handlers/htmx';
11
+ import { handleSvelteUpdate } from './handlers/svelte';
12
+ import { handleVueUpdate } from './handlers/vue';
13
+ import {
14
+ handleFullReload,
15
+ handleManifest,
16
+ handleModuleUpdate,
17
+ handleRebuildComplete,
18
+ handleRebuildError
19
+ } from './handlers/rebuild';
20
+
21
+ // Initialize HMR globals
22
+ if (typeof window !== 'undefined') {
23
+ if (!window.__HMR_MANIFEST__) {
24
+ window.__HMR_MANIFEST__ = {};
25
+ }
26
+ if (!window.__HMR_MODULE_UPDATES__) {
27
+ window.__HMR_MODULE_UPDATES__ = [];
28
+ }
29
+ if (!window.__HMR_MODULE_VERSIONS__) {
30
+ window.__HMR_MODULE_VERSIONS__ = {};
31
+ }
32
+ if (!window.__HMR_SERVER_VERSIONS__) {
33
+ window.__HMR_SERVER_VERSIONS__ = {};
34
+ }
35
+ }
36
+
37
+ // Prevent multiple WebSocket connections
38
+ if (!(window.__HMR_WS__ && window.__HMR_WS__.readyState === WebSocket.OPEN)) {
39
+ // Determine WebSocket URL
40
+ const wsHost = location.hostname;
41
+ const wsPort =
42
+ location.port || (location.protocol === 'https:' ? '443' : '80');
43
+ const wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
44
+ const wsUrl = wsProtocol + '://' + wsHost + ':' + wsPort + '/hmr';
45
+
46
+ const wsc = new WebSocket(wsUrl);
47
+ window.__HMR_WS__ = wsc;
48
+
49
+ wsc.onopen = function () {
50
+ hmrState.isConnected = true;
51
+ sessionStorage.setItem('__HMR_CONNECTED__', 'true');
52
+
53
+ const currentFramework = detectCurrentFramework();
54
+ wsc.send(
55
+ JSON.stringify({
56
+ framework: currentFramework,
57
+ type: 'ready'
58
+ })
59
+ );
60
+
61
+ if (hmrState.reconnectTimeout) {
62
+ clearTimeout(hmrState.reconnectTimeout);
63
+ hmrState.reconnectTimeout = null;
64
+ }
65
+
66
+ hmrState.pingInterval = setInterval(function () {
67
+ if (wsc.readyState === WebSocket.OPEN && hmrState.isConnected) {
68
+ wsc.send(JSON.stringify({ type: 'ping' }));
69
+ }
70
+ }, 30000);
71
+ };
72
+
73
+ wsc.onmessage = function (event: MessageEvent) {
74
+ try {
75
+ const message = JSON.parse(event.data as string);
76
+
77
+ if (
78
+ message.type === 'react-update' ||
79
+ message.type === 'html-update' ||
80
+ message.type === 'htmx-update' ||
81
+ message.type === 'vue-update' ||
82
+ message.type === 'svelte-update' ||
83
+ message.type === 'module-update' ||
84
+ message.type === 'rebuild-start'
85
+ ) {
86
+ hmrState.isHMRUpdating = true;
87
+ setTimeout(function () {
88
+ hmrState.isHMRUpdating = false;
89
+ }, 2000);
90
+ }
91
+
92
+ switch (message.type) {
93
+ case 'manifest':
94
+ handleManifest(message);
95
+ break;
96
+
97
+ case 'rebuild-start':
98
+ break;
99
+
100
+ case 'rebuild-complete':
101
+ handleRebuildComplete(message);
102
+ break;
103
+
104
+ case 'framework-update':
105
+ break;
106
+
107
+ case 'module-update':
108
+ handleModuleUpdate(message);
109
+ break;
110
+
111
+ case 'react-update':
112
+ handleReactUpdate(message);
113
+ break;
114
+
115
+ case 'script-update':
116
+ handleScriptUpdate(message);
117
+ break;
118
+
119
+ case 'html-update':
120
+ handleHTMLUpdate(message);
121
+ break;
122
+
123
+ case 'htmx-update':
124
+ handleHTMXUpdate(message);
125
+ break;
126
+
127
+ case 'svelte-update':
128
+ handleSvelteUpdate(message);
129
+ break;
130
+
131
+ case 'vue-update':
132
+ handleVueUpdate(message);
133
+ break;
134
+
135
+ case 'rebuild-error':
136
+ handleRebuildError(message);
137
+ break;
138
+
139
+ case 'full-reload':
140
+ handleFullReload();
141
+ break;
142
+
143
+ case 'pong':
144
+ break;
145
+
146
+ case 'connected':
147
+ break;
148
+
149
+ default:
150
+ break;
151
+ }
152
+ } catch {
153
+ /* ignore parse errors */
154
+ }
155
+ };
156
+
157
+ wsc.onclose = function (event: CloseEvent) {
158
+ hmrState.isConnected = false;
159
+
160
+ if (hmrState.pingInterval) {
161
+ clearInterval(hmrState.pingInterval);
162
+ hmrState.pingInterval = null;
163
+ }
164
+
165
+ if (event.code !== 1000) {
166
+ let attempts = 0;
167
+ hmrState.reconnectTimeout = setTimeout(function pollServer() {
168
+ attempts++;
169
+ if (attempts > 60) return;
170
+
171
+ fetch('/hmr-status', { cache: 'no-store' })
172
+ .then(function (res) {
173
+ if (res.ok) {
174
+ window.location.reload();
175
+ } else {
176
+ hmrState.reconnectTimeout = setTimeout(
177
+ pollServer,
178
+ 300
179
+ );
180
+ }
181
+ })
182
+ .catch(function () {
183
+ hmrState.reconnectTimeout = setTimeout(pollServer, 300);
184
+ });
185
+ }, 500);
186
+ }
187
+ };
188
+
189
+ wsc.onerror = function () {
190
+ hmrState.isConnected = false;
191
+ };
192
+
193
+ window.addEventListener('beforeunload', function () {
194
+ if (hmrState.isHMRUpdating) {
195
+ if (hmrState.pingInterval) clearInterval(hmrState.pingInterval);
196
+ if (hmrState.reconnectTimeout)
197
+ clearTimeout(hmrState.reconnectTimeout);
198
+ return;
199
+ }
200
+
201
+ if (hmrState.pingInterval) clearInterval(hmrState.pingInterval);
202
+ if (hmrState.reconnectTimeout) clearTimeout(hmrState.reconnectTimeout);
203
+ });
204
+ }