@absolutejs/absolute 0.15.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.
@@ -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
+ }
@@ -0,0 +1,57 @@
1
+ /* Module version validation and sync */
2
+
3
+ export const checkModuleVersions = (
4
+ serverVersions: Record<string, number> | undefined,
5
+ clientVersions: Record<string, number> | undefined
6
+ ) => {
7
+ if (!serverVersions || !clientVersions) {
8
+ return { needsSync: false, stale: [] };
9
+ }
10
+
11
+ const stale: string[] = [];
12
+ let needsSync = false;
13
+
14
+ for (const [modulePath, serverVersion] of Object.entries(serverVersions)) {
15
+ const clientVersion = clientVersions[modulePath];
16
+
17
+ if (clientVersion === undefined || clientVersion < serverVersion) {
18
+ stale.push(modulePath);
19
+ needsSync = true;
20
+ }
21
+ }
22
+
23
+ return { needsSync, stale };
24
+ };
25
+
26
+ export const prefetchModules = (
27
+ modulePaths: string[],
28
+ manifest: Record<string, string> | undefined
29
+ ) => {
30
+ const prefetchPromises: Promise<unknown>[] = [];
31
+
32
+ for (const modulePath of modulePaths) {
33
+ let manifestPath = modulePath;
34
+ for (const key in manifest || {}) {
35
+ if (Object.prototype.hasOwnProperty.call(manifest, key)) {
36
+ const path = manifest![key]!;
37
+ if (path === modulePath || path.includes(modulePath)) {
38
+ manifestPath = path;
39
+ break;
40
+ }
41
+ }
42
+ }
43
+
44
+ const cacheBuster = '?t=' + Date.now();
45
+ const fullPath = manifestPath.startsWith('/')
46
+ ? manifestPath + cacheBuster
47
+ : '/' + manifestPath + cacheBuster;
48
+
49
+ prefetchPromises.push(
50
+ import(/* @vite-ignore */ fullPath).catch(function () {
51
+ /* ignore */
52
+ })
53
+ );
54
+ }
55
+
56
+ return Promise.all(prefetchPromises);
57
+ };
@@ -0,0 +1,21 @@
1
+ /* React Refresh runtime setup — must be imported before any component modules.
2
+ Bun's reactFastRefresh flag injects $RefreshSig$/$RefreshReg$ calls into
3
+ component code. This module ensures those globals exist before components
4
+ initialize.
5
+
6
+ IMPORTANT: This module is idempotent. On HMR re-import the existing runtime
7
+ is preserved so new component registrations feed into the SAME RefreshRuntime
8
+ instance that owns the current React tree. */
9
+
10
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
11
+ // @ts-ignore — react-refresh has no type declarations
12
+ import RefreshRuntime from 'react-refresh/runtime';
13
+
14
+ if (!window.$RefreshRuntime$) {
15
+ RefreshRuntime.injectIntoGlobalHook(window);
16
+ window.$RefreshRuntime$ = RefreshRuntime;
17
+ window.$RefreshReg$ = (type: unknown, id: string) =>
18
+ RefreshRuntime.register(type, id);
19
+ window.$RefreshSig$ = () =>
20
+ RefreshRuntime.createSignatureFunctionForTransform();
21
+ }