@absolutejs/absolute 0.19.0-beta.704 → 0.19.0-beta.706
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/browser.js +6 -4
- package/dist/angular/browser.js.map +3 -3
- package/dist/angular/index.js +71 -82
- package/dist/angular/index.js.map +10 -10
- package/dist/angular/server.js +70 -81
- package/dist/angular/server.js.map +10 -10
- package/dist/build.js +152 -84
- package/dist/build.js.map +15 -15
- package/dist/cli/index.js +330 -296
- package/dist/client/index.js +7 -4
- package/dist/client/index.js.map +3 -3
- package/dist/index.js +253 -153
- package/dist/index.js.map +24 -24
- package/dist/islands/index.js +9 -6
- package/dist/islands/index.js.map +5 -5
- package/dist/react/browser.js +7 -7
- package/dist/react/browser.js.map +2 -2
- package/dist/react/components/browser/index.js +101 -101
- package/dist/react/components/index.js +104 -104
- package/dist/react/components/index.js.map +2 -2
- package/dist/react/index.js +77 -88
- package/dist/react/index.js.map +10 -10
- package/dist/react/jsxDevRuntimeCompat.js +1 -6
- package/dist/react/jsxDevRuntimeCompat.js.map +3 -3
- package/dist/react/server.js +57 -71
- package/dist/react/server.js.map +8 -8
- package/dist/src/angular/components/defer-slot-templates.directive.d.ts +0 -7
- package/dist/src/angular/components/defer-slot.component.d.ts +2 -5
- package/dist/src/angular/components/image.component.d.ts +2 -5
- package/dist/src/angular/components/index.d.ts +4 -4
- package/dist/src/angular/components/stream-slot.component.d.ts +0 -3
- package/dist/src/angular/pageHandler.d.ts +6 -1
- package/dist/src/angular/ssrRender.d.ts +1 -1
- package/dist/src/build/buildAngularVendor.d.ts +3 -4
- package/dist/src/constants.d.ts +21 -0
- package/dist/src/core/ssrCache.d.ts +1 -1
- package/dist/src/core/wrapPageHandlerWithStreamingSlots.d.ts +1 -1
- package/dist/src/react/jsxDevRuntimeCompat.d.ts +3 -6
- package/dist/src/react/pageHandler.d.ts +2 -1
- package/dist/src/svelte/pageHandler.d.ts +2 -2
- package/dist/src/utils/defineConfig.d.ts +2 -2
- package/dist/src/utils/imageProcessing.d.ts +1 -1
- package/dist/src/utils/loadConfig.d.ts +38 -2
- package/dist/src/vue/components/Image.d.ts +3 -3
- package/dist/src/vue/components/index.d.ts +1 -1
- package/dist/src/vue/index.d.ts +1 -1
- package/dist/src/vue/pageHandler.d.ts +2 -1
- package/dist/svelte/index.js +52 -58
- package/dist/svelte/index.js.map +10 -10
- package/dist/svelte/server.js +46 -55
- package/dist/svelte/server.js.map +9 -9
- package/dist/vue/components/Image.js +18 -18
- package/dist/vue/components/Image.js.map +3 -3
- package/dist/vue/components/index.js +77 -62
- package/dist/vue/components/index.js.map +5 -5
- package/dist/vue/index.js +137 -142
- package/dist/vue/index.js.map +13 -13
- package/dist/vue/server.js +54 -77
- package/dist/vue/server.js.map +8 -8
- package/package.json +42 -42
- package/dist/angular/components/constants.js +0 -56
- package/dist/angular/components/core/streamingSlotRegistrar.js +0 -58
- package/dist/angular/components/core/streamingSlotRegistry.js +0 -114
- package/dist/angular/components/defer-slot-payload.js +0 -6
- package/dist/angular/components/defer-slot-templates.directive.js +0 -44
- package/dist/angular/components/defer-slot.component.js +0 -149
- package/dist/angular/components/image.component.js +0 -202
- package/dist/angular/components/index.js +0 -4
- package/dist/angular/components/stream-slot.component.js +0 -103
- package/dist/dev/client/constants.ts +0 -26
- package/dist/dev/client/cssUtils.ts +0 -307
- package/dist/dev/client/domDiff.ts +0 -226
- package/dist/dev/client/domState.ts +0 -421
- package/dist/dev/client/domTracker.ts +0 -61
- package/dist/dev/client/errorOverlay.ts +0 -184
- package/dist/dev/client/frameworkDetect.ts +0 -63
- package/dist/dev/client/handlers/angular.ts +0 -551
- package/dist/dev/client/handlers/angularRuntime.ts +0 -206
- package/dist/dev/client/handlers/html.ts +0 -363
- package/dist/dev/client/handlers/htmx.ts +0 -272
- package/dist/dev/client/handlers/react.ts +0 -108
- package/dist/dev/client/handlers/rebuild.ts +0 -153
- package/dist/dev/client/handlers/svelte.ts +0 -332
- package/dist/dev/client/handlers/vue.ts +0 -292
- package/dist/dev/client/headPatch.ts +0 -233
- package/dist/dev/client/hmrClient.ts +0 -251
- package/dist/dev/client/hmrState.ts +0 -14
- package/dist/dev/client/moduleVersions.ts +0 -62
- package/dist/dev/client/reactRefreshSetup.ts +0 -33
- package/dist/src/angular/components/constants.d.ts +0 -53
- package/dist/svelte/components/AwaitSlot.svelte +0 -39
- package/dist/svelte/components/AwaitSlot.svelte.d.ts +0 -2
- package/dist/svelte/components/Head.svelte +0 -144
- package/dist/svelte/components/Head.svelte.d.ts +0 -2
- package/dist/svelte/components/Image.svelte +0 -164
- package/dist/svelte/components/Image.svelte.d.ts +0 -5
- package/dist/svelte/components/Island.svelte +0 -71
- package/dist/svelte/components/Island.svelte.d.ts +0 -5
- package/dist/svelte/components/JsonLd.svelte +0 -21
- package/dist/svelte/components/JsonLd.svelte.d.ts +0 -2
- package/dist/svelte/components/StreamSlot.svelte +0 -41
- package/dist/svelte/components/StreamSlot.svelte.d.ts +0 -2
- package/dist/types/globals.d.ts +0 -121
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import type {} from '../../../types/globals';
|
|
2
|
-
/* Angular HMR — Zoneless Runtime Preservation
|
|
3
|
-
DEV MODE ONLY — never included in production builds.
|
|
4
|
-
|
|
5
|
-
Runtime component patching via prototype swap and ɵcmp metadata swap.
|
|
6
|
-
State persists naturally via instance continuity — NO serialization.
|
|
7
|
-
|
|
8
|
-
Why state serialization was removed:
|
|
9
|
-
Angular component + service state lives on JS object instances.
|
|
10
|
-
Prototype swapping replaces method implementations without destroying
|
|
11
|
-
instances, so all state (properties, injected services, etc.) survives.
|
|
12
|
-
Serializing and reassigning state is fragile, lossy, and unnecessary.
|
|
13
|
-
|
|
14
|
-
Why zoneless requires manual tick():
|
|
15
|
-
With provideZonelessChangeDetection(), there is no Zone.js to
|
|
16
|
-
auto-trigger change detection. After swapping prototypes or templates,
|
|
17
|
-
we must explicitly call ApplicationRef.tick() to re-render.
|
|
18
|
-
|
|
19
|
-
Why this is safe in a multi-framework environment:
|
|
20
|
-
This module only touches Angular-specific globals (__ANGULAR_APP__,
|
|
21
|
-
__ANGULAR_HMR__). It never modifies document.body, React roots,
|
|
22
|
-
Vue instances, or Svelte components. The registry is keyed by
|
|
23
|
-
source file path, so name collisions across frameworks are impossible. */
|
|
24
|
-
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
-
type ComponentCtor = any;
|
|
27
|
-
|
|
28
|
-
type RegistryEntry = {
|
|
29
|
-
liveCtor: ComponentCtor;
|
|
30
|
-
id: string;
|
|
31
|
-
registeredAt: number;
|
|
32
|
-
updateCount: number;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const componentRegistry = new Map<string, RegistryEntry>();
|
|
36
|
-
let globalUpdateCount = 0;
|
|
37
|
-
|
|
38
|
-
const hasInjectorProviderChanges = (
|
|
39
|
-
oldCtor: ComponentCtor,
|
|
40
|
-
newCtor: ComponentCtor
|
|
41
|
-
) => {
|
|
42
|
-
if (oldCtor.ɵinj === undefined || newCtor.ɵinj === undefined) return false;
|
|
43
|
-
const oldP = oldCtor.ɵinj?.providers;
|
|
44
|
-
const newP = newCtor.ɵinj?.providers;
|
|
45
|
-
if (!Array.isArray(oldP) || !Array.isArray(newP)) return false;
|
|
46
|
-
|
|
47
|
-
return oldP.length !== newP.length;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const hasComponentProviderChanges = (
|
|
51
|
-
oldCtor: ComponentCtor,
|
|
52
|
-
newCtor: ComponentCtor
|
|
53
|
-
) => {
|
|
54
|
-
if (!oldCtor.ɵcmp || !newCtor.ɵcmp) return false;
|
|
55
|
-
const oldResolver = oldCtor.ɵcmp.providersResolver;
|
|
56
|
-
const newResolver = newCtor.ɵcmp.providersResolver;
|
|
57
|
-
|
|
58
|
-
return (oldResolver === undefined) !== (newResolver === undefined);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const hasProviderChanges = (oldCtor: ComponentCtor, newCtor: ComponentCtor) => {
|
|
62
|
-
if (hasInjectorProviderChanges(oldCtor, newCtor)) return true;
|
|
63
|
-
if (hasComponentProviderChanges(oldCtor, newCtor)) return true;
|
|
64
|
-
|
|
65
|
-
return false;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const register = (id: string, ctor: ComponentCtor) => {
|
|
69
|
-
if (!id || typeof ctor !== 'function') return;
|
|
70
|
-
if (!componentRegistry.has(id)) {
|
|
71
|
-
componentRegistry.set(id, {
|
|
72
|
-
id,
|
|
73
|
-
liveCtor: ctor,
|
|
74
|
-
registeredAt: Date.now(),
|
|
75
|
-
updateCount: 0
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const SKIP_STATIC_PROPS = [
|
|
81
|
-
'prototype',
|
|
82
|
-
'length',
|
|
83
|
-
'name',
|
|
84
|
-
'caller',
|
|
85
|
-
'arguments'
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
const swapPrototypeProp = (
|
|
89
|
-
liveCtor: ComponentCtor,
|
|
90
|
-
newProto: ComponentCtor,
|
|
91
|
-
prop: string
|
|
92
|
-
) => {
|
|
93
|
-
if (prop === 'constructor') return;
|
|
94
|
-
try {
|
|
95
|
-
const desc = Object.getOwnPropertyDescriptor(newProto, prop);
|
|
96
|
-
if (desc) Object.defineProperty(liveCtor.prototype, prop, desc);
|
|
97
|
-
} catch {
|
|
98
|
-
/* non-configurable */
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const swapStaticProp = (
|
|
103
|
-
liveCtor: ComponentCtor,
|
|
104
|
-
newCtor: ComponentCtor,
|
|
105
|
-
prop: string
|
|
106
|
-
) => {
|
|
107
|
-
if (SKIP_STATIC_PROPS.includes(prop)) return true;
|
|
108
|
-
try {
|
|
109
|
-
const desc = Object.getOwnPropertyDescriptor(newCtor, prop);
|
|
110
|
-
if (!desc) return true;
|
|
111
|
-
if (!desc.configurable) return prop !== 'ɵcmp' && prop !== 'ɵfac';
|
|
112
|
-
Object.defineProperty(liveCtor, prop, desc);
|
|
113
|
-
|
|
114
|
-
return true;
|
|
115
|
-
} catch {
|
|
116
|
-
return prop !== 'ɵcmp' && prop !== 'ɵfac';
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const patchConstructor = (entry: RegistryEntry, newCtor: ComponentCtor) => {
|
|
121
|
-
const { liveCtor } = entry;
|
|
122
|
-
|
|
123
|
-
const newProto = newCtor.prototype;
|
|
124
|
-
Object.getOwnPropertyNames(newProto).forEach((prop) => {
|
|
125
|
-
swapPrototypeProp(liveCtor, newProto, prop);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const allPatched = Object.getOwnPropertyNames(newCtor).every((prop) =>
|
|
129
|
-
swapStaticProp(liveCtor, newCtor, prop)
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
if (!allPatched) {
|
|
133
|
-
throw new Error('Cannot patch non-configurable Angular metadata');
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
globalUpdateCount++;
|
|
137
|
-
entry.updateCount++;
|
|
138
|
-
entry.registeredAt = Date.now();
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const applyUpdate = (id: string, newCtor: ComponentCtor) => {
|
|
142
|
-
const entry = componentRegistry.get(id);
|
|
143
|
-
if (!entry) {
|
|
144
|
-
register(id, newCtor);
|
|
145
|
-
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const { liveCtor } = entry;
|
|
150
|
-
if (liveCtor === newCtor) return true;
|
|
151
|
-
|
|
152
|
-
if (hasProviderChanges(liveCtor, newCtor)) {
|
|
153
|
-
console.warn(
|
|
154
|
-
'[HMR] Angular provider change detected for',
|
|
155
|
-
id,
|
|
156
|
-
'→ full reload'
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
if (newCtor.ɵcmp === undefined && liveCtor.ɵcmp !== undefined) {
|
|
162
|
-
console.warn(
|
|
163
|
-
'[HMR] New constructor missing ɵcmp for',
|
|
164
|
-
id,
|
|
165
|
-
'→ full reload'
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
patchConstructor(entry, newCtor);
|
|
173
|
-
|
|
174
|
-
return true;
|
|
175
|
-
} catch (err) {
|
|
176
|
-
console.error('[HMR] Angular runtime patch failed for', id, ':', err);
|
|
177
|
-
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const refresh = () => {
|
|
183
|
-
if (!window.__ANGULAR_APP__) return;
|
|
184
|
-
try {
|
|
185
|
-
window.__ANGULAR_APP__.tick();
|
|
186
|
-
} catch (err) {
|
|
187
|
-
console.warn('[HMR] Angular tick() failed after patch:', err);
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
export const installAngularHMRRuntime = () => {
|
|
192
|
-
if (typeof window === 'undefined') return;
|
|
193
|
-
window.__ANGULAR_HMR__ = {
|
|
194
|
-
applyUpdate,
|
|
195
|
-
refresh,
|
|
196
|
-
register,
|
|
197
|
-
getRegistry: () => componentRegistry,
|
|
198
|
-
// eslint-disable-next-line absolute/no-useless-function -- must be a callable method on the HMR API
|
|
199
|
-
getStats: () => ({
|
|
200
|
-
componentCount: componentRegistry.size,
|
|
201
|
-
updateCount: globalUpdateCount
|
|
202
|
-
})
|
|
203
|
-
};
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
installAngularHMRRuntime();
|
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
import type {} from '../../../types/globals';
|
|
2
|
-
/* HTML + script HMR update handlers */
|
|
3
|
-
|
|
4
|
-
import { DOM_UPDATE_DELAY_MS } from '../constants';
|
|
5
|
-
import { patchDOMInPlace } from '../domDiff';
|
|
6
|
-
import {
|
|
7
|
-
saveDOMState,
|
|
8
|
-
restoreDOMState,
|
|
9
|
-
saveFormState,
|
|
10
|
-
restoreFormState,
|
|
11
|
-
saveScrollState,
|
|
12
|
-
restoreScrollState
|
|
13
|
-
} from '../domState';
|
|
14
|
-
import { processCSSLinks, waitForCSSAndUpdate } from '../cssUtils';
|
|
15
|
-
import { patchHeadInPlace } from '../headPatch';
|
|
16
|
-
import { detectCurrentFramework } from '../frameworkDetect';
|
|
17
|
-
import type { ScriptInfo } from '../../../types/client';
|
|
18
|
-
import { restoreDOMChanges, snapshotDOMChanges } from '../domTracker';
|
|
19
|
-
import { hmrState } from '../hmrState';
|
|
20
|
-
|
|
21
|
-
const parseHTMLMessage = (
|
|
22
|
-
html: string | { body?: string; head?: string } | null | undefined
|
|
23
|
-
) => {
|
|
24
|
-
let body: string | null = null;
|
|
25
|
-
let head: string | null = null;
|
|
26
|
-
if (typeof html === 'string') {
|
|
27
|
-
body = html;
|
|
28
|
-
} else if (html && typeof html === 'object') {
|
|
29
|
-
body = html.body || null;
|
|
30
|
-
head = html.head || null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return { body, head };
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const applyHeadPatch = (htmlHead: string | null) => {
|
|
37
|
-
if (!htmlHead) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const doPatchHead = () => {
|
|
42
|
-
patchHeadInPlace(htmlHead);
|
|
43
|
-
};
|
|
44
|
-
if (hmrState.isFirstHMRUpdate) {
|
|
45
|
-
setTimeout(doPatchHead, DOM_UPDATE_DELAY_MS);
|
|
46
|
-
} else {
|
|
47
|
-
doPatchHead();
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const handleHTMLBodyWithHead = (
|
|
52
|
-
htmlBody: string,
|
|
53
|
-
htmlHead: string,
|
|
54
|
-
htmlDomState: ReturnType<typeof saveDOMState>
|
|
55
|
-
) => {
|
|
56
|
-
applyHeadPatch(htmlHead);
|
|
57
|
-
|
|
58
|
-
const cssResult = processCSSLinks(htmlHead);
|
|
59
|
-
|
|
60
|
-
const updateBodyAfterCSS = () => {
|
|
61
|
-
updateHTMLBody(htmlBody, htmlDomState, document.body);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
waitForCSSAndUpdate(cssResult, updateBodyAfterCSS);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const handleHTMLBodyWithoutHead = (
|
|
68
|
-
htmlBody: string,
|
|
69
|
-
htmlDomState: ReturnType<typeof saveDOMState>
|
|
70
|
-
) => {
|
|
71
|
-
const container = document.body;
|
|
72
|
-
if (!container) {
|
|
73
|
-
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
74
|
-
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
updateHTMLBodyDirect(htmlBody, htmlDomState, container);
|
|
79
|
-
restoreDOMState(container, htmlDomState);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export const handleHTMLUpdate = (message: {
|
|
83
|
-
data: {
|
|
84
|
-
html?: string | { body?: string; head?: string } | null;
|
|
85
|
-
};
|
|
86
|
-
}) => {
|
|
87
|
-
const htmlFrameworkCheck = detectCurrentFramework();
|
|
88
|
-
if (htmlFrameworkCheck !== 'html') {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (window.__REACT_ROOT__) {
|
|
93
|
-
window.__REACT_ROOT__ = undefined;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
sessionStorage.setItem('__HMR_ACTIVE__', 'true');
|
|
97
|
-
|
|
98
|
-
const htmlDomState = saveDOMState(document.body);
|
|
99
|
-
const { body: htmlBody, head: htmlHead } = parseHTMLMessage(
|
|
100
|
-
message.data.html
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
if (!htmlBody) {
|
|
104
|
-
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
105
|
-
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (htmlHead) {
|
|
110
|
-
handleHTMLBodyWithHead(htmlBody, htmlHead, htmlDomState);
|
|
111
|
-
} else {
|
|
112
|
-
handleHTMLBodyWithoutHead(htmlBody, htmlDomState);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
export const handleScriptUpdate = (message: {
|
|
116
|
-
data: { framework?: string; scriptPath?: string };
|
|
117
|
-
}) => {
|
|
118
|
-
const scriptFramework = message.data.framework;
|
|
119
|
-
const currentFw = detectCurrentFramework();
|
|
120
|
-
|
|
121
|
-
if (currentFw !== scriptFramework) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const { scriptPath } = message.data;
|
|
126
|
-
if (!scriptPath) {
|
|
127
|
-
console.warn('[HMR] No script path in update');
|
|
128
|
-
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const interactiveSelectors =
|
|
133
|
-
'button, [onclick], [onchange], [oninput], details, input, select, textarea';
|
|
134
|
-
document.body.querySelectorAll(interactiveSelectors).forEach((elem) => {
|
|
135
|
-
const cloned = elem.cloneNode(true);
|
|
136
|
-
if (elem.parentNode) {
|
|
137
|
-
elem.parentNode.replaceChild(cloned, elem);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const cacheBustedPath = `${scriptPath}?t=${Date.now()}`;
|
|
142
|
-
import(cacheBustedPath)
|
|
143
|
-
.then(() => true)
|
|
144
|
-
.catch((err: unknown) => {
|
|
145
|
-
console.error(
|
|
146
|
-
'[HMR] Script hot-reload failed, falling back to page reload:',
|
|
147
|
-
err
|
|
148
|
-
);
|
|
149
|
-
window.location.reload();
|
|
150
|
-
});
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
// eslint-disable-next-line absolute/no-useless-function -- must be called each time to capture current state
|
|
154
|
-
const saveHTMLState = () => ({
|
|
155
|
-
forms: saveFormState(),
|
|
156
|
-
scroll: saveScrollState()
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const preserveHmrScript = (
|
|
160
|
-
container: HTMLElement,
|
|
161
|
-
hmrScript: Element | null
|
|
162
|
-
) => {
|
|
163
|
-
if (hmrScript && !container.querySelector('script[data-hmr-client]')) {
|
|
164
|
-
container.appendChild(hmrScript);
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const updateHTMLBody = (
|
|
169
|
-
htmlBody: string,
|
|
170
|
-
htmlDomState: ReturnType<typeof saveDOMState>,
|
|
171
|
-
container: HTMLElement
|
|
172
|
-
) => {
|
|
173
|
-
if (!container) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const savedState = saveHTMLState();
|
|
178
|
-
const domSnapshot = snapshotDOMChanges(container);
|
|
179
|
-
|
|
180
|
-
const existingScripts = collectScripts(container);
|
|
181
|
-
const hmrScript = container.querySelector('script[data-hmr-client]');
|
|
182
|
-
const tempDiv = document.createElement('div');
|
|
183
|
-
tempDiv.innerHTML = htmlBody;
|
|
184
|
-
const newScripts = collectScriptsFromElement(tempDiv);
|
|
185
|
-
|
|
186
|
-
const htmlStructureChanged = didHTMLStructureChange(container, tempDiv);
|
|
187
|
-
|
|
188
|
-
if (htmlStructureChanged || didScriptsChange(existingScripts, newScripts)) {
|
|
189
|
-
patchDOMInPlace(container, htmlBody);
|
|
190
|
-
restoreDOMChanges(container, domSnapshot, htmlBody);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
preserveHmrScript(container, hmrScript);
|
|
194
|
-
|
|
195
|
-
requestAnimationFrame(() => {
|
|
196
|
-
restoreDOMState(container, htmlDomState);
|
|
197
|
-
restoreFormState(savedState.forms);
|
|
198
|
-
restoreScrollState(savedState.scroll);
|
|
199
|
-
|
|
200
|
-
if (
|
|
201
|
-
didScriptsChange(existingScripts, newScripts) ||
|
|
202
|
-
htmlStructureChanged
|
|
203
|
-
) {
|
|
204
|
-
cloneInteractiveElements(container);
|
|
205
|
-
reExecuteScripts(container, newScripts);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const cloneHmrListenerElements = (container: HTMLElement) => {
|
|
212
|
-
container
|
|
213
|
-
.querySelectorAll('[data-hmr-listeners-attached]')
|
|
214
|
-
.forEach((elem) => {
|
|
215
|
-
const cloned = elem.cloneNode(true);
|
|
216
|
-
if (elem.parentNode) {
|
|
217
|
-
elem.parentNode.replaceChild(cloned, elem);
|
|
218
|
-
}
|
|
219
|
-
if (cloned instanceof Element) {
|
|
220
|
-
cloned.removeAttribute('data-hmr-listeners-attached');
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const replaceInlineScript = (script: Element) => {
|
|
226
|
-
if (script.hasAttribute('data-hmr-client')) {
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const newScript = document.createElement('script');
|
|
231
|
-
newScript.textContent = script.textContent || '';
|
|
232
|
-
const scriptEl = script instanceof HTMLScriptElement ? script : null;
|
|
233
|
-
newScript.type = scriptEl?.type || 'text/javascript';
|
|
234
|
-
if (script.parentNode) {
|
|
235
|
-
script.parentNode.replaceChild(newScript, script);
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
const updateHTMLBodyDirect = (
|
|
240
|
-
htmlBody: string,
|
|
241
|
-
htmlDomState: ReturnType<typeof saveDOMState>,
|
|
242
|
-
container: HTMLElement
|
|
243
|
-
) => {
|
|
244
|
-
const savedState = saveHTMLState();
|
|
245
|
-
const domSnapshot = snapshotDOMChanges(container);
|
|
246
|
-
|
|
247
|
-
const tempDiv = document.createElement('div');
|
|
248
|
-
tempDiv.innerHTML = htmlBody;
|
|
249
|
-
const newScripts = collectScriptsFromElement(tempDiv);
|
|
250
|
-
const hmrScript = container.querySelector('script[data-hmr-client]');
|
|
251
|
-
|
|
252
|
-
patchDOMInPlace(container, htmlBody);
|
|
253
|
-
restoreDOMChanges(container, domSnapshot, htmlBody);
|
|
254
|
-
|
|
255
|
-
preserveHmrScript(container, hmrScript);
|
|
256
|
-
|
|
257
|
-
requestAnimationFrame(() => {
|
|
258
|
-
restoreDOMState(container, htmlDomState);
|
|
259
|
-
restoreFormState(savedState.forms);
|
|
260
|
-
restoreScrollState(savedState.scroll);
|
|
261
|
-
|
|
262
|
-
cloneHmrListenerElements(container);
|
|
263
|
-
|
|
264
|
-
removeOldScripts(container);
|
|
265
|
-
newScripts.forEach((scriptInfo) => {
|
|
266
|
-
const newScript = document.createElement('script');
|
|
267
|
-
const separator = scriptInfo.src.includes('?') ? '&' : '?';
|
|
268
|
-
newScript.src = `${scriptInfo.src + separator}t=${Date.now()}`;
|
|
269
|
-
newScript.type = scriptInfo.type;
|
|
270
|
-
container.appendChild(newScript);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
const inlineScripts = container.querySelectorAll('script:not([src])');
|
|
274
|
-
inlineScripts.forEach(replaceInlineScript);
|
|
275
|
-
});
|
|
276
|
-
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
/* Shared helpers for HTML body updates */
|
|
280
|
-
|
|
281
|
-
const collectScripts = (container: HTMLElement) =>
|
|
282
|
-
Array.from(container.querySelectorAll('script[src]')).map((script) => ({
|
|
283
|
-
src: script.getAttribute('src') || '',
|
|
284
|
-
type: script.getAttribute('type') || 'text/javascript'
|
|
285
|
-
}));
|
|
286
|
-
|
|
287
|
-
const collectScriptsFromElement = (elem: HTMLElement) =>
|
|
288
|
-
Array.from(elem.querySelectorAll('script[src]')).map((script) => ({
|
|
289
|
-
src: script.getAttribute('src') || '',
|
|
290
|
-
type: script.getAttribute('type') || 'text/javascript'
|
|
291
|
-
}));
|
|
292
|
-
|
|
293
|
-
const didScriptsChange = (oldScripts: ScriptInfo[], newScripts: ScriptInfo[]) =>
|
|
294
|
-
oldScripts.length !== newScripts.length ||
|
|
295
|
-
oldScripts.some((oldScript, idx) => {
|
|
296
|
-
const [oldSrcBase] = oldScript.src.split('?')[0]?.split('&') ?? [''];
|
|
297
|
-
const newScript = newScripts[idx];
|
|
298
|
-
if (!newScript) return true;
|
|
299
|
-
const [newSrcBase] = newScript.src.split('?')[0]?.split('&') ?? [''];
|
|
300
|
-
|
|
301
|
-
return oldSrcBase !== newSrcBase;
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
const normalizeHTMLForComparison = (element: HTMLElement) => {
|
|
305
|
-
const clonedNode = element.cloneNode(true);
|
|
306
|
-
if (!(clonedNode instanceof HTMLElement)) return '';
|
|
307
|
-
const clone = clonedNode;
|
|
308
|
-
const scripts = clone.querySelectorAll('script');
|
|
309
|
-
scripts.forEach((script) => {
|
|
310
|
-
if (script.parentNode) {
|
|
311
|
-
script.parentNode.removeChild(script);
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
const allElements = clone.querySelectorAll('*');
|
|
315
|
-
allElements.forEach((elem) => {
|
|
316
|
-
elem.removeAttribute('data-hmr-listeners-attached');
|
|
317
|
-
});
|
|
318
|
-
if (clone.removeAttribute) {
|
|
319
|
-
clone.removeAttribute('data-hmr-listeners-attached');
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return clone.innerHTML;
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
const didHTMLStructureChange = (container: HTMLElement, tempDiv: HTMLElement) =>
|
|
326
|
-
normalizeHTMLForComparison(container) !==
|
|
327
|
-
normalizeHTMLForComparison(tempDiv);
|
|
328
|
-
|
|
329
|
-
const cloneInteractiveElements = (container: HTMLElement) => {
|
|
330
|
-
const interactiveSelectors =
|
|
331
|
-
'button, [onclick], [onchange], [oninput], [onsubmit], ' +
|
|
332
|
-
'details, input[type="button"], input[type="submit"], input[type="reset"]';
|
|
333
|
-
container.querySelectorAll(interactiveSelectors).forEach((elem) => {
|
|
334
|
-
const cloned = elem.cloneNode(true);
|
|
335
|
-
if (elem.parentNode) {
|
|
336
|
-
elem.parentNode.replaceChild(cloned, elem);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
const removeOldScripts = (container: HTMLElement) => {
|
|
342
|
-
const scriptsInNewHTML = container.querySelectorAll('script[src]');
|
|
343
|
-
scriptsInNewHTML.forEach((script) => {
|
|
344
|
-
if (!script.hasAttribute('data-hmr-client')) {
|
|
345
|
-
script.remove();
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
const reExecuteScripts = (container: HTMLElement, newScripts: ScriptInfo[]) => {
|
|
351
|
-
removeOldScripts(container);
|
|
352
|
-
|
|
353
|
-
newScripts.forEach((scriptInfo) => {
|
|
354
|
-
const newScript = document.createElement('script');
|
|
355
|
-
const separator = scriptInfo.src.includes('?') ? '&' : '?';
|
|
356
|
-
newScript.src = `${scriptInfo.src + separator}t=${Date.now()}`;
|
|
357
|
-
newScript.type = scriptInfo.type;
|
|
358
|
-
container.appendChild(newScript);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
const inlineScripts = container.querySelectorAll('script:not([src])');
|
|
362
|
-
inlineScripts.forEach(replaceInlineScript);
|
|
363
|
-
};
|