@lytjs/ssr 6.0.0
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/index.cjs +741 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +372 -0
- package/dist/index.d.ts +372 -0
- package/dist/index.mjs +722 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +50 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
import { isString, isNumber, isArray, isObject, isFunction, isNullish } from '@lytjs/common-is';
|
|
2
|
+
import { defineComponent } from '@lytjs/component';
|
|
3
|
+
import { createVNode } from '@lytjs/vdom';
|
|
4
|
+
import { signal, computedSignal } from '@lytjs/reactivity';
|
|
5
|
+
import { promises } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
|
|
8
|
+
// src/render.ts
|
|
9
|
+
function escapeHtml(str) {
|
|
10
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
11
|
+
}
|
|
12
|
+
function renderToString(vnode) {
|
|
13
|
+
if (vnode === null || vnode === void 0) {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
if (isString(vnode)) {
|
|
17
|
+
return escapeHtml(vnode);
|
|
18
|
+
}
|
|
19
|
+
if (isNumber(vnode)) {
|
|
20
|
+
return String(vnode);
|
|
21
|
+
}
|
|
22
|
+
if (isArray(vnode)) {
|
|
23
|
+
return vnode.map((child) => renderToString(child)).join("");
|
|
24
|
+
}
|
|
25
|
+
if (!isObject(vnode)) {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
const node = vnode;
|
|
29
|
+
if (node.type === "text" || typeof node.type === "symbol") {
|
|
30
|
+
return escapeHtml(String(node.children || ""));
|
|
31
|
+
}
|
|
32
|
+
if (isFunction(node.type)) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
if (isString(node.type)) {
|
|
36
|
+
const tag = node.type;
|
|
37
|
+
const props = node.props || {};
|
|
38
|
+
const voidTags = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"];
|
|
39
|
+
if (voidTags.includes(tag)) {
|
|
40
|
+
return `<${tag}${renderAttributes(props)}>`;
|
|
41
|
+
}
|
|
42
|
+
const children = renderToString(node.children);
|
|
43
|
+
return `<${tag}${renderAttributes(props)}>${children}</${tag}>`;
|
|
44
|
+
}
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
function renderAttributes(props) {
|
|
48
|
+
const attrs = [];
|
|
49
|
+
for (const [key, value] of Object.entries(props)) {
|
|
50
|
+
if (key.startsWith("on") || key === "key" || key === "ref") {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (key === "class" || key === "className") {
|
|
54
|
+
if (value) {
|
|
55
|
+
const classValue = isObject(value) ? Object.entries(value).filter(([, v]) => v).map(([k]) => k).join(" ") : String(value);
|
|
56
|
+
if (classValue) {
|
|
57
|
+
attrs.push(` class="${escapeHtml(classValue)}"`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (key === "style") {
|
|
63
|
+
if (value) {
|
|
64
|
+
const styleValue = isObject(value) ? Object.entries(value).map(([k, v]) => `${k}:${v}`).join(";") : String(value);
|
|
65
|
+
if (styleValue) {
|
|
66
|
+
attrs.push(` style="${escapeHtml(styleValue)}"`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (value === true) {
|
|
72
|
+
attrs.push(` ${key}`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (value === false || value === null || value === void 0) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
attrs.push(` ${key}="${escapeHtml(String(value))}"`);
|
|
79
|
+
}
|
|
80
|
+
return attrs.join("");
|
|
81
|
+
}
|
|
82
|
+
function renderToHtml(vnode, options = {}) {
|
|
83
|
+
const { title = "LytJS App", lang = "zh-CN", head = "", bodyAttrs = {} } = options;
|
|
84
|
+
const content = renderToString(vnode);
|
|
85
|
+
const bodyAttrsStr = Object.entries(bodyAttrs).map(([k, v]) => ` ${k}="${escapeHtml(v)}"`).join("");
|
|
86
|
+
return `<!DOCTYPE html>
|
|
87
|
+
<html lang="${lang}">
|
|
88
|
+
<head>
|
|
89
|
+
<meta charset="UTF-8">
|
|
90
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
91
|
+
<title>${escapeHtml(title)}</title>
|
|
92
|
+
${head}
|
|
93
|
+
</head>
|
|
94
|
+
<body${bodyAttrsStr}>
|
|
95
|
+
<div id="app">${content}</div>
|
|
96
|
+
</body>
|
|
97
|
+
</html>`;
|
|
98
|
+
}
|
|
99
|
+
var render_default = {
|
|
100
|
+
renderToString,
|
|
101
|
+
renderToHtml
|
|
102
|
+
};
|
|
103
|
+
var VirtualList = defineComponent({
|
|
104
|
+
name: "LytVirtualList",
|
|
105
|
+
props: {
|
|
106
|
+
data: { type: Array, default: () => [] },
|
|
107
|
+
itemHeight: { type: Number, default: 50 },
|
|
108
|
+
height: { type: Number, default: 400 },
|
|
109
|
+
buffer: { type: Number, default: 5 },
|
|
110
|
+
class: { type: String, default: "" }
|
|
111
|
+
},
|
|
112
|
+
setup(props, { slots }) {
|
|
113
|
+
const scrollTop = signal(0);
|
|
114
|
+
const visibleCount = computedSignal(() => {
|
|
115
|
+
return Math.ceil(props.height / props.itemHeight) + props.buffer * 2;
|
|
116
|
+
});
|
|
117
|
+
const startIndex = computedSignal(() => {
|
|
118
|
+
const start = Math.floor(scrollTop() / props.itemHeight);
|
|
119
|
+
return Math.max(0, start - props.buffer);
|
|
120
|
+
});
|
|
121
|
+
const endIndex = computedSignal(() => {
|
|
122
|
+
return Math.min(props.data.length, startIndex() + visibleCount());
|
|
123
|
+
});
|
|
124
|
+
const visibleData = computedSignal(() => {
|
|
125
|
+
return props.data.slice(startIndex(), endIndex());
|
|
126
|
+
});
|
|
127
|
+
const totalHeight = computedSignal(() => {
|
|
128
|
+
return props.data.length * props.itemHeight;
|
|
129
|
+
});
|
|
130
|
+
const offset = computedSignal(() => {
|
|
131
|
+
return startIndex() * props.itemHeight;
|
|
132
|
+
});
|
|
133
|
+
const handleScroll = (e) => {
|
|
134
|
+
const target = e.target;
|
|
135
|
+
scrollTop.set(target.scrollTop);
|
|
136
|
+
};
|
|
137
|
+
const getListClass = () => {
|
|
138
|
+
const classes = ["lyt-virtual-list"];
|
|
139
|
+
if (props.class) classes.push(props.class);
|
|
140
|
+
return classes.join(" ");
|
|
141
|
+
};
|
|
142
|
+
return () => {
|
|
143
|
+
const items = visibleData().map((item, index) => {
|
|
144
|
+
const actualIndex = startIndex() + index;
|
|
145
|
+
return createVNode(
|
|
146
|
+
"div",
|
|
147
|
+
{
|
|
148
|
+
class: "lyt-virtual-list__item",
|
|
149
|
+
style: `height: ${props.itemHeight}px;`,
|
|
150
|
+
"data-index": actualIndex
|
|
151
|
+
},
|
|
152
|
+
slots.default?.({ item, index: actualIndex })
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
return createVNode(
|
|
156
|
+
"div",
|
|
157
|
+
{
|
|
158
|
+
class: getListClass(),
|
|
159
|
+
style: `height: ${props.height}px; overflow-y: auto;`,
|
|
160
|
+
onScroll: handleScroll
|
|
161
|
+
},
|
|
162
|
+
[
|
|
163
|
+
createVNode(
|
|
164
|
+
"div",
|
|
165
|
+
{
|
|
166
|
+
class: "lyt-virtual-list__content",
|
|
167
|
+
style: `height: ${totalHeight()}px; position: relative;`
|
|
168
|
+
},
|
|
169
|
+
[
|
|
170
|
+
createVNode(
|
|
171
|
+
"div",
|
|
172
|
+
{
|
|
173
|
+
class: "lyt-virtual-list__visible",
|
|
174
|
+
style: `position: absolute; top: ${offset()}px; left: 0; right: 0;`
|
|
175
|
+
},
|
|
176
|
+
items
|
|
177
|
+
)
|
|
178
|
+
]
|
|
179
|
+
)
|
|
180
|
+
]
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
var DEFAULT_CHUNK_SIZE = 4096;
|
|
186
|
+
var SUSPENSE_TYPE = "Suspense";
|
|
187
|
+
function isSuspenseVNode(vnode) {
|
|
188
|
+
return isObject(vnode) && (typeof vnode.type === "object" && vnode.type !== null && "__suspense" in vnode.type || isString(vnode.type) && vnode.type === SUSPENSE_TYPE);
|
|
189
|
+
}
|
|
190
|
+
function collectChunks(vnode, suspenseBoundaryIndex = []) {
|
|
191
|
+
const chunks = [];
|
|
192
|
+
if (vnode == null) {
|
|
193
|
+
return chunks;
|
|
194
|
+
}
|
|
195
|
+
if (isString(vnode) || isNumber(vnode)) {
|
|
196
|
+
chunks.push(renderToString(vnode));
|
|
197
|
+
return chunks;
|
|
198
|
+
}
|
|
199
|
+
if (isArray(vnode)) {
|
|
200
|
+
for (const child of vnode) {
|
|
201
|
+
chunks.push(...collectChunks(child, suspenseBoundaryIndex));
|
|
202
|
+
}
|
|
203
|
+
return chunks;
|
|
204
|
+
}
|
|
205
|
+
if (!isObject(vnode)) {
|
|
206
|
+
return chunks;
|
|
207
|
+
}
|
|
208
|
+
const node = vnode;
|
|
209
|
+
if (isSuspenseVNode(node)) {
|
|
210
|
+
suspenseBoundaryIndex.push(chunks.length);
|
|
211
|
+
}
|
|
212
|
+
if (node.type === "text" || typeof node.type === "symbol") {
|
|
213
|
+
chunks.push(renderToString(node));
|
|
214
|
+
return chunks;
|
|
215
|
+
}
|
|
216
|
+
if (isFunction(node.type)) {
|
|
217
|
+
chunks.push(renderToString(node));
|
|
218
|
+
return chunks;
|
|
219
|
+
}
|
|
220
|
+
if (isString(node.type)) {
|
|
221
|
+
chunks.push(renderToString(node));
|
|
222
|
+
return chunks;
|
|
223
|
+
}
|
|
224
|
+
return chunks;
|
|
225
|
+
}
|
|
226
|
+
function splitIntoByteChunks(html, chunkSize) {
|
|
227
|
+
const encoder = new TextEncoder();
|
|
228
|
+
const chunks = [];
|
|
229
|
+
let remaining = html;
|
|
230
|
+
while (remaining.length > 0) {
|
|
231
|
+
const encoded = encoder.encode(remaining);
|
|
232
|
+
if (encoded.length <= chunkSize) {
|
|
233
|
+
chunks.push(remaining);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
let cutIndex = 0;
|
|
237
|
+
let byteCount = 0;
|
|
238
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
239
|
+
byteCount += encoder.encode(remaining[i]).length;
|
|
240
|
+
if (byteCount > chunkSize) {
|
|
241
|
+
cutIndex = i;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (cutIndex === 0) {
|
|
246
|
+
cutIndex = 1;
|
|
247
|
+
}
|
|
248
|
+
chunks.push(remaining.slice(0, cutIndex));
|
|
249
|
+
remaining = remaining.slice(cutIndex);
|
|
250
|
+
}
|
|
251
|
+
return chunks;
|
|
252
|
+
}
|
|
253
|
+
function renderToStream(vnode, options) {
|
|
254
|
+
const {
|
|
255
|
+
chunkSize = DEFAULT_CHUNK_SIZE,
|
|
256
|
+
onShellReady,
|
|
257
|
+
onError
|
|
258
|
+
} = options || {};
|
|
259
|
+
const encoder = new TextEncoder();
|
|
260
|
+
return new ReadableStream({
|
|
261
|
+
start(controller) {
|
|
262
|
+
try {
|
|
263
|
+
const suspenseBoundaryIndex = [];
|
|
264
|
+
const chunks = collectChunks(vnode, suspenseBoundaryIndex);
|
|
265
|
+
const fullHtml = chunks.join("");
|
|
266
|
+
if (suspenseBoundaryIndex.length > 0) {
|
|
267
|
+
const shellBoundary = suspenseBoundaryIndex[0];
|
|
268
|
+
const shellChunks = chunks.slice(0, shellBoundary);
|
|
269
|
+
const shellHtml = shellChunks.join("");
|
|
270
|
+
const byteChunks = splitIntoByteChunks(shellHtml, chunkSize);
|
|
271
|
+
for (const chunk of byteChunks) {
|
|
272
|
+
controller.enqueue(encoder.encode(chunk));
|
|
273
|
+
}
|
|
274
|
+
onShellReady?.();
|
|
275
|
+
const remainingChunks = chunks.slice(shellBoundary);
|
|
276
|
+
const remainingHtml = remainingChunks.join("");
|
|
277
|
+
const remainingByteChunks = splitIntoByteChunks(remainingHtml, chunkSize);
|
|
278
|
+
for (const chunk of remainingByteChunks) {
|
|
279
|
+
controller.enqueue(encoder.encode(chunk));
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
const byteChunks = splitIntoByteChunks(fullHtml, chunkSize);
|
|
283
|
+
for (const chunk of byteChunks) {
|
|
284
|
+
controller.enqueue(encoder.encode(chunk));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
controller.close();
|
|
288
|
+
} catch (err) {
|
|
289
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
290
|
+
onError?.(error);
|
|
291
|
+
controller.error(error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function renderToStreamAsync(vnode, options) {
|
|
297
|
+
const {
|
|
298
|
+
chunkSize = DEFAULT_CHUNK_SIZE,
|
|
299
|
+
onShellReady,
|
|
300
|
+
onError,
|
|
301
|
+
prefetchContext,
|
|
302
|
+
onDataPrefetched
|
|
303
|
+
} = options || {};
|
|
304
|
+
const encoder = new TextEncoder();
|
|
305
|
+
return new ReadableStream({
|
|
306
|
+
async start(controller) {
|
|
307
|
+
try {
|
|
308
|
+
const prefetchData = await collectAndPrefetchData(vnode, prefetchContext);
|
|
309
|
+
if (Object.keys(prefetchData).length > 0) {
|
|
310
|
+
onDataPrefetched?.(prefetchData);
|
|
311
|
+
}
|
|
312
|
+
const html = await renderToStringAsync(vnode, prefetchData);
|
|
313
|
+
const byteChunks = splitIntoByteChunks(html, chunkSize);
|
|
314
|
+
onShellReady?.();
|
|
315
|
+
for (const chunk of byteChunks) {
|
|
316
|
+
controller.enqueue(encoder.encode(chunk));
|
|
317
|
+
}
|
|
318
|
+
controller.close();
|
|
319
|
+
} catch (err) {
|
|
320
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
321
|
+
onError?.(error);
|
|
322
|
+
controller.error(error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
async function collectAndPrefetchData(vnode, context) {
|
|
328
|
+
const result = {};
|
|
329
|
+
if (vnode === null || vnode === void 0) {
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
if (isString(vnode) || isNumber(vnode)) {
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
if (isArray(vnode)) {
|
|
336
|
+
const results = await Promise.all(
|
|
337
|
+
vnode.map((child) => collectAndPrefetchData(child, context))
|
|
338
|
+
);
|
|
339
|
+
for (const data of results) {
|
|
340
|
+
Object.assign(result, data);
|
|
341
|
+
}
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
if (!isObject(vnode)) {
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
const node = vnode;
|
|
348
|
+
if (node.type === "text" || typeof node.type === "symbol") {
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
if (isFunction(node.type)) {
|
|
352
|
+
const component = node.type;
|
|
353
|
+
if (component.prefetch && context) {
|
|
354
|
+
try {
|
|
355
|
+
const prefetchResult = await component.prefetch(context);
|
|
356
|
+
Object.assign(result, prefetchResult.data);
|
|
357
|
+
} catch (e) {
|
|
358
|
+
console.warn("Data prefetch failed:", e);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (node.children) {
|
|
362
|
+
const childData = await collectAndPrefetchData(node.children, context);
|
|
363
|
+
Object.assign(result, childData);
|
|
364
|
+
}
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
if (isString(node.type)) {
|
|
368
|
+
if (node.children) {
|
|
369
|
+
const childData = await collectAndPrefetchData(node.children, context);
|
|
370
|
+
Object.assign(result, childData);
|
|
371
|
+
}
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
374
|
+
return result;
|
|
375
|
+
}
|
|
376
|
+
async function renderToStringAsync(vnode, prefetchData) {
|
|
377
|
+
if (vnode === null || vnode === void 0) {
|
|
378
|
+
return "";
|
|
379
|
+
}
|
|
380
|
+
if (isString(vnode)) {
|
|
381
|
+
return renderToString(vnode);
|
|
382
|
+
}
|
|
383
|
+
if (isNumber(vnode)) {
|
|
384
|
+
return String(vnode);
|
|
385
|
+
}
|
|
386
|
+
if (isArray(vnode)) {
|
|
387
|
+
const results = await Promise.all(
|
|
388
|
+
vnode.map((child) => renderToStringAsync(child))
|
|
389
|
+
);
|
|
390
|
+
return results.join("");
|
|
391
|
+
}
|
|
392
|
+
if (!isObject(vnode)) {
|
|
393
|
+
return "";
|
|
394
|
+
}
|
|
395
|
+
const node = vnode;
|
|
396
|
+
if (node.type === "text" || typeof node.type === "symbol") {
|
|
397
|
+
return renderToString(node);
|
|
398
|
+
}
|
|
399
|
+
if (isFunction(node.type)) {
|
|
400
|
+
return renderToString(node);
|
|
401
|
+
}
|
|
402
|
+
if (isString(node.type)) {
|
|
403
|
+
return renderToString(node);
|
|
404
|
+
}
|
|
405
|
+
return "";
|
|
406
|
+
}
|
|
407
|
+
async function renderToStreamEnhanced(vnode, options) {
|
|
408
|
+
const { prefetchContext, onDataPrefetched } = options || {};
|
|
409
|
+
const prefetchData = await collectAndPrefetchData(vnode, prefetchContext);
|
|
410
|
+
if (Object.keys(prefetchData).length > 0 && onDataPrefetched) {
|
|
411
|
+
onDataPrefetched(prefetchData);
|
|
412
|
+
}
|
|
413
|
+
const dehydratedState = {
|
|
414
|
+
prefetchData,
|
|
415
|
+
timestamp: Date.now()
|
|
416
|
+
};
|
|
417
|
+
const stream = renderToStreamAsync(vnode, {
|
|
418
|
+
...options,
|
|
419
|
+
onDataPrefetched: void 0
|
|
420
|
+
// 避免重复调用
|
|
421
|
+
});
|
|
422
|
+
return {
|
|
423
|
+
stream,
|
|
424
|
+
dehydratedState
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
var DEFAULT_SSG_OPTIONS = {
|
|
428
|
+
baseUrl: "/",
|
|
429
|
+
outDir: "dist",
|
|
430
|
+
defaultTitle: "LytJS App",
|
|
431
|
+
lang: "zh-CN",
|
|
432
|
+
generateSitemap: false,
|
|
433
|
+
siteName: "LytJS Site",
|
|
434
|
+
hashMode: false,
|
|
435
|
+
globalScripts: [],
|
|
436
|
+
globalStyles: []
|
|
437
|
+
};
|
|
438
|
+
function normalizePath(path) {
|
|
439
|
+
let normalized = path.startsWith("/") ? path : `/${path}`;
|
|
440
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
441
|
+
normalized = normalized.slice(0, -1);
|
|
442
|
+
}
|
|
443
|
+
return normalized;
|
|
444
|
+
}
|
|
445
|
+
function pathToFilePath(path) {
|
|
446
|
+
if (path === "/") {
|
|
447
|
+
return "/index.html";
|
|
448
|
+
}
|
|
449
|
+
return `${path}/index.html`;
|
|
450
|
+
}
|
|
451
|
+
function renderMetaTags(meta) {
|
|
452
|
+
return Object.entries(meta).map(([name, content]) => `<meta name="${name}" content="${content}">`).join("\n ");
|
|
453
|
+
}
|
|
454
|
+
function renderPage(page, options) {
|
|
455
|
+
const { component, layout, head, scripts, styles } = page;
|
|
456
|
+
const title = head?.title || options.defaultTitle;
|
|
457
|
+
let extraHead = "";
|
|
458
|
+
if (head?.meta) {
|
|
459
|
+
extraHead = renderMetaTags(head.meta);
|
|
460
|
+
}
|
|
461
|
+
const allStyles = [...options.globalStyles || [], ...styles || []];
|
|
462
|
+
const allScripts = [...options.globalScripts || [], ...scripts || []];
|
|
463
|
+
if (allStyles.length > 0) {
|
|
464
|
+
extraHead += "\n " + allStyles.join("\n ");
|
|
465
|
+
}
|
|
466
|
+
if (allScripts.length > 0) {
|
|
467
|
+
extraHead += "\n " + allScripts.join("\n ");
|
|
468
|
+
}
|
|
469
|
+
const content = layout ? renderToHtml(component, { title, lang: options.lang, head: extraHead }) : renderToHtml(component, { title, lang: options.lang, head: extraHead });
|
|
470
|
+
return content;
|
|
471
|
+
}
|
|
472
|
+
function generateSitemapXml(pages, options) {
|
|
473
|
+
const baseUrl = options.baseUrl.endsWith("/") ? options.baseUrl.slice(0, -1) : options.baseUrl;
|
|
474
|
+
const urls = pages.map((page) => {
|
|
475
|
+
const path = normalizePath(page.path);
|
|
476
|
+
const fullUrl = options.hashMode ? `${baseUrl}/#${path}` : `${baseUrl}${path}`;
|
|
477
|
+
const lastmod = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
478
|
+
return ` <url>
|
|
479
|
+
<loc>${fullUrl}</loc>
|
|
480
|
+
<lastmod>${lastmod}</lastmod>
|
|
481
|
+
<changefreq>weekly</changefreq>
|
|
482
|
+
<priority>0.8</priority>
|
|
483
|
+
</url>`;
|
|
484
|
+
}).join("\n");
|
|
485
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
486
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
487
|
+
${urls}
|
|
488
|
+
</urlset>`;
|
|
489
|
+
}
|
|
490
|
+
async function writeStaticFiles(pages, options) {
|
|
491
|
+
const resolvedOptions = { ...DEFAULT_SSG_OPTIONS, ...options };
|
|
492
|
+
const { outDir, generateSitemap } = resolvedOptions;
|
|
493
|
+
const errors = validatePages(pages);
|
|
494
|
+
if (errors.length > 0) {
|
|
495
|
+
throw new Error(`\u9875\u9762\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25:
|
|
496
|
+
${errors.join("\n")}`);
|
|
497
|
+
}
|
|
498
|
+
const staticPages = generateStaticPages(pages, resolvedOptions);
|
|
499
|
+
for (const [filePath, html] of staticPages) {
|
|
500
|
+
const fullPath = join(outDir, filePath);
|
|
501
|
+
const dir = dirname(fullPath);
|
|
502
|
+
await promises.mkdir(dir, { recursive: true });
|
|
503
|
+
await promises.writeFile(fullPath, html, "utf-8");
|
|
504
|
+
}
|
|
505
|
+
if (generateSitemap) {
|
|
506
|
+
const sitemapContent = generateSitemapXml(pages, resolvedOptions);
|
|
507
|
+
const sitemapPath = join(outDir, "sitemap.xml");
|
|
508
|
+
await promises.writeFile(sitemapPath, sitemapContent, "utf-8");
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function generateStaticPages(pages, options) {
|
|
512
|
+
const resolvedOptions = { ...DEFAULT_SSG_OPTIONS, ...options };
|
|
513
|
+
const results = /* @__PURE__ */ new Map();
|
|
514
|
+
for (const page of pages) {
|
|
515
|
+
const normalizedPath = normalizePath(page.path);
|
|
516
|
+
const filePath = pathToFilePath(normalizedPath);
|
|
517
|
+
const html = renderPage(page, resolvedOptions);
|
|
518
|
+
results.set(filePath, html);
|
|
519
|
+
}
|
|
520
|
+
return results;
|
|
521
|
+
}
|
|
522
|
+
function generateRouteManifest(pages, baseUrl = "/") {
|
|
523
|
+
return pages.map((page) => {
|
|
524
|
+
const normalizedPath = normalizePath(page.path);
|
|
525
|
+
const filePath = pathToFilePath(normalizedPath);
|
|
526
|
+
return {
|
|
527
|
+
path: `${baseUrl}${normalizedPath}`,
|
|
528
|
+
filePath,
|
|
529
|
+
title: page.head?.title
|
|
530
|
+
};
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
function validatePages(pages) {
|
|
534
|
+
const errors = [];
|
|
535
|
+
for (let i = 0; i < pages.length; i++) {
|
|
536
|
+
const page = pages[i];
|
|
537
|
+
if (!page) {
|
|
538
|
+
errors.push(`\u9875\u9762 ${i}: \u65E0\u6548\u7684\u9875\u9762\u914D\u7F6E`);
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
const pagePath = page.path;
|
|
542
|
+
if (!pagePath || !isString(pagePath)) {
|
|
543
|
+
errors.push(`\u9875\u9762 ${i}: path \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (!page.component) {
|
|
547
|
+
errors.push(`\u9875\u9762 "${pagePath}": component \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
548
|
+
}
|
|
549
|
+
if (isString(pagePath) && !pagePath.startsWith("/")) {
|
|
550
|
+
errors.push(`\u9875\u9762 "${pagePath}": path \u5FC5\u987B\u4EE5 / \u5F00\u5934`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return errors;
|
|
554
|
+
}
|
|
555
|
+
var HYDRATE_ATTR = "data-hydrate";
|
|
556
|
+
var HYDRATE_STRATEGY_ATTR = "data-hydrate-strategy";
|
|
557
|
+
var DEHYDRATED_STATE_ID = "__LYT_DEHYDRATED_STATE__";
|
|
558
|
+
var componentIdCounter = 0;
|
|
559
|
+
function generateComponentId() {
|
|
560
|
+
componentIdCounter++;
|
|
561
|
+
return `lyt-hydrate-${componentIdCounter}`;
|
|
562
|
+
}
|
|
563
|
+
function resetComponentIdCounter() {
|
|
564
|
+
componentIdCounter = 0;
|
|
565
|
+
}
|
|
566
|
+
function isElementVNode(vnode) {
|
|
567
|
+
return isObject(vnode) && isString(vnode.type);
|
|
568
|
+
}
|
|
569
|
+
function isComponentVNode(vnode) {
|
|
570
|
+
return isObject(vnode) && isFunction(vnode.type);
|
|
571
|
+
}
|
|
572
|
+
function getVNodeProps(vnode) {
|
|
573
|
+
if (isObject(vnode) && isObject(vnode.props)) {
|
|
574
|
+
return vnode.props;
|
|
575
|
+
}
|
|
576
|
+
return {};
|
|
577
|
+
}
|
|
578
|
+
function getVNodeChildren(vnode) {
|
|
579
|
+
if (isObject(vnode)) {
|
|
580
|
+
return vnode.children;
|
|
581
|
+
}
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
function createHydrationMarkers(vnode) {
|
|
585
|
+
if (!isObject(vnode)) {
|
|
586
|
+
return vnode;
|
|
587
|
+
}
|
|
588
|
+
const node = vnode;
|
|
589
|
+
if (isArray(vnode)) {
|
|
590
|
+
return vnode.map(
|
|
591
|
+
(child) => createHydrationMarkers(child)
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
if (isElementVNode(node)) {
|
|
595
|
+
const id = generateComponentId();
|
|
596
|
+
const props = { ...getVNodeProps(node) };
|
|
597
|
+
props[HYDRATE_ATTR] = id;
|
|
598
|
+
const strategy = props["hydrateStrategy"];
|
|
599
|
+
if (strategy && isValidStrategy(strategy)) {
|
|
600
|
+
props[HYDRATE_STRATEGY_ATTR] = strategy;
|
|
601
|
+
}
|
|
602
|
+
const children = getVNodeChildren(node);
|
|
603
|
+
let processedChildren = children;
|
|
604
|
+
if (children !== null && !isString(children) && !isNumber(children)) {
|
|
605
|
+
if (isArray(children)) {
|
|
606
|
+
processedChildren = children.map(
|
|
607
|
+
(child) => createHydrationMarkers(child)
|
|
608
|
+
);
|
|
609
|
+
} else if (isObject(children)) {
|
|
610
|
+
processedChildren = [createHydrationMarkers(children)];
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return { ...node, props, children: processedChildren };
|
|
614
|
+
}
|
|
615
|
+
if (isComponentVNode(node)) {
|
|
616
|
+
const children = getVNodeChildren(node);
|
|
617
|
+
if (children !== null && !isString(children) && !isNumber(children)) {
|
|
618
|
+
if (isArray(children)) {
|
|
619
|
+
return {
|
|
620
|
+
...node,
|
|
621
|
+
children: children.map(
|
|
622
|
+
(child) => createHydrationMarkers(child)
|
|
623
|
+
)
|
|
624
|
+
};
|
|
625
|
+
} else if (isObject(children)) {
|
|
626
|
+
return {
|
|
627
|
+
...node,
|
|
628
|
+
children: [createHydrationMarkers(children)]
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return node;
|
|
633
|
+
}
|
|
634
|
+
return node;
|
|
635
|
+
}
|
|
636
|
+
function isValidStrategy(strategy) {
|
|
637
|
+
return strategy === "lazy" || strategy === "eager" || strategy === "idle";
|
|
638
|
+
}
|
|
639
|
+
function getHydrationStrategy(vnode) {
|
|
640
|
+
if (!isObject(vnode)) {
|
|
641
|
+
return "eager";
|
|
642
|
+
}
|
|
643
|
+
const props = getVNodeProps(vnode);
|
|
644
|
+
const strategy = props["hydrateStrategy"];
|
|
645
|
+
if (isString(strategy) && isValidStrategy(strategy)) {
|
|
646
|
+
return strategy;
|
|
647
|
+
}
|
|
648
|
+
return "eager";
|
|
649
|
+
}
|
|
650
|
+
function collectHydrationHints(vnode, hints = []) {
|
|
651
|
+
if (!isObject(vnode)) {
|
|
652
|
+
return hints;
|
|
653
|
+
}
|
|
654
|
+
const node = vnode;
|
|
655
|
+
if (isArray(vnode)) {
|
|
656
|
+
for (const child of vnode) {
|
|
657
|
+
collectHydrationHints(child, hints);
|
|
658
|
+
}
|
|
659
|
+
return hints;
|
|
660
|
+
}
|
|
661
|
+
if (isElementVNode(node)) {
|
|
662
|
+
const props = getVNodeProps(node);
|
|
663
|
+
const hydrateId = props[HYDRATE_ATTR];
|
|
664
|
+
if (isString(hydrateId)) {
|
|
665
|
+
const cleanProps = {};
|
|
666
|
+
for (const [key, value] of Object.entries(props)) {
|
|
667
|
+
if (!key.startsWith("data-") && key !== "key" && key !== "ref") {
|
|
668
|
+
cleanProps[key] = value;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
hints.push({
|
|
672
|
+
componentId: hydrateId,
|
|
673
|
+
strategy: getHydrationStrategy(node),
|
|
674
|
+
props: cleanProps
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
const children = getVNodeChildren(node);
|
|
678
|
+
if (children !== null && !isString(children) && !isNumber(children)) {
|
|
679
|
+
if (isArray(children)) {
|
|
680
|
+
for (const child of children) {
|
|
681
|
+
collectHydrationHints(child, hints);
|
|
682
|
+
}
|
|
683
|
+
} else if (isObject(children)) {
|
|
684
|
+
collectHydrationHints(children, hints);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return hints;
|
|
689
|
+
}
|
|
690
|
+
function serializeHydrationState(state) {
|
|
691
|
+
if (isNullish(state)) {
|
|
692
|
+
return "{}";
|
|
693
|
+
}
|
|
694
|
+
return JSON.stringify(state, (_, value) => {
|
|
695
|
+
if (typeof value === "undefined") {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
if (typeof value === "function") {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
if (typeof value === "symbol") {
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
return value;
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
function createDehydratedState(vnode, initialState) {
|
|
708
|
+
resetComponentIdCounter();
|
|
709
|
+
const markedVNode = createHydrationMarkers(vnode);
|
|
710
|
+
const hints = collectHydrationHints(markedVNode);
|
|
711
|
+
const hydrationState = {
|
|
712
|
+
hints,
|
|
713
|
+
initialState
|
|
714
|
+
};
|
|
715
|
+
const json = serializeHydrationState(hydrationState);
|
|
716
|
+
const safeJson = json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\/script/gi, "\\/script");
|
|
717
|
+
return `<script id="${DEHYDRATED_STATE_ID}" type="application/json">${safeJson}</script>`;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export { VirtualList, createDehydratedState, createHydrationMarkers, render_default as default, generateRouteManifest, generateStaticPages, getHydrationStrategy, renderToHtml, renderToStream, renderToStreamAsync, renderToStreamEnhanced, renderToString, resetComponentIdCounter, serializeHydrationState, validatePages, writeStaticFiles };
|
|
721
|
+
//# sourceMappingURL=index.mjs.map
|
|
722
|
+
//# sourceMappingURL=index.mjs.map
|