@dom-expressions/runtime 0.50.0-next.15
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/CHANGELOG.md +348 -0
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/package.json +36 -0
- package/src/client.d.ts +117 -0
- package/src/client.js +893 -0
- package/src/constants.d.ts +23 -0
- package/src/constants.js +517 -0
- package/src/jsx-customize.mjs +58 -0
- package/src/jsx-h.d.ts +4148 -0
- package/src/jsx-properties.d.ts +93 -0
- package/src/jsx-update.mjs +96 -0
- package/src/jsx.d.ts +4134 -0
- package/src/reconcile.d.ts +1 -0
- package/src/reconcile.js +149 -0
- package/src/serializer.d.ts +3 -0
- package/src/serializer.js +54 -0
- package/src/server.d.ts +193 -0
- package/src/server.js +1302 -0
- package/src/universal.d.ts +38 -0
- package/src/universal.js +352 -0
package/src/server.js
ADDED
|
@@ -0,0 +1,1302 @@
|
|
|
1
|
+
import { ChildProperties } from "./constants";
|
|
2
|
+
import { sharedConfig, root, ssrHandleError, getOwner, runWithOwner } from "rxcore";
|
|
3
|
+
import { createSerializer, getLocalHeaderScript } from "./serializer";
|
|
4
|
+
|
|
5
|
+
// `mergeProps` comes from the framework like the client/universal entries —
|
|
6
|
+
// prop-merge semantics (function sources, precedence) belong to the reactive
|
|
7
|
+
// core, and a local copy here drifts from them (it resolved function sources
|
|
8
|
+
// for key enumeration only, dropping their values in SSR output).
|
|
9
|
+
export { createComponent, effect, memo, untrack, mergeProps } from "rxcore";
|
|
10
|
+
export { getOwner };
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
DOMWithState,
|
|
14
|
+
ChildProperties,
|
|
15
|
+
DOMElements,
|
|
16
|
+
SVGElements,
|
|
17
|
+
MathMLElements,
|
|
18
|
+
VoidElements,
|
|
19
|
+
RawTextElements,
|
|
20
|
+
Namespaces,
|
|
21
|
+
DelegatedEvents
|
|
22
|
+
} from "./constants.js";
|
|
23
|
+
|
|
24
|
+
// ---- Asset Manifest ----
|
|
25
|
+
|
|
26
|
+
function resolveAssets(moduleUrl, manifest) {
|
|
27
|
+
if (!manifest) return null;
|
|
28
|
+
const base = manifest._base || "/";
|
|
29
|
+
const entry = manifest[moduleUrl];
|
|
30
|
+
if (!entry) return null;
|
|
31
|
+
const css = [];
|
|
32
|
+
const js = [];
|
|
33
|
+
const visited = new Set();
|
|
34
|
+
const walk = key => {
|
|
35
|
+
if (visited.has(key)) return;
|
|
36
|
+
visited.add(key);
|
|
37
|
+
const e = manifest[key];
|
|
38
|
+
if (!e) return;
|
|
39
|
+
js.push(base + e.file);
|
|
40
|
+
if (e.css) for (let i = 0; i < e.css.length; i++) css.push(base + e.css[i]);
|
|
41
|
+
if (e.imports) for (let i = 0; i < e.imports.length; i++) walk(e.imports[i]);
|
|
42
|
+
};
|
|
43
|
+
walk(moduleUrl);
|
|
44
|
+
return { js, css };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function registerEntryAssets(manifest) {
|
|
48
|
+
if (!manifest) return;
|
|
49
|
+
const ctx = sharedConfig.context;
|
|
50
|
+
if (!ctx?.registerAsset) return;
|
|
51
|
+
for (const key in manifest) {
|
|
52
|
+
if (manifest[key].isEntry) {
|
|
53
|
+
const assets = resolveAssets(key, manifest);
|
|
54
|
+
if (assets) {
|
|
55
|
+
for (let i = 0; i < assets.css.length; i++) ctx.registerAsset("style", assets.css[i]);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---- Asset Tracking ----
|
|
63
|
+
|
|
64
|
+
function createAssetTracking() {
|
|
65
|
+
const boundaryModules = new Map();
|
|
66
|
+
const boundaryStyles = new Map();
|
|
67
|
+
const emittedAssets = new Set();
|
|
68
|
+
let currentBoundaryId = null;
|
|
69
|
+
return {
|
|
70
|
+
boundaryModules,
|
|
71
|
+
boundaryStyles,
|
|
72
|
+
emittedAssets,
|
|
73
|
+
get currentBoundaryId() {
|
|
74
|
+
return currentBoundaryId;
|
|
75
|
+
},
|
|
76
|
+
set currentBoundaryId(v) {
|
|
77
|
+
currentBoundaryId = v;
|
|
78
|
+
},
|
|
79
|
+
registerModule(moduleUrl, entryUrl) {
|
|
80
|
+
const id = currentBoundaryId || "";
|
|
81
|
+
let map = boundaryModules.get(id);
|
|
82
|
+
if (!map) {
|
|
83
|
+
map = {};
|
|
84
|
+
boundaryModules.set(id, map);
|
|
85
|
+
}
|
|
86
|
+
map[moduleUrl] = entryUrl;
|
|
87
|
+
},
|
|
88
|
+
getBoundaryModules(id) {
|
|
89
|
+
return boundaryModules.get(id) || null;
|
|
90
|
+
},
|
|
91
|
+
getBoundaryStyles(id) {
|
|
92
|
+
return boundaryStyles.get(id) || null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function applyAssetTracking(context, tracking, manifest) {
|
|
98
|
+
Object.defineProperty(context, "_currentBoundaryId", {
|
|
99
|
+
get() {
|
|
100
|
+
return tracking.currentBoundaryId;
|
|
101
|
+
},
|
|
102
|
+
set(v) {
|
|
103
|
+
tracking.currentBoundaryId = v;
|
|
104
|
+
},
|
|
105
|
+
configurable: true,
|
|
106
|
+
enumerable: true
|
|
107
|
+
});
|
|
108
|
+
context.registerModule = tracking.registerModule;
|
|
109
|
+
context.getBoundaryModules = tracking.getBoundaryModules;
|
|
110
|
+
if (manifest) context.resolveAssets = moduleUrl => resolveAssets(moduleUrl, manifest);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Based on https://github.com/WebReflection/domtagger/blob/master/esm/sanitizer.js
|
|
114
|
+
const VOID_ELEMENTS =
|
|
115
|
+
/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
|
|
116
|
+
// Fragment replacement helpers emitted into stream task scripts:
|
|
117
|
+
// - $df(id): swap template payload into the `pl-*` marker range.
|
|
118
|
+
// - $dfl(id): materialize fallback from `pl-*` template content without resolving.
|
|
119
|
+
// - $dflj(ids): materialize fallback content for every id in the list.
|
|
120
|
+
// - $dfs(id, count, defer): register pending stylesheet count for fragment `id`.
|
|
121
|
+
// - $dfc(id): style completion callback; reveals when the fragment/group is unblocked.
|
|
122
|
+
// - $dfg(id): group-style gate check; reveals a waiting group once all style counts hit zero.
|
|
123
|
+
// - $dfj(ids): reveal a group in registration order, waiting if any member still has pending styles.
|
|
124
|
+
const REPLACE_SCRIPT = `function $df(e,n,o,t){if(!(n=document.getElementById(e))||!(o=document.getElementById("pl-"+e)))return 0;for(;o&&8!==o.nodeType&&o.nodeValue!=="pl-"+e;)t=o.nextSibling,o.remove(),o=t;_$HY.done?o.remove():o.replaceWith(n.content),n.remove(),_$HY.fe(e);return 1}function $dfl(e,o,n){if(!(o=document.getElementById("pl-"+e)))return 0;if(o._$fl)return 1;for(n=o.nextSibling;n;){if(8===n.nodeType&&n.nodeValue==="pl-"+e){o.parentNode&&o.parentNode.insertBefore(o.content.cloneNode(!0),n),o._$fl=1;return 1}n=n.nextSibling}return 0}function $dflj(e,i){for(i=0;i<e.length;i++)$dfl(e[i])}function $dfs(e,c,d){(_$HY.sc=_$HY.sc||{})[e]=c,d&&((_$HY.sd=_$HY.sd||{})[e]=1)}function $dfg(e,g,i,k){if(!(g=_$HY.sg&&_$HY.sg[e]))return;for(i=0;i<g.length;i++)if(_$HY.sc&&_$HY.sc[g[i]]>0)return;for(i=0;i<g.length;i++)k=g[i],delete _$HY.sg[k],$df(k)}function $dfc(e){if(--_$HY.sc[e]<=0){delete _$HY.sc[e],_$HY.sg&&_$HY.sg[e]?$dfg(e):!(_$HY.sd&&_$HY.sd[e])&&$df(e);_$HY.sd&&delete _$HY.sd[e]}}function $dfj(e,i,n){for(i=0;i<e.length;i++)if(_$HY.sc&&_$HY.sc[e[i]]>0){for(n=0;n<e.length;n++)(_$HY.sg=_$HY.sg||{})[e[n]]=e;return}for(i=0;i<e.length;i++)$df(e[i])}`;
|
|
125
|
+
|
|
126
|
+
export function renderToString(code, options = {}) {
|
|
127
|
+
const { renderId = "", nonce, noScripts, manifest } = options;
|
|
128
|
+
let scripts = "";
|
|
129
|
+
const serializer = createSerializer({
|
|
130
|
+
scopeId: renderId,
|
|
131
|
+
plugins: options.plugins,
|
|
132
|
+
onData(script) {
|
|
133
|
+
if (noScripts) return;
|
|
134
|
+
if (!scripts) {
|
|
135
|
+
scripts = getLocalHeaderScript(renderId);
|
|
136
|
+
}
|
|
137
|
+
scripts += script + ";";
|
|
138
|
+
},
|
|
139
|
+
onError: options.onError
|
|
140
|
+
});
|
|
141
|
+
const tracking = createAssetTracking();
|
|
142
|
+
sharedConfig.context = {
|
|
143
|
+
assets: [],
|
|
144
|
+
nonce,
|
|
145
|
+
escape: escape,
|
|
146
|
+
resolve: resolveSSRNode,
|
|
147
|
+
ssr: ssr,
|
|
148
|
+
serialize(id, p) {
|
|
149
|
+
if (sharedConfig.context.noHydrate) return;
|
|
150
|
+
if (
|
|
151
|
+
p != null &&
|
|
152
|
+
typeof p === "object" &&
|
|
153
|
+
(typeof p.then === "function" || typeof p[Symbol.asyncIterator] === "function")
|
|
154
|
+
) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
"Cannot serialize async value in renderToString (id: " +
|
|
157
|
+
id +
|
|
158
|
+
"). " +
|
|
159
|
+
"Use renderToStream for async data."
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
serializer.write(id, p);
|
|
163
|
+
},
|
|
164
|
+
registerAsset(type, url) {
|
|
165
|
+
if (tracking.currentBoundaryId && type === "style") {
|
|
166
|
+
let styles = tracking.boundaryStyles.get(tracking.currentBoundaryId);
|
|
167
|
+
if (!styles) {
|
|
168
|
+
styles = new Set();
|
|
169
|
+
tracking.boundaryStyles.set(tracking.currentBoundaryId, styles);
|
|
170
|
+
}
|
|
171
|
+
styles.add(url);
|
|
172
|
+
}
|
|
173
|
+
tracking.emittedAssets.add(url);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
applyAssetTracking(sharedConfig.context, tracking, manifest);
|
|
177
|
+
registerEntryAssets(manifest);
|
|
178
|
+
let html = root(
|
|
179
|
+
d => {
|
|
180
|
+
setTimeout(d);
|
|
181
|
+
return resolveSSRSync(escape(code()));
|
|
182
|
+
},
|
|
183
|
+
{ id: renderId }
|
|
184
|
+
);
|
|
185
|
+
serializeFragmentAssets("", tracking.boundaryModules, sharedConfig.context);
|
|
186
|
+
sharedConfig.context.noHydrate = true;
|
|
187
|
+
serializer.close();
|
|
188
|
+
html = injectAssets(sharedConfig.context.assets, html);
|
|
189
|
+
html = injectPreloadLinks(tracking.emittedAssets, html, nonce);
|
|
190
|
+
if (scripts.length) html = injectScripts(html, scripts, options.nonce);
|
|
191
|
+
return html;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function renderToStream(code, options = {}) {
|
|
195
|
+
let { nonce, onCompleteShell, onCompleteAll, renderId = "", noScripts, manifest } = options;
|
|
196
|
+
let dispose;
|
|
197
|
+
const blockingPromises = new Set();
|
|
198
|
+
let headerEmitted = false;
|
|
199
|
+
const pushTask = task => {
|
|
200
|
+
if (noScripts) return;
|
|
201
|
+
if (!headerEmitted) {
|
|
202
|
+
headerEmitted = true;
|
|
203
|
+
tasks += getLocalHeaderScript(renderId);
|
|
204
|
+
}
|
|
205
|
+
tasks += task + ";";
|
|
206
|
+
if (!timer && firstFlushed) {
|
|
207
|
+
timer = setTimeout(writeTasks);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const onDone = () => {
|
|
211
|
+
writeTasks();
|
|
212
|
+
doShell();
|
|
213
|
+
onCompleteAll &&
|
|
214
|
+
onCompleteAll({
|
|
215
|
+
write(v) {
|
|
216
|
+
!completed && buffer.write(v);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
writable && writable.end();
|
|
220
|
+
completed = true;
|
|
221
|
+
if (firstFlushed) dispose();
|
|
222
|
+
};
|
|
223
|
+
const serializer = createSerializer({
|
|
224
|
+
scopeId: options.renderId,
|
|
225
|
+
plugins: options.plugins,
|
|
226
|
+
onData: pushTask,
|
|
227
|
+
onDone,
|
|
228
|
+
onError: options.onError
|
|
229
|
+
});
|
|
230
|
+
let rootAssetsSerialized = false;
|
|
231
|
+
const serializeRootAssets = () => {
|
|
232
|
+
if (rootAssetsSerialized) return;
|
|
233
|
+
rootAssetsSerialized = true;
|
|
234
|
+
// Ensure the root boundary's module map is written to the serializer
|
|
235
|
+
// before it flushes. A Loading boundary's resolve path can queue flushEnd
|
|
236
|
+
// while the shell is still pending (cascading root holes), which would
|
|
237
|
+
// otherwise call serializer.flush() before doShell() writes root _assets.
|
|
238
|
+
// Seroval silently drops writes after flush, so the root module mapping
|
|
239
|
+
// would be lost and lazy hydration would fail for root-level lazy modules.
|
|
240
|
+
serializeFragmentAssets("", tracking.boundaryModules, context);
|
|
241
|
+
};
|
|
242
|
+
const flushEnd = () => {
|
|
243
|
+
if (!registry.size) {
|
|
244
|
+
serializeRootAssets();
|
|
245
|
+
queue(() => queue(() => serializer.flush())); // double queue because of elsewhere
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
const registry = new Map();
|
|
249
|
+
const writeTasks = () => {
|
|
250
|
+
if (tasks.length && !completed && firstFlushed) {
|
|
251
|
+
buffer.write(`<script${nonce ? ` nonce="${nonce}"` : ""}>${tasks}</script>`);
|
|
252
|
+
tasks = "";
|
|
253
|
+
}
|
|
254
|
+
timer && clearTimeout(timer);
|
|
255
|
+
timer = null;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
let context;
|
|
259
|
+
let writable;
|
|
260
|
+
let tmp = "";
|
|
261
|
+
let tasks = "";
|
|
262
|
+
let firstFlushed = false;
|
|
263
|
+
let completed = false;
|
|
264
|
+
let shellCompleted = false;
|
|
265
|
+
let scriptFlushed = false;
|
|
266
|
+
let headStyles;
|
|
267
|
+
const revealGroups = new Map();
|
|
268
|
+
let timer = null;
|
|
269
|
+
const emitTask = task => {
|
|
270
|
+
pushTask(`${task}${!scriptFlushed ? ";" + REPLACE_SCRIPT : ""}`);
|
|
271
|
+
scriptFlushed = true;
|
|
272
|
+
};
|
|
273
|
+
function resolveRevealKeys(groupOrKeys, release, consume) {
|
|
274
|
+
if (Array.isArray(groupOrKeys)) return groupOrKeys.slice();
|
|
275
|
+
let group = revealGroups.get(groupOrKeys);
|
|
276
|
+
if (!group) {
|
|
277
|
+
if (!release) return;
|
|
278
|
+
group = { order: [], keys: new Set(), released: true };
|
|
279
|
+
revealGroups.set(groupOrKeys, group);
|
|
280
|
+
} else if (release) group.released = true;
|
|
281
|
+
if (!group.order.length) return;
|
|
282
|
+
const keys = group.order.slice();
|
|
283
|
+
if (consume) revealGroups.delete(groupOrKeys);
|
|
284
|
+
return keys;
|
|
285
|
+
}
|
|
286
|
+
let rootHoles = null;
|
|
287
|
+
let nextHoleId = 0;
|
|
288
|
+
let buffer = {
|
|
289
|
+
write(payload) {
|
|
290
|
+
tmp += payload;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const tracking = createAssetTracking();
|
|
294
|
+
|
|
295
|
+
sharedConfig.context = context = {
|
|
296
|
+
async: true,
|
|
297
|
+
assets: [],
|
|
298
|
+
nonce,
|
|
299
|
+
registerAsset(type, url) {
|
|
300
|
+
if (tracking.currentBoundaryId && type === "style") {
|
|
301
|
+
let styles = tracking.boundaryStyles.get(tracking.currentBoundaryId);
|
|
302
|
+
if (!styles) {
|
|
303
|
+
styles = new Set();
|
|
304
|
+
tracking.boundaryStyles.set(tracking.currentBoundaryId, styles);
|
|
305
|
+
}
|
|
306
|
+
styles.add(url);
|
|
307
|
+
}
|
|
308
|
+
if (!tracking.emittedAssets.has(url)) {
|
|
309
|
+
tracking.emittedAssets.add(url);
|
|
310
|
+
if (firstFlushed && type === "module") {
|
|
311
|
+
buffer.write(`<link rel="modulepreload" href="${url}">`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
block(p) {
|
|
316
|
+
if (!firstFlushed) blockingPromises.add(p);
|
|
317
|
+
},
|
|
318
|
+
replace(id, payloadFn) {
|
|
319
|
+
if (firstFlushed) return;
|
|
320
|
+
const placeholder = `<!--!$${id}-->`;
|
|
321
|
+
const first = html.indexOf(placeholder);
|
|
322
|
+
if (first === -1) return;
|
|
323
|
+
const last = html.indexOf(`<!--!$/${id}-->`, first + placeholder.length);
|
|
324
|
+
html =
|
|
325
|
+
html.slice(0, first) +
|
|
326
|
+
resolveSSRSync(escape(payloadFn())) +
|
|
327
|
+
html.slice(last + placeholder.length + 1);
|
|
328
|
+
},
|
|
329
|
+
serialize(id, p, deferStream) {
|
|
330
|
+
if (sharedConfig.context.noHydrate) return;
|
|
331
|
+
if (!firstFlushed && deferStream && typeof p === "object" && "then" in p) {
|
|
332
|
+
blockingPromises.add(p);
|
|
333
|
+
p.then(d => serializer.write(id, d)).catch(e => serializer.write(id, e));
|
|
334
|
+
} else serializer.write(id, p);
|
|
335
|
+
},
|
|
336
|
+
escape: escape,
|
|
337
|
+
resolve: resolveSSRNode,
|
|
338
|
+
ssr: ssr,
|
|
339
|
+
registerFragment(key, options) {
|
|
340
|
+
const revealGroup = options && options.revealGroup;
|
|
341
|
+
if (revealGroup) {
|
|
342
|
+
let group = revealGroups.get(revealGroup);
|
|
343
|
+
if (!group) {
|
|
344
|
+
group = { order: [], keys: new Set(), released: false };
|
|
345
|
+
revealGroups.set(revealGroup, group);
|
|
346
|
+
}
|
|
347
|
+
if (!group.keys.has(key)) {
|
|
348
|
+
group.keys.add(key);
|
|
349
|
+
group.order.push(key);
|
|
350
|
+
}
|
|
351
|
+
if (group.released) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
"registerFragment() for reveal group '" +
|
|
354
|
+
revealGroup +
|
|
355
|
+
"' was called after revealFragments(). Ensure template payload is emitted before grouped reveal."
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (!registry.has(key)) {
|
|
360
|
+
let resolve, reject;
|
|
361
|
+
const p = new Promise((r, rej) => ((resolve = r), (reject = rej)));
|
|
362
|
+
// double queue to ensure that the fragment is last but in same flush
|
|
363
|
+
registry.set(key, {
|
|
364
|
+
resolve: err =>
|
|
365
|
+
queue(() =>
|
|
366
|
+
queue(() => {
|
|
367
|
+
err ? reject(err) : resolve(true);
|
|
368
|
+
queue(flushEnd);
|
|
369
|
+
})
|
|
370
|
+
)
|
|
371
|
+
});
|
|
372
|
+
serializer.write(key + "_fr", p);
|
|
373
|
+
}
|
|
374
|
+
return (value, error) => {
|
|
375
|
+
if (registry.has(key)) {
|
|
376
|
+
const item = registry.get(key);
|
|
377
|
+
registry.delete(key);
|
|
378
|
+
|
|
379
|
+
if (item.children) {
|
|
380
|
+
for (const k in item.children) {
|
|
381
|
+
value = replacePlaceholder(value, k, item.children[k]);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const parentKey = waitForFragments(registry, key);
|
|
386
|
+
if (parentKey) {
|
|
387
|
+
const parent = registry.get(parentKey);
|
|
388
|
+
parent.children ||= {};
|
|
389
|
+
parent.children[key] = value !== undefined ? value : "";
|
|
390
|
+
serializeFragmentAssets(key, tracking.boundaryModules, context);
|
|
391
|
+
propagateBoundaryStyles(key, parentKey, tracking);
|
|
392
|
+
item.resolve();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (!completed) {
|
|
396
|
+
if (!firstFlushed) {
|
|
397
|
+
queue(() => (html = replacePlaceholder(html, key, value !== undefined ? value : "")));
|
|
398
|
+
serializeFragmentAssets(key, tracking.boundaryModules, context);
|
|
399
|
+
item.resolve(error);
|
|
400
|
+
} else {
|
|
401
|
+
serializeFragmentAssets(key, tracking.boundaryModules, context);
|
|
402
|
+
const styles = collectStreamStyles(key, tracking, headStyles);
|
|
403
|
+
const deferActivation = !!revealGroup;
|
|
404
|
+
if (styles.length) {
|
|
405
|
+
emitTask(`$dfs("${key}",${styles.length},${deferActivation ? 1 : 0})`);
|
|
406
|
+
writeTasks();
|
|
407
|
+
for (const url of styles) {
|
|
408
|
+
buffer.write(
|
|
409
|
+
`<link rel="stylesheet" href="${url}" onload="$dfc('${key}')" onerror="$dfc('${key}')">`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
buffer.write(
|
|
413
|
+
`<template id="${key}">${value !== undefined ? value : " "}</template>`
|
|
414
|
+
);
|
|
415
|
+
} else {
|
|
416
|
+
buffer.write(
|
|
417
|
+
`<template id="${key}">${value !== undefined ? value : " "}</template>`
|
|
418
|
+
);
|
|
419
|
+
if (!deferActivation) {
|
|
420
|
+
emitTask(`$df("${key}")`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
item.resolve(error);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return firstFlushed;
|
|
428
|
+
};
|
|
429
|
+
},
|
|
430
|
+
revealFragments(groupOrKeys) {
|
|
431
|
+
// Group reveal follows fragment registration order so visibility order
|
|
432
|
+
// cannot be changed by resolve timing.
|
|
433
|
+
const keys = resolveRevealKeys(groupOrKeys, true, true);
|
|
434
|
+
if (!keys) return;
|
|
435
|
+
emitTask(`$dfj(${JSON.stringify(keys)})`);
|
|
436
|
+
},
|
|
437
|
+
revealFallbacks(groupOrKeys) {
|
|
438
|
+
const keys = resolveRevealKeys(groupOrKeys, false, false);
|
|
439
|
+
if (!keys) return;
|
|
440
|
+
emitTask(`$dflj(${JSON.stringify(keys)})`);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
applyAssetTracking(context, tracking, manifest);
|
|
444
|
+
registerEntryAssets(manifest);
|
|
445
|
+
|
|
446
|
+
let html = root(
|
|
447
|
+
d => {
|
|
448
|
+
dispose = d;
|
|
449
|
+
const res = resolveSSRNode(escape(code()));
|
|
450
|
+
if (!res.h.length) return res.t[0];
|
|
451
|
+
rootHoles = [];
|
|
452
|
+
let out = res.t[0];
|
|
453
|
+
for (let i = 0; i < res.h.length; i++) {
|
|
454
|
+
const id = nextHoleId++;
|
|
455
|
+
rootHoles.push({ id, fn: res.h[i] });
|
|
456
|
+
out += `<!--rh${id}-->` + res.t[i + 1];
|
|
457
|
+
}
|
|
458
|
+
for (const p of res.p) blockingPromises.add(p);
|
|
459
|
+
return out;
|
|
460
|
+
},
|
|
461
|
+
{ id: renderId }
|
|
462
|
+
);
|
|
463
|
+
// Re-pull pending root holes, splicing sync results into `html` and
|
|
464
|
+
// re-queueing still-async ones (their retry promises join
|
|
465
|
+
// `blockingPromises`). Returns true once no holes remain.
|
|
466
|
+
function resolveRootHoles() {
|
|
467
|
+
if (!rootHoles) return true;
|
|
468
|
+
const pending = [];
|
|
469
|
+
for (const { id, fn } of rootHoles) {
|
|
470
|
+
const marker = `<!--rh${id}-->`;
|
|
471
|
+
const res = resolveSSRNode(fn);
|
|
472
|
+
if (!res.h.length) {
|
|
473
|
+
html = html.replace(marker, res.t[0]);
|
|
474
|
+
} else {
|
|
475
|
+
let out = res.t[0];
|
|
476
|
+
for (let j = 0; j < res.h.length; j++) {
|
|
477
|
+
const newId = nextHoleId++;
|
|
478
|
+
pending.push({ id: newId, fn: res.h[j] });
|
|
479
|
+
out += `<!--rh${newId}-->` + res.t[j + 1];
|
|
480
|
+
}
|
|
481
|
+
html = html.replace(marker, out);
|
|
482
|
+
for (const p of res.p) blockingPromises.add(p);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (pending.length) {
|
|
486
|
+
rootHoles = pending;
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
rootHoles = null;
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
function doShell() {
|
|
493
|
+
if (shellCompleted) return;
|
|
494
|
+
if (!resolveRootHoles()) return;
|
|
495
|
+
sharedConfig.context = context;
|
|
496
|
+
html = injectAssets(context.assets, html);
|
|
497
|
+
headStyles = new Set();
|
|
498
|
+
for (const url of tracking.emittedAssets) {
|
|
499
|
+
if (url.endsWith(".css")) headStyles.add(url);
|
|
500
|
+
}
|
|
501
|
+
html = injectPreloadLinks(tracking.emittedAssets, html, nonce);
|
|
502
|
+
serializeRootAssets();
|
|
503
|
+
if (tasks.length) html = injectScripts(html, tasks, nonce);
|
|
504
|
+
buffer.write(html);
|
|
505
|
+
tasks = "";
|
|
506
|
+
onCompleteShell &&
|
|
507
|
+
onCompleteShell({
|
|
508
|
+
write(v) {
|
|
509
|
+
!completed && buffer.write(v);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
shellCompleted = true;
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
then(fn) {
|
|
516
|
+
function complete() {
|
|
517
|
+
dispose();
|
|
518
|
+
fn(tmp);
|
|
519
|
+
}
|
|
520
|
+
if (onCompleteAll) {
|
|
521
|
+
let ogComplete = onCompleteAll;
|
|
522
|
+
onCompleteAll = options => {
|
|
523
|
+
ogComplete(options);
|
|
524
|
+
complete();
|
|
525
|
+
};
|
|
526
|
+
} else onCompleteAll = complete;
|
|
527
|
+
function flush() {
|
|
528
|
+
allSettled(blockingPromises).then(() => {
|
|
529
|
+
setTimeout(() => {
|
|
530
|
+
if (!resolveRootHoles()) return flush();
|
|
531
|
+
queue(flushEnd);
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
flush();
|
|
536
|
+
},
|
|
537
|
+
pipe(w) {
|
|
538
|
+
function flush() {
|
|
539
|
+
allSettled(blockingPromises).then(() => {
|
|
540
|
+
setTimeout(() => {
|
|
541
|
+
doShell();
|
|
542
|
+
if (!shellCompleted) return flush();
|
|
543
|
+
buffer = writable = w;
|
|
544
|
+
buffer.write(tmp);
|
|
545
|
+
firstFlushed = true;
|
|
546
|
+
if (completed) {
|
|
547
|
+
dispose();
|
|
548
|
+
writable.end();
|
|
549
|
+
} else flushEnd();
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
flush();
|
|
554
|
+
},
|
|
555
|
+
pipeTo(w) {
|
|
556
|
+
let resolve;
|
|
557
|
+
const p = new Promise(r => (resolve = r));
|
|
558
|
+
function flush() {
|
|
559
|
+
allSettled(blockingPromises).then(() => {
|
|
560
|
+
setTimeout(() => {
|
|
561
|
+
doShell();
|
|
562
|
+
if (!shellCompleted) return flush();
|
|
563
|
+
const encoder = new TextEncoder();
|
|
564
|
+
const writer = w.getWriter();
|
|
565
|
+
writable = {
|
|
566
|
+
end() {
|
|
567
|
+
writer.releaseLock();
|
|
568
|
+
w.close().catch(() => {});
|
|
569
|
+
resolve();
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
buffer = {
|
|
573
|
+
write(payload) {
|
|
574
|
+
writer.write(encoder.encode(payload)).catch(() => {});
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
buffer.write(tmp);
|
|
578
|
+
firstFlushed = true;
|
|
579
|
+
if (completed) {
|
|
580
|
+
dispose();
|
|
581
|
+
writable.end();
|
|
582
|
+
} else flushEnd();
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
flush();
|
|
587
|
+
return p;
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// components
|
|
593
|
+
export function HydrationScript(props) {
|
|
594
|
+
const { nonce } = sharedConfig.context;
|
|
595
|
+
return ssr(generateHydrationScript({ nonce, ...props }));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Compiler-emitted: tags `fn` so `ssr()` routes it through the grouped
|
|
599
|
+
// fast-path. One grouped fn per element collapses N attribute/textContent
|
|
600
|
+
// closures into one array-returning call.
|
|
601
|
+
export function ssrGroup(fn, n) {
|
|
602
|
+
fn.$g = n;
|
|
603
|
+
return fn;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Cold-path NotReady catch + owner-capture wrap, shared by every site
|
|
607
|
+
// that escalates a sync throw to a streaming retry slot. Returns
|
|
608
|
+
// `{ fn, p }` on `NotReadyError` (with `fn` bound to the original owner
|
|
609
|
+
// so retries see the same id counter / contexts) or `null` for
|
|
610
|
+
// non-NotReady errors so callers can fall back to their contribute-
|
|
611
|
+
// nothing path.
|
|
612
|
+
function buildAsyncWrap(err, node) {
|
|
613
|
+
const p = ssrHandleError(err);
|
|
614
|
+
if (!p) return null;
|
|
615
|
+
const owner = getOwner();
|
|
616
|
+
return { fn: owner ? () => runWithOwner(owner, node) : node, p };
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Cold-path helper for the first hit of a group. Isolates `try/catch`
|
|
620
|
+
// from the hot `ssr()` loop. Returns the value array on sync success,
|
|
621
|
+
// `{ fn, p }` on `NotReadyError` escalation, or `null` for non-NotReady
|
|
622
|
+
// errors (matches `tryResolveString`'s "" path).
|
|
623
|
+
function ssrFirstGroupHit(hole) {
|
|
624
|
+
try {
|
|
625
|
+
return hole();
|
|
626
|
+
} catch (err) {
|
|
627
|
+
return buildAsyncWrap(err, hole);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function tryResolveFunctionHole(hole) {
|
|
632
|
+
let value;
|
|
633
|
+
try {
|
|
634
|
+
value = hole();
|
|
635
|
+
} catch (err) {
|
|
636
|
+
return buildAsyncWrap(err, hole) || "";
|
|
637
|
+
}
|
|
638
|
+
const t = typeof value;
|
|
639
|
+
if (t === "string") return value;
|
|
640
|
+
if (t === "number") return "" + value;
|
|
641
|
+
if (value == null || t === "boolean") return "";
|
|
642
|
+
return tryResolveString(value);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Cold-path: splice a nested `{ t, h, p }` template into `result` at
|
|
646
|
+
// its current last segment. Used when `tryResolveString` walks into a
|
|
647
|
+
// template object that itself carries async holes.
|
|
648
|
+
function mergeTemplateInto(result, node) {
|
|
649
|
+
result.t[result.t.length - 1] += node.t[0];
|
|
650
|
+
if (node.t.length > 1) {
|
|
651
|
+
result.t.push(...node.t.slice(1));
|
|
652
|
+
result.h.push(...node.h);
|
|
653
|
+
result.p.push(...node.p);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function appendResolvedNode(result, node) {
|
|
658
|
+
if (node.fn !== undefined) {
|
|
659
|
+
result.h.push(node.fn);
|
|
660
|
+
result.p.push(node.p);
|
|
661
|
+
result.t.push("");
|
|
662
|
+
} else if (node.merge !== undefined) mergeTemplateInto(result, node.merge);
|
|
663
|
+
else resolveSSRNode(node.bail, result);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Module-scoped cache for grouped retry slots. Slots fire contiguously
|
|
667
|
+
// in queue order, so slot 0 evaluates `fn` once and caches `arr`
|
|
668
|
+
// (success) or `err` (NotReady) on the module slots; slots `1..N-1`
|
|
669
|
+
// short-circuit on `_lastGroupFn === fn`. Cache invalidates on a
|
|
670
|
+
// different fn (next group) or when slot 0 re-fires (next retry pass
|
|
671
|
+
// for the same group). Net: 1 evaluation per group per pass.
|
|
672
|
+
let _lastGroupFn = null;
|
|
673
|
+
let _lastGroupArr = null;
|
|
674
|
+
let _lastGroupErr = null;
|
|
675
|
+
|
|
676
|
+
function ssrGroupSlot(fn, idx) {
|
|
677
|
+
return () => {
|
|
678
|
+
if (idx > 0 && _lastGroupFn === fn) {
|
|
679
|
+
if (_lastGroupArr !== null) return _lastGroupArr[idx];
|
|
680
|
+
throw _lastGroupErr;
|
|
681
|
+
}
|
|
682
|
+
_lastGroupFn = fn;
|
|
683
|
+
_lastGroupArr = null;
|
|
684
|
+
_lastGroupErr = null;
|
|
685
|
+
try {
|
|
686
|
+
_lastGroupArr = fn();
|
|
687
|
+
return _lastGroupArr[idx];
|
|
688
|
+
} catch (err) {
|
|
689
|
+
_lastGroupErr = err;
|
|
690
|
+
throw err;
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// rendering
|
|
696
|
+
export function ssr(t) {
|
|
697
|
+
// Inlined hole resolution — uses `arguments` instead of a `(t, ...nodes)`
|
|
698
|
+
// rest parameter to avoid the per-call holes-array allocation. Inline
|
|
699
|
+
// string/number/null/bool fast paths skip `tryResolveString` entirely
|
|
700
|
+
// for the typical "all-static-after-eval" hole shape; only the heavy
|
|
701
|
+
// path (async escalation) materializes the `{ t, h, p }` result shape.
|
|
702
|
+
//
|
|
703
|
+
// Group fast-path (`hole.$g` set by compiler `_$ssrGroup`): one call
|
|
704
|
+
// returns an array of values for >=N hole positions. The check is at
|
|
705
|
+
// the END of the typeof chain so non-function holes don't pay for it.
|
|
706
|
+
const len = arguments.length;
|
|
707
|
+
if (len === 1) return { t };
|
|
708
|
+
let s = t[0];
|
|
709
|
+
let result = null;
|
|
710
|
+
let lastGroup = null;
|
|
711
|
+
// Array on sync success, `{ fn, p }` on escalation, null otherwise.
|
|
712
|
+
let lastGroupVal = null;
|
|
713
|
+
let lastGroupIdx = 0;
|
|
714
|
+
for (let i = 1; i < len; i++) {
|
|
715
|
+
const hole = arguments[i];
|
|
716
|
+
const ht = typeof hole;
|
|
717
|
+
if (ht === "string") {
|
|
718
|
+
if (result === null) s += hole;
|
|
719
|
+
else result.t[result.t.length - 1] += hole;
|
|
720
|
+
} else if (ht === "number") {
|
|
721
|
+
if (result === null) s += hole;
|
|
722
|
+
else result.t[result.t.length - 1] += hole;
|
|
723
|
+
} else if (hole == null || ht === "boolean") {
|
|
724
|
+
// skip
|
|
725
|
+
} else if (ht === "function" && hole.$g) {
|
|
726
|
+
let value;
|
|
727
|
+
let hasValue = false;
|
|
728
|
+
if (lastGroup !== hole) {
|
|
729
|
+
const r = ssrFirstGroupHit(hole);
|
|
730
|
+
if (r !== null) {
|
|
731
|
+
lastGroup = hole;
|
|
732
|
+
lastGroupVal = r;
|
|
733
|
+
lastGroupIdx = 0;
|
|
734
|
+
if (!Array.isArray(r) && result === null) {
|
|
735
|
+
result = { t: [s], h: [], p: [] };
|
|
736
|
+
s = "";
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// r === null: non-NotReady error, contribute nothing — matches
|
|
740
|
+
// the `return ""` path in `tryResolveString`.
|
|
741
|
+
}
|
|
742
|
+
if (lastGroup === hole) {
|
|
743
|
+
if (Array.isArray(lastGroupVal)) {
|
|
744
|
+
value = lastGroupVal[lastGroupIdx++];
|
|
745
|
+
hasValue = true;
|
|
746
|
+
} else {
|
|
747
|
+
result.h.push(ssrGroupSlot(lastGroupVal.fn, lastGroupIdx++));
|
|
748
|
+
result.p.push(lastGroupVal.p);
|
|
749
|
+
result.t.push("");
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (hasValue) {
|
|
753
|
+
// Type dispatch on the dequeued value. textContent expressions
|
|
754
|
+
// (e.g., `_$escape(item().title)`) can return arrays when the
|
|
755
|
+
// input is an array, so we cannot assume strings here.
|
|
756
|
+
const vt = typeof value;
|
|
757
|
+
if (vt === "string" || vt === "number") {
|
|
758
|
+
if (result === null) s += value;
|
|
759
|
+
else result.t[result.t.length - 1] += value;
|
|
760
|
+
} else if (value == null || vt === "boolean") {
|
|
761
|
+
// skip
|
|
762
|
+
} else if (result !== null) {
|
|
763
|
+
resolveSSRNode(value, result);
|
|
764
|
+
} else {
|
|
765
|
+
const rs = tryResolveString(value);
|
|
766
|
+
if (typeof rs === "string") {
|
|
767
|
+
s += rs;
|
|
768
|
+
} else {
|
|
769
|
+
result = { t: [s], h: [], p: [] };
|
|
770
|
+
s = "";
|
|
771
|
+
if (rs.merge !== undefined) mergeTemplateInto(result, rs.merge);
|
|
772
|
+
else resolveSSRNode(rs.bail, result);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
} else if (result !== null) {
|
|
777
|
+
resolveSSRNode(hole, result);
|
|
778
|
+
} else if (ht === "function") {
|
|
779
|
+
const r = tryResolveFunctionHole(hole);
|
|
780
|
+
if (typeof r === "string") s += r;
|
|
781
|
+
else {
|
|
782
|
+
result = { t: [s], h: [], p: [] };
|
|
783
|
+
s = "";
|
|
784
|
+
appendResolvedNode(result, r);
|
|
785
|
+
}
|
|
786
|
+
} else {
|
|
787
|
+
const r = tryResolveString(hole);
|
|
788
|
+
if (typeof r === "string") {
|
|
789
|
+
s += r;
|
|
790
|
+
} else {
|
|
791
|
+
// Escalation: allocate the heavy `{ t, h, p }` result shape and
|
|
792
|
+
// splice in the sync prefix we accumulated.
|
|
793
|
+
result = { t: [s], h: [], p: [] };
|
|
794
|
+
s = "";
|
|
795
|
+
appendResolvedNode(result, r);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const next = t[i];
|
|
799
|
+
if (result === null) s += next;
|
|
800
|
+
else result.t[result.t.length - 1] += next;
|
|
801
|
+
}
|
|
802
|
+
if (result === null) return { t: s };
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
export function ssrClassName(value) {
|
|
807
|
+
if (!value) return "";
|
|
808
|
+
if (typeof value === "string") return escape(value, true);
|
|
809
|
+
value = classListToObject(value);
|
|
810
|
+
let classKeys = Object.keys(value),
|
|
811
|
+
result = "";
|
|
812
|
+
for (let i = 0, len = classKeys.length; i < len; i++) {
|
|
813
|
+
const key = classKeys[i],
|
|
814
|
+
classValue = !!value[key];
|
|
815
|
+
if (!key || key === "undefined" || !classValue) continue;
|
|
816
|
+
i && (result += " ");
|
|
817
|
+
// Object keys land inside class="..." so they must be attribute-escaped.
|
|
818
|
+
result += escape(key, true);
|
|
819
|
+
}
|
|
820
|
+
return result;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
export function ssrStyle(value) {
|
|
824
|
+
if (!value) return "";
|
|
825
|
+
if (typeof value === "string") return escape(value, true);
|
|
826
|
+
|
|
827
|
+
let result = "";
|
|
828
|
+
const k = Object.keys(value);
|
|
829
|
+
for (let i = 0; i < k.length; i++) {
|
|
830
|
+
// Object keys land inside style="..." so they must be attribute-escaped
|
|
831
|
+
// to prevent breaking out via `"`.
|
|
832
|
+
const s = escape(k[i], true);
|
|
833
|
+
const v = value[k[i]];
|
|
834
|
+
if (v != undefined) {
|
|
835
|
+
if (i) result += ";";
|
|
836
|
+
const r = escape(v, true);
|
|
837
|
+
if (r != undefined && r !== "undefined") {
|
|
838
|
+
result += `${s}:${r}`;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return result;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
export function ssrStyleProperty(name, value) {
|
|
846
|
+
// Compiler contract: for literal-key `style={{ color: v }}` the compiler
|
|
847
|
+
// passes a fixed string like `"color:"`; for computed-key
|
|
848
|
+
// `style={{ [k]: v }}` the compiler wraps the key with `_$escape(k, true)`
|
|
849
|
+
// before concatenating the `:` suffix. Either way `name` is safe to splice
|
|
850
|
+
// into style="..." without further escaping.
|
|
851
|
+
return value != null ? name + value : "";
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// review with new ssr
|
|
855
|
+
export function ssrElement(tag, props, children, needsId) {
|
|
856
|
+
if (props == null) props = {};
|
|
857
|
+
else if (typeof props === "function") props = props();
|
|
858
|
+
const skipChildren = VOID_ELEMENTS.test(tag);
|
|
859
|
+
const keys = Object.keys(props);
|
|
860
|
+
let result = `<${tag}${needsId ? ssrHydrationKey() : ""} `;
|
|
861
|
+
for (let i = 0; i < keys.length; i++) {
|
|
862
|
+
const prop = keys[i];
|
|
863
|
+
if (ChildProperties.has(prop)) {
|
|
864
|
+
if (children === undefined && !skipChildren)
|
|
865
|
+
children =
|
|
866
|
+
tag === "script" || tag === "style" || prop === "innerHTML"
|
|
867
|
+
? props[prop]
|
|
868
|
+
: escape(props[prop]);
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
const value = props[prop];
|
|
872
|
+
if (prop === "style") {
|
|
873
|
+
result += `style="${ssrStyle(value)}"`;
|
|
874
|
+
} else if (prop === "class") {
|
|
875
|
+
result += `class="${ssrClassName(value)}"`;
|
|
876
|
+
} else if (
|
|
877
|
+
value == undefined ||
|
|
878
|
+
prop === "ref" ||
|
|
879
|
+
prop.slice(0, 2) === "on" ||
|
|
880
|
+
prop.slice(0, 5) === "prop:"
|
|
881
|
+
) {
|
|
882
|
+
continue;
|
|
883
|
+
} else if (typeof value === "boolean") {
|
|
884
|
+
if (!value) continue;
|
|
885
|
+
result += escape(prop);
|
|
886
|
+
} else {
|
|
887
|
+
result += value === "" ? escape(prop) : `${escape(prop)}="${escape(value, true)}"`;
|
|
888
|
+
}
|
|
889
|
+
if (i !== keys.length - 1) result += " ";
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (skipChildren) return { t: result + "/>" };
|
|
893
|
+
if (typeof children === "function") children = children();
|
|
894
|
+
return ssr([result + ">", `</${tag}>`], resolveSSRNode(children, undefined, true));
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
export function ssrAttribute(key, value) {
|
|
898
|
+
// Compiler contract: `key` is always a compile-time string literal emitted
|
|
899
|
+
// from a JSX attribute name (see setAttr in babel-plugin/src/ssr/element.js)
|
|
900
|
+
// which can never contain `"`, `<`, `&`, or `>`. `value` is already
|
|
901
|
+
// attribute-escaped by the compiler via `_$escape(..., true)`. Both are
|
|
902
|
+
// trusted here so this hot path stays a pure string concatenation.
|
|
903
|
+
return value == null || value === false ? "" : value === true ? ` ${key}` : ` ${key}="${value}"`;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
export function ssrHydrationKey() {
|
|
907
|
+
const hk = getHydrationKey();
|
|
908
|
+
return hk ? ` _hk=${hk}` : "";
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
export function escape(s, attr) {
|
|
912
|
+
const t = typeof s;
|
|
913
|
+
if (t !== "string") {
|
|
914
|
+
if (!attr && Array.isArray(s)) {
|
|
915
|
+
const joined = tryJoinPlainSSRArray(s);
|
|
916
|
+
if (joined !== undefined) return joined;
|
|
917
|
+
s = s.slice(); // avoids double escaping - https://github.com/ryansolid/dom-expressions/issues/393
|
|
918
|
+
for (let i = 0; i < s.length; i++) s[i] = escape(s[i]);
|
|
919
|
+
return s;
|
|
920
|
+
}
|
|
921
|
+
if (attr && t === "boolean") return s;
|
|
922
|
+
return s;
|
|
923
|
+
}
|
|
924
|
+
// Fast path: single forward pass over the string. Most values (color
|
|
925
|
+
// names, ids, prop strings, plain text) contain none of `&`, `<`, or
|
|
926
|
+
// `"`, so we bail without allocating. Slow path resumes from the first
|
|
927
|
+
// hit so we don't re-scan the clean prefix.
|
|
928
|
+
// Char codes: `&` = 38, `<` = 60, `"` = 34.
|
|
929
|
+
const delimCode = attr ? 34 : 60;
|
|
930
|
+
const len = s.length;
|
|
931
|
+
for (let i = 0; i < len; i++) {
|
|
932
|
+
const c = s.charCodeAt(i);
|
|
933
|
+
if (c === 38 || c === delimCode) return escapeSlow(s, attr, i);
|
|
934
|
+
}
|
|
935
|
+
return s;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Slow path: at least one of `&`, `<`/`"` was found at position `start`.
|
|
939
|
+
// Kept separate so `escape()` stays small and inlinable in the hot path.
|
|
940
|
+
function escapeSlow(s, attr, start) {
|
|
941
|
+
const delim = attr ? '"' : "<";
|
|
942
|
+
const delimCode = attr ? 34 : 60;
|
|
943
|
+
const escDelim = attr ? """ : "<";
|
|
944
|
+
// Seed iDelim/iAmp from the first hit we already found, so we don't
|
|
945
|
+
// re-scan the prefix we just proved is clean.
|
|
946
|
+
const c0 = s.charCodeAt(start);
|
|
947
|
+
let iDelim = c0 === delimCode ? start : s.indexOf(delim, start);
|
|
948
|
+
let iAmp = c0 === 38 ? start : s.indexOf("&", start);
|
|
949
|
+
|
|
950
|
+
let left = 0,
|
|
951
|
+
out = "";
|
|
952
|
+
|
|
953
|
+
while (iDelim >= 0 && iAmp >= 0) {
|
|
954
|
+
if (iDelim < iAmp) {
|
|
955
|
+
if (left < iDelim) out += s.substring(left, iDelim);
|
|
956
|
+
out += escDelim;
|
|
957
|
+
left = iDelim + 1;
|
|
958
|
+
iDelim = s.indexOf(delim, left);
|
|
959
|
+
} else {
|
|
960
|
+
if (left < iAmp) out += s.substring(left, iAmp);
|
|
961
|
+
out += "&";
|
|
962
|
+
left = iAmp + 1;
|
|
963
|
+
iAmp = s.indexOf("&", left);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (iDelim >= 0) {
|
|
968
|
+
do {
|
|
969
|
+
if (left < iDelim) out += s.substring(left, iDelim);
|
|
970
|
+
out += escDelim;
|
|
971
|
+
left = iDelim + 1;
|
|
972
|
+
iDelim = s.indexOf(delim, left);
|
|
973
|
+
} while (iDelim >= 0);
|
|
974
|
+
} else
|
|
975
|
+
while (iAmp >= 0) {
|
|
976
|
+
if (left < iAmp) out += s.substring(left, iAmp);
|
|
977
|
+
out += "&";
|
|
978
|
+
left = iAmp + 1;
|
|
979
|
+
iAmp = s.indexOf("&", left);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
return left < s.length ? out + s.substring(left) : out;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function tryJoinPlainSSRArray(nodes) {
|
|
986
|
+
if (nodes.length === 0) return undefined;
|
|
987
|
+
let out = "";
|
|
988
|
+
for (let i = 0, len = nodes.length; i < len; i++) {
|
|
989
|
+
const node = nodes[i];
|
|
990
|
+
if (node == null || typeof node !== "object" || node.h || typeof node.t !== "string") {
|
|
991
|
+
return undefined;
|
|
992
|
+
}
|
|
993
|
+
out += node.t;
|
|
994
|
+
}
|
|
995
|
+
return out;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
export function getHydrationKey() {
|
|
999
|
+
const hydrate = sharedConfig.context;
|
|
1000
|
+
return hydrate && sharedConfig.getNextContextId();
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
export function applyRef(r, element) {
|
|
1004
|
+
Array.isArray(r) ? r.flat(Infinity).forEach(f => f && f(element)) : r(element);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
export function useAssets(fn) {
|
|
1008
|
+
sharedConfig.context.assets.push(() => resolveSSRSync(escape(fn())));
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
export function getAssets() {
|
|
1012
|
+
const assets = sharedConfig.context.assets;
|
|
1013
|
+
let out = "";
|
|
1014
|
+
for (let i = 0, len = assets.length; i < len; i++) out += assets[i]();
|
|
1015
|
+
return out;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// consider deprecating
|
|
1019
|
+
export function Assets(props) {
|
|
1020
|
+
useAssets(() => props.children);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
export function generateHydrationScript({ eventNames = ["click", "input"], nonce } = {}) {
|
|
1024
|
+
return `<script${
|
|
1025
|
+
nonce ? ` nonce="${nonce}"` : ""
|
|
1026
|
+
}>window._$HY||(e=>{let t=e=>e&&e.hasAttribute&&(e.hasAttribute("_hk")?e:t(e.host&&e.host.nodeType?e.host:e.parentNode));["${eventNames.join(
|
|
1027
|
+
'","'
|
|
1028
|
+
)}"].forEach((o=>document.addEventListener(o,(o=>{if(!e.events)return;let s=t(o.composedPath&&o.composedPath()[0]||o.target);s&&!e.completed.has(s)&&e.events.push([s,o])}))))})(_$HY={events:[],completed:new WeakSet,r:{},fe(){}});</script><!--xs-->`;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function queue(fn) {
|
|
1032
|
+
return Promise.resolve().then(fn);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function allSettled(promises) {
|
|
1036
|
+
let size = promises.size;
|
|
1037
|
+
return Promise.allSettled(promises).then(() => {
|
|
1038
|
+
if (promises.size !== size) return allSettled(promises);
|
|
1039
|
+
return;
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function injectAssets(assets, html) {
|
|
1044
|
+
if (!assets || !assets.length) return html;
|
|
1045
|
+
let out = "";
|
|
1046
|
+
for (let i = 0, len = assets.length; i < len; i++) out += assets[i]();
|
|
1047
|
+
const index = html.indexOf("</head>");
|
|
1048
|
+
if (index === -1) return html;
|
|
1049
|
+
return html.slice(0, index) + out + html.slice(index);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function injectPreloadLinks(emittedAssets, html, nonce) {
|
|
1053
|
+
if (!emittedAssets.size) return html;
|
|
1054
|
+
let links = "";
|
|
1055
|
+
for (const url of emittedAssets) {
|
|
1056
|
+
if (url.endsWith(".css")) {
|
|
1057
|
+
links += `<link rel="stylesheet" href="${url}">`;
|
|
1058
|
+
} else {
|
|
1059
|
+
links += `<link rel="modulepreload" href="${url}">`;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const index = html.indexOf("</head>");
|
|
1063
|
+
if (index === -1) return html;
|
|
1064
|
+
return html.slice(0, index) + links + html.slice(index);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function serializeFragmentAssets(key, boundaryModules, context) {
|
|
1068
|
+
const map = boundaryModules.get(key);
|
|
1069
|
+
if (!map || !Object.keys(map).length) return;
|
|
1070
|
+
context.serialize(key + "_assets", map);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
function propagateBoundaryStyles(childKey, parentKey, tracking) {
|
|
1074
|
+
const childStyles = tracking.getBoundaryStyles(childKey);
|
|
1075
|
+
if (!childStyles) return;
|
|
1076
|
+
let parentStyles = tracking.boundaryStyles.get(parentKey);
|
|
1077
|
+
if (!parentStyles) {
|
|
1078
|
+
parentStyles = new Set();
|
|
1079
|
+
tracking.boundaryStyles.set(parentKey, parentStyles);
|
|
1080
|
+
}
|
|
1081
|
+
for (const url of childStyles) {
|
|
1082
|
+
parentStyles.add(url);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function collectStreamStyles(key, tracking, headStyles) {
|
|
1087
|
+
const styles = tracking.getBoundaryStyles(key);
|
|
1088
|
+
if (!styles) return [];
|
|
1089
|
+
const result = [];
|
|
1090
|
+
for (const url of styles) {
|
|
1091
|
+
if (!headStyles || !headStyles.has(url)) {
|
|
1092
|
+
result.push(url);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return result;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function injectScripts(html, scripts, nonce) {
|
|
1099
|
+
const tag = `<script${nonce ? ` nonce="${nonce}"` : ""}>${scripts}</script>`;
|
|
1100
|
+
const index = html.indexOf("<!--xs-->");
|
|
1101
|
+
if (index > -1) {
|
|
1102
|
+
return html.slice(0, index) + tag + html.slice(index);
|
|
1103
|
+
}
|
|
1104
|
+
return html + tag;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function waitForFragments(registry, key) {
|
|
1108
|
+
for (const k of [...registry.keys()].reverse()) {
|
|
1109
|
+
if (key.startsWith(k)) return k;
|
|
1110
|
+
}
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function replacePlaceholder(html, key, value) {
|
|
1115
|
+
const marker = `<template id="pl-${key}">`;
|
|
1116
|
+
const close = `<!--pl-${key}-->`;
|
|
1117
|
+
|
|
1118
|
+
const first = html.indexOf(marker);
|
|
1119
|
+
if (first === -1) return html;
|
|
1120
|
+
const last = html.indexOf(close, first + marker.length);
|
|
1121
|
+
|
|
1122
|
+
return html.slice(0, first) + value + html.slice(last + close.length);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function classListToObject(classList) {
|
|
1126
|
+
if (Array.isArray(classList)) {
|
|
1127
|
+
const result = {};
|
|
1128
|
+
flattenClassList(classList, result);
|
|
1129
|
+
return result;
|
|
1130
|
+
}
|
|
1131
|
+
return classList;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function flattenClassList(list, result) {
|
|
1135
|
+
for (let i = 0, len = list.length; i < len; i++) {
|
|
1136
|
+
const item = list[i];
|
|
1137
|
+
if (Array.isArray(item)) flattenClassList(item, result);
|
|
1138
|
+
else if (typeof item === "object" && item != null) Object.assign(result, item);
|
|
1139
|
+
else if (item || item === 0) result[item] = true;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Best-effort sync resolution. Returns a string when the entire `node`
|
|
1144
|
+
// resolves synchronously to text. Otherwise returns one of three shapes
|
|
1145
|
+
// shared with `ssrFirstGroupHit`:
|
|
1146
|
+
// `{ fn, p }` — function hole that threw `NotReadyError`; `fn` is
|
|
1147
|
+
// wrapped in `runWithOwner(owner, ...)` so the streaming
|
|
1148
|
+
// engine's retry sees the same context the original sync
|
|
1149
|
+
// call did.
|
|
1150
|
+
// `{ merge }` — template object with non-empty `h`.
|
|
1151
|
+
// `{ bail }` — interior contains async; `bail` carries the evaluated
|
|
1152
|
+
// form (typically the array we walked) so the caller can
|
|
1153
|
+
// hand it to `resolveSSRNode` without re-invoking the
|
|
1154
|
+
// original closure. Re-invocation is unsafe — a hole may
|
|
1155
|
+
// read stateful getters such as JSX `props.children`
|
|
1156
|
+
// whose backing component rebuilds an owner subtree on
|
|
1157
|
+
// each access, producing a divergent hydration tree.
|
|
1158
|
+
function tryResolveString(node) {
|
|
1159
|
+
const t = typeof node;
|
|
1160
|
+
if (t === "string") return node;
|
|
1161
|
+
if (t === "number") return "" + node;
|
|
1162
|
+
if (node == null || t === "boolean") return "";
|
|
1163
|
+
if (t === "object") {
|
|
1164
|
+
if (Array.isArray(node)) {
|
|
1165
|
+
const joined = tryJoinPlainSSRArray(node);
|
|
1166
|
+
if (joined !== undefined) return joined;
|
|
1167
|
+
let s = "";
|
|
1168
|
+
let prevNonObj = false;
|
|
1169
|
+
for (let i = 0, len = node.length; i < len; i++) {
|
|
1170
|
+
const item = node[i];
|
|
1171
|
+
const itemNonObj = item !== null && typeof item !== "object";
|
|
1172
|
+
if (prevNonObj && itemNonObj) s += "<!--!$-->";
|
|
1173
|
+
prevNonObj = itemNonObj;
|
|
1174
|
+
const r = tryResolveString(item);
|
|
1175
|
+
if (typeof r !== "string") return { bail: node };
|
|
1176
|
+
s += r;
|
|
1177
|
+
}
|
|
1178
|
+
return s;
|
|
1179
|
+
}
|
|
1180
|
+
if (node.h && node.h.length > 0) return { merge: node };
|
|
1181
|
+
return Array.isArray(node.t) ? node.t[0] : node.t;
|
|
1182
|
+
}
|
|
1183
|
+
if (t === "function") {
|
|
1184
|
+
let v;
|
|
1185
|
+
try {
|
|
1186
|
+
v = node();
|
|
1187
|
+
} catch (err) {
|
|
1188
|
+
return buildAsyncWrap(err, node) || "";
|
|
1189
|
+
}
|
|
1190
|
+
// Recurse on the evaluated value. If recursion bails, propagate the
|
|
1191
|
+
// bail object unchanged — its `bail` field already carries the
|
|
1192
|
+
// deepest evaluated form, so the caller never re-invokes `node`.
|
|
1193
|
+
return tryResolveString(v);
|
|
1194
|
+
}
|
|
1195
|
+
return "";
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function resolveSSRNode(
|
|
1199
|
+
node,
|
|
1200
|
+
result = {
|
|
1201
|
+
t: [""],
|
|
1202
|
+
h: [],
|
|
1203
|
+
p: []
|
|
1204
|
+
},
|
|
1205
|
+
top
|
|
1206
|
+
) {
|
|
1207
|
+
const t = typeof node;
|
|
1208
|
+
if (t === "string" || t === "number") {
|
|
1209
|
+
result.t[result.t.length - 1] += node;
|
|
1210
|
+
} else if (node == null || t === "boolean") {
|
|
1211
|
+
} else if (Array.isArray(node)) {
|
|
1212
|
+
let prevNonObj = false;
|
|
1213
|
+
for (let i = 0, len = node.length; i < len; i++) {
|
|
1214
|
+
const item = node[i];
|
|
1215
|
+
const itemNonObj = item !== null && typeof item !== "object";
|
|
1216
|
+
if (!top && prevNonObj && itemNonObj) result.t[result.t.length - 1] += `<!--!$-->`;
|
|
1217
|
+
prevNonObj = itemNonObj;
|
|
1218
|
+
resolveSSRNode(item, result);
|
|
1219
|
+
}
|
|
1220
|
+
} else if (t === "object") {
|
|
1221
|
+
if (node.h) {
|
|
1222
|
+
result.t[result.t.length - 1] += node.t[0];
|
|
1223
|
+
if (node.t.length > 1) {
|
|
1224
|
+
result.t.push(...node.t.slice(1));
|
|
1225
|
+
result.h.push(...node.h);
|
|
1226
|
+
result.p.push(...node.p);
|
|
1227
|
+
}
|
|
1228
|
+
} else result.t[result.t.length - 1] += node.t;
|
|
1229
|
+
} else if (t === "function") {
|
|
1230
|
+
try {
|
|
1231
|
+
resolveSSRNode(node(), result);
|
|
1232
|
+
} catch (err) {
|
|
1233
|
+
const wrap = buildAsyncWrap(err, node);
|
|
1234
|
+
if (wrap) {
|
|
1235
|
+
result.h.push(wrap.fn);
|
|
1236
|
+
result.p.push(wrap.p);
|
|
1237
|
+
result.t.push("");
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return result;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function resolveSSRSync(node) {
|
|
1245
|
+
const res = resolveSSRNode(node);
|
|
1246
|
+
if (!res.h.length) return res.t[0];
|
|
1247
|
+
throw new Error("This value cannot be rendered synchronously. Are you missing a boundary?");
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// experimental
|
|
1251
|
+
export const RequestContext = Symbol();
|
|
1252
|
+
|
|
1253
|
+
export function getRequestEvent() {
|
|
1254
|
+
return globalThis[RequestContext]
|
|
1255
|
+
? globalThis[RequestContext].getStore() ||
|
|
1256
|
+
(sharedConfig.context && sharedConfig.context.event) ||
|
|
1257
|
+
console.warn(
|
|
1258
|
+
"RequestEvent is missing. This is most likely due to accessing `getRequestEvent` non-managed async scope in a partially polyfilled environment. Try moving it above all `await` calls."
|
|
1259
|
+
)
|
|
1260
|
+
: undefined;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/** @deprecated use renderToStream which also returns a promise */
|
|
1264
|
+
export function renderToStringAsync(code, options = {}) {
|
|
1265
|
+
return new Promise(resolve => renderToStream(code, options).then(resolve));
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// client-only APIs
|
|
1269
|
+
|
|
1270
|
+
export {
|
|
1271
|
+
notSup as style,
|
|
1272
|
+
notSup as insert,
|
|
1273
|
+
notSup as spread,
|
|
1274
|
+
notSup as delegateEvents,
|
|
1275
|
+
notSup as registerDelegatedRoot,
|
|
1276
|
+
notSup as unregisterDelegatedRoot,
|
|
1277
|
+
notSup as registerDelegatedContainer,
|
|
1278
|
+
notSup as unregisterDelegatedContainer,
|
|
1279
|
+
notSup as getDelegatedRoot,
|
|
1280
|
+
notSup as dynamicProperty,
|
|
1281
|
+
notSup as setAttribute,
|
|
1282
|
+
notSup as setAttributeNS,
|
|
1283
|
+
notSup as addEvent,
|
|
1284
|
+
notSup as render,
|
|
1285
|
+
notSup as template,
|
|
1286
|
+
notSup as setProperty,
|
|
1287
|
+
notSup as className,
|
|
1288
|
+
notSup as assign,
|
|
1289
|
+
notSup as hydrate,
|
|
1290
|
+
notSup as getNextElement,
|
|
1291
|
+
notSup as getNextMatch,
|
|
1292
|
+
notSup as getNextMarker,
|
|
1293
|
+
notSup as runHydrationEvents,
|
|
1294
|
+
notSup as ref,
|
|
1295
|
+
notSup as setStyleProperty
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
function notSup() {
|
|
1299
|
+
throw new Error(
|
|
1300
|
+
"Client-only API called on the server side. Run client-only code in onMount, or conditionally run client-only component with <Show>."
|
|
1301
|
+
);
|
|
1302
|
+
}
|