@bitstillery/mithril 3.0.0-AA
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/LICENSE +21 -0
- package/README.md +185 -0
- package/api/mount-redraw.ts +173 -0
- package/api/router-server.ts +88 -0
- package/api/router.ts +670 -0
- package/browser.ts +10 -0
- package/htm.ts +7 -0
- package/hyperscript.ts +8 -0
- package/index.ts +134 -0
- package/jsx.d.ts +16 -0
- package/mount.ts +10 -0
- package/package.json +66 -0
- package/pathname/build.ts +40 -0
- package/pathname/compileTemplate.ts +49 -0
- package/pathname/parse.ts +19 -0
- package/querystring/build.ts +22 -0
- package/querystring/parse.ts +50 -0
- package/redraw.ts +10 -0
- package/render/cachedAttrsIsStaticMap.ts +10 -0
- package/render/delayedRemoval.ts +1 -0
- package/render/domFor.ts +23 -0
- package/render/emptyAttrs.ts +2 -0
- package/render/fragment.ts +11 -0
- package/render/hyperscript.ts +125 -0
- package/render/hyperscriptVnode.ts +21 -0
- package/render/render.ts +1307 -0
- package/render/renderToString.ts +494 -0
- package/render/ssrState.ts +366 -0
- package/render/trust.ts +6 -0
- package/render/vnode.ts +131 -0
- package/route.ts +11 -0
- package/server/logger.ts +223 -0
- package/server/session.ts +87 -0
- package/server/ssr.ts +205 -0
- package/server/ssrLogger.ts +13 -0
- package/server.ts +101 -0
- package/signal.ts +320 -0
- package/ssrContext.ts +87 -0
- package/state.ts +1026 -0
- package/store.ts +457 -0
- package/util/censor.ts +46 -0
- package/util/decodeURIComponentSafe.ts +34 -0
- package/util/hasOwn.ts +2 -0
- package/util/next_tick.ts +45 -0
- package/util/ssr.ts +385 -0
- package/util/uri.ts +126 -0
- package/version.ts +3 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import {setCurrentComponent, clearCurrentComponent} from '../signal'
|
|
2
|
+
|
|
3
|
+
import {serializeAllStates} from './ssrState'
|
|
4
|
+
import Vnode from './vnode'
|
|
5
|
+
|
|
6
|
+
import type {Vnode as VnodeType, Children} from '../index'
|
|
7
|
+
|
|
8
|
+
// Void elements that don't have closing tags
|
|
9
|
+
const VOID_ELEMENTS = new Set([
|
|
10
|
+
'area',
|
|
11
|
+
'base',
|
|
12
|
+
'br',
|
|
13
|
+
'col',
|
|
14
|
+
'embed',
|
|
15
|
+
'hr',
|
|
16
|
+
'img',
|
|
17
|
+
'input',
|
|
18
|
+
'link',
|
|
19
|
+
'meta',
|
|
20
|
+
'param',
|
|
21
|
+
'source',
|
|
22
|
+
'track',
|
|
23
|
+
'wbr',
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
export interface RenderToStringOptions {
|
|
27
|
+
escapeAttribute?: (value: any) => string
|
|
28
|
+
escapeText?: (value: any) => string
|
|
29
|
+
strict?: boolean // Close all empty tags
|
|
30
|
+
xml?: boolean // XML mode (implies strict)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Default escape functions
|
|
34
|
+
function escapeAttributeDefault(value: any): string {
|
|
35
|
+
const str = String(value)
|
|
36
|
+
return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function escapeTextDefault(value: any): string {
|
|
40
|
+
const str = String(value)
|
|
41
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isVoidElement(tag: string): boolean {
|
|
45
|
+
return VOID_ELEMENTS.has(tag.toLowerCase())
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Promise tracker for async data fetching
|
|
49
|
+
class PromiseTracker {
|
|
50
|
+
private promises: Promise<any>[] = []
|
|
51
|
+
|
|
52
|
+
waitFor(promise: Promise<any>) {
|
|
53
|
+
this.promises.push(promise)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async waitAll(): Promise<void> {
|
|
57
|
+
if (this.promises.length > 0) {
|
|
58
|
+
await Promise.all(this.promises)
|
|
59
|
+
this.promises = []
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
hasPromises(): boolean {
|
|
64
|
+
return this.promises.length > 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
reset() {
|
|
68
|
+
this.promises = []
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Serialize attributes to HTML string
|
|
73
|
+
function serializeAttributes(attrs: Record<string, any> | null | undefined, options: Required<RenderToStringOptions>): string {
|
|
74
|
+
if (!attrs) return ''
|
|
75
|
+
|
|
76
|
+
const parts: string[] = []
|
|
77
|
+
|
|
78
|
+
for (const key in attrs) {
|
|
79
|
+
const value = attrs[key]
|
|
80
|
+
|
|
81
|
+
// Skip lifecycle hooks and special attributes
|
|
82
|
+
if (
|
|
83
|
+
key === 'key' ||
|
|
84
|
+
key === 'oninit' ||
|
|
85
|
+
key === 'oncreate' ||
|
|
86
|
+
key === 'onupdate' ||
|
|
87
|
+
key === 'onremove' ||
|
|
88
|
+
key === 'onbeforeremove' ||
|
|
89
|
+
key === 'onbeforeupdate' ||
|
|
90
|
+
(key.startsWith('on') && typeof value === 'function')
|
|
91
|
+
) {
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (value == null) continue
|
|
96
|
+
|
|
97
|
+
// Handle className -> class
|
|
98
|
+
const attrName = key === 'className' ? 'class' : key
|
|
99
|
+
|
|
100
|
+
if (typeof value === 'boolean') {
|
|
101
|
+
if (value) {
|
|
102
|
+
parts.push(attrName)
|
|
103
|
+
}
|
|
104
|
+
} else if (typeof value === 'object') {
|
|
105
|
+
// Handle style objects
|
|
106
|
+
if (key === 'style') {
|
|
107
|
+
const styleStr = Object.entries(value)
|
|
108
|
+
.map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`)
|
|
109
|
+
.join('; ')
|
|
110
|
+
parts.push(`${attrName}="${options.escapeAttribute(styleStr)}"`)
|
|
111
|
+
} else {
|
|
112
|
+
// For other objects, stringify
|
|
113
|
+
parts.push(`${attrName}="${options.escapeAttribute(JSON.stringify(value))}"`)
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
parts.push(`${attrName}="${options.escapeAttribute(value)}"`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return parts.length > 0 ? ' ' + parts.join(' ') : ''
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Serialize text node
|
|
124
|
+
function serializeText(text: string | number, options: Required<RenderToStringOptions>): string {
|
|
125
|
+
return options.escapeText(text)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Serialize component (sync version - no async handling)
|
|
129
|
+
function serializeComponentSync(
|
|
130
|
+
vnode: VnodeType,
|
|
131
|
+
options: Required<RenderToStringOptions>,
|
|
132
|
+
promiseTracker: PromiseTracker,
|
|
133
|
+
): string {
|
|
134
|
+
const component = vnode.tag as any
|
|
135
|
+
|
|
136
|
+
// Initialize component state
|
|
137
|
+
let state: any
|
|
138
|
+
let view: ((vnode: any) => any) | undefined
|
|
139
|
+
|
|
140
|
+
if (typeof component.view === 'function') {
|
|
141
|
+
// Component object
|
|
142
|
+
state = Object.create(component)
|
|
143
|
+
view = state.view
|
|
144
|
+
} else {
|
|
145
|
+
// Component factory/class
|
|
146
|
+
if (component.prototype && typeof component.prototype.view === 'function') {
|
|
147
|
+
state = new component(vnode)
|
|
148
|
+
} else {
|
|
149
|
+
state = component(vnode)
|
|
150
|
+
}
|
|
151
|
+
view = state.view
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!view) {
|
|
155
|
+
return ''
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
vnode.state = state
|
|
159
|
+
|
|
160
|
+
// Call oninit with context (sync mode - don't await)
|
|
161
|
+
if (typeof state.oninit === 'function') {
|
|
162
|
+
try {
|
|
163
|
+
const context = {
|
|
164
|
+
isSSR: true,
|
|
165
|
+
isHydrating: false,
|
|
166
|
+
}
|
|
167
|
+
// Call oninit but don't wait for it (sync mode)
|
|
168
|
+
state.oninit(vnode, context)
|
|
169
|
+
} catch (_e) {
|
|
170
|
+
// Ignore errors
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Call view (bind this to state)
|
|
175
|
+
const instance = Vnode.normalize(view.call(state, vnode))
|
|
176
|
+
if (instance === vnode) {
|
|
177
|
+
throw Error('A view cannot return the vnode it received as argument')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
vnode.instance = instance
|
|
181
|
+
|
|
182
|
+
// Serialize the instance
|
|
183
|
+
if (instance != null) {
|
|
184
|
+
return serializeNodeSync(instance, options, promiseTracker)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return ''
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Serialize a single vnode to HTML string (sync version)
|
|
191
|
+
function serializeNodeSync(
|
|
192
|
+
vnode: VnodeType | null,
|
|
193
|
+
options: Required<RenderToStringOptions>,
|
|
194
|
+
promiseTracker: PromiseTracker,
|
|
195
|
+
): string {
|
|
196
|
+
if (vnode == null) return ''
|
|
197
|
+
|
|
198
|
+
const tag = vnode.tag
|
|
199
|
+
|
|
200
|
+
// Text node
|
|
201
|
+
if (tag === '#') {
|
|
202
|
+
return serializeText(vnode.children as string | number, options)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// HTML/trust node
|
|
206
|
+
if (tag === '<') {
|
|
207
|
+
return vnode.children as string
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Fragment
|
|
211
|
+
if (tag === '[') {
|
|
212
|
+
const children = vnode.children as (VnodeType | null)[]
|
|
213
|
+
if (!children) return ''
|
|
214
|
+
return children.map((child) => serializeNodeSync(child, options, promiseTracker)).join('')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Component
|
|
218
|
+
if (typeof tag !== 'string') {
|
|
219
|
+
return serializeComponentSync(vnode, options, promiseTracker)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Element
|
|
223
|
+
const attrs = serializeAttributes(vnode.attrs, options)
|
|
224
|
+
const children = vnode.children as Children
|
|
225
|
+
|
|
226
|
+
let html = `<${tag}${attrs}`
|
|
227
|
+
|
|
228
|
+
const isVoid = isVoidElement(tag)
|
|
229
|
+
const shouldSelfClose = (options.strict || options.xml) && isVoid
|
|
230
|
+
|
|
231
|
+
if (shouldSelfClose) {
|
|
232
|
+
html += options.xml ? ' />' : '>'
|
|
233
|
+
return html
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
html += '>'
|
|
237
|
+
|
|
238
|
+
// Serialize children
|
|
239
|
+
if (children != null) {
|
|
240
|
+
if (Array.isArray(children)) {
|
|
241
|
+
html += children.map((child) => serializeNodeSync(child as VnodeType | null, options, promiseTracker)).join('')
|
|
242
|
+
} else if (typeof children === 'string' || typeof children === 'number') {
|
|
243
|
+
html += serializeText(children, options)
|
|
244
|
+
} else if (children != null) {
|
|
245
|
+
html += serializeNodeSync(children as unknown as VnodeType, options, promiseTracker)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Always close non-void elements
|
|
250
|
+
if (!isVoid) {
|
|
251
|
+
html += `</${tag}>`
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return html
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Serialize a single vnode to HTML string
|
|
258
|
+
async function serializeNode(
|
|
259
|
+
vnode: VnodeType | null,
|
|
260
|
+
options: Required<RenderToStringOptions>,
|
|
261
|
+
promiseTracker: PromiseTracker,
|
|
262
|
+
isServer: boolean,
|
|
263
|
+
): Promise<string> {
|
|
264
|
+
if (vnode == null) return ''
|
|
265
|
+
|
|
266
|
+
const tag = vnode.tag
|
|
267
|
+
|
|
268
|
+
// Text node
|
|
269
|
+
if (tag === '#') {
|
|
270
|
+
return serializeText(vnode.children as string | number, options)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// HTML/trust node
|
|
274
|
+
if (tag === '<') {
|
|
275
|
+
return vnode.children as string
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Fragment
|
|
279
|
+
if (tag === '[') {
|
|
280
|
+
const children = vnode.children as (VnodeType | null)[]
|
|
281
|
+
if (!children) return ''
|
|
282
|
+
const results = await Promise.all(children.map((child) => serializeNode(child, options, promiseTracker, isServer)))
|
|
283
|
+
return results.join('')
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Component
|
|
287
|
+
if (typeof tag !== 'string') {
|
|
288
|
+
return await serializeComponent(vnode, options, promiseTracker, isServer)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Element
|
|
292
|
+
const attrs = serializeAttributes(vnode.attrs, options)
|
|
293
|
+
const children = vnode.children as Children
|
|
294
|
+
|
|
295
|
+
let html = `<${tag}${attrs}`
|
|
296
|
+
|
|
297
|
+
const isVoid = isVoidElement(tag)
|
|
298
|
+
const shouldSelfClose = (options.strict || options.xml) && isVoid
|
|
299
|
+
|
|
300
|
+
if (shouldSelfClose) {
|
|
301
|
+
html += options.xml ? ' />' : '>'
|
|
302
|
+
return html
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
html += '>'
|
|
306
|
+
|
|
307
|
+
// Serialize children
|
|
308
|
+
if (children != null) {
|
|
309
|
+
if (Array.isArray(children)) {
|
|
310
|
+
const results = await Promise.all(
|
|
311
|
+
children.map((child) => serializeNode(child as VnodeType | null, options, promiseTracker, isServer)),
|
|
312
|
+
)
|
|
313
|
+
html += results.join('')
|
|
314
|
+
} else if (typeof children === 'string' || typeof children === 'number') {
|
|
315
|
+
html += serializeText(children, options)
|
|
316
|
+
} else if (children != null) {
|
|
317
|
+
html += await serializeNode(children as unknown as VnodeType, options, promiseTracker, isServer)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Always close non-void elements
|
|
322
|
+
if (!isVoid) {
|
|
323
|
+
html += `</${tag}>`
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return html
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Serialize component
|
|
330
|
+
async function serializeComponent(
|
|
331
|
+
vnode: VnodeType,
|
|
332
|
+
options: Required<RenderToStringOptions>,
|
|
333
|
+
promiseTracker: PromiseTracker,
|
|
334
|
+
isServer: boolean,
|
|
335
|
+
): Promise<string> {
|
|
336
|
+
const component = vnode.tag as any
|
|
337
|
+
|
|
338
|
+
// Initialize component state
|
|
339
|
+
let state: any
|
|
340
|
+
let view: ((vnode: any) => any) | undefined
|
|
341
|
+
|
|
342
|
+
if (typeof component.view === 'function') {
|
|
343
|
+
// Component object
|
|
344
|
+
state = Object.create(component)
|
|
345
|
+
view = state.view
|
|
346
|
+
} else {
|
|
347
|
+
// Component factory/class
|
|
348
|
+
if (component.prototype && typeof component.prototype.view === 'function') {
|
|
349
|
+
state = new component(vnode)
|
|
350
|
+
} else {
|
|
351
|
+
state = component(vnode)
|
|
352
|
+
}
|
|
353
|
+
view = state.view
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!view) {
|
|
357
|
+
return ''
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
vnode.state = state
|
|
361
|
+
|
|
362
|
+
// Set current component for signal tracking (needed for store access during SSR)
|
|
363
|
+
// This ensures store properties can track component dependencies correctly
|
|
364
|
+
setCurrentComponent(state)
|
|
365
|
+
|
|
366
|
+
let instance: any
|
|
367
|
+
try {
|
|
368
|
+
// Call oninit with context if on server
|
|
369
|
+
if (isServer && typeof state.oninit === 'function') {
|
|
370
|
+
const context = {
|
|
371
|
+
isSSR: true,
|
|
372
|
+
isHydrating: false,
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
const result = state.oninit(vnode, context)
|
|
376
|
+
// If oninit returns a promise, await it
|
|
377
|
+
if (result && typeof result.then === 'function') {
|
|
378
|
+
await result
|
|
379
|
+
}
|
|
380
|
+
} catch (_e) {
|
|
381
|
+
// Ignore errors in oninit for now
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Call view (bind this to state)
|
|
386
|
+
instance = Vnode.normalize(view.call(state, vnode))
|
|
387
|
+
if (instance === vnode) {
|
|
388
|
+
throw Error('A view cannot return the vnode it received as argument')
|
|
389
|
+
}
|
|
390
|
+
} finally {
|
|
391
|
+
// Clear current component after rendering
|
|
392
|
+
clearCurrentComponent()
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
vnode.instance = instance
|
|
396
|
+
|
|
397
|
+
// Serialize the instance
|
|
398
|
+
if (instance != null) {
|
|
399
|
+
return serializeNode(instance, options, promiseTracker, isServer)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return ''
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function renderToStringFactory() {
|
|
406
|
+
const defaultOptions: Required<RenderToStringOptions> = {
|
|
407
|
+
escapeAttribute: escapeAttributeDefault,
|
|
408
|
+
escapeText: escapeTextDefault,
|
|
409
|
+
strict: false,
|
|
410
|
+
xml: false,
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Async version (waits for promises)
|
|
414
|
+
async function renderToString(
|
|
415
|
+
vnodes: Children | VnodeType | null,
|
|
416
|
+
options?: RenderToStringOptions,
|
|
417
|
+
): Promise<{html: string; state: Record<string, any>}> {
|
|
418
|
+
const opts: Required<RenderToStringOptions> = {
|
|
419
|
+
...defaultOptions,
|
|
420
|
+
...options,
|
|
421
|
+
escapeAttribute: options?.escapeAttribute || defaultOptions.escapeAttribute,
|
|
422
|
+
escapeText: options?.escapeText || defaultOptions.escapeText,
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Normalize vnodes
|
|
426
|
+
const normalized = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes])
|
|
427
|
+
|
|
428
|
+
const promiseTracker = new PromiseTracker()
|
|
429
|
+
|
|
430
|
+
// First pass: render and collect promises
|
|
431
|
+
let html = ''
|
|
432
|
+
const htmlParts: Promise<string>[] = []
|
|
433
|
+
for (const vnode of normalized) {
|
|
434
|
+
if (vnode != null) {
|
|
435
|
+
htmlParts.push(serializeNode(vnode, opts, promiseTracker, true))
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
html = (await Promise.all(htmlParts)).join('')
|
|
439
|
+
|
|
440
|
+
// Wait for all promises
|
|
441
|
+
if (promiseTracker.hasPromises()) {
|
|
442
|
+
await promiseTracker.waitAll()
|
|
443
|
+
|
|
444
|
+
// Second pass: re-render after promises resolve
|
|
445
|
+
promiseTracker.reset()
|
|
446
|
+
html = ''
|
|
447
|
+
const htmlParts2: Promise<string>[] = []
|
|
448
|
+
for (const vnode of normalized) {
|
|
449
|
+
if (vnode != null) {
|
|
450
|
+
htmlParts2.push(serializeNode(vnode, opts, promiseTracker, true))
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
html = (await Promise.all(htmlParts2)).join('')
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Serialize all registered stores
|
|
457
|
+
const state = serializeAllStates()
|
|
458
|
+
|
|
459
|
+
return {html, state}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Sync version (no promise waiting)
|
|
463
|
+
function renderToStringSync(vnodes: Children | VnodeType | null, options?: RenderToStringOptions): string {
|
|
464
|
+
const opts: Required<RenderToStringOptions> = {
|
|
465
|
+
...defaultOptions,
|
|
466
|
+
...options,
|
|
467
|
+
escapeAttribute: options?.escapeAttribute || defaultOptions.escapeAttribute,
|
|
468
|
+
escapeText: options?.escapeText || defaultOptions.escapeText,
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const normalized = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes])
|
|
472
|
+
|
|
473
|
+
const promiseTracker = new PromiseTracker()
|
|
474
|
+
|
|
475
|
+
// For sync version, we need to handle async functions synchronously
|
|
476
|
+
// This means we won't wait for promises, but we'll still render what we can
|
|
477
|
+
let html = ''
|
|
478
|
+
for (const vnode of normalized) {
|
|
479
|
+
if (vnode != null) {
|
|
480
|
+
// In sync mode, we can't await, so we'll render synchronously
|
|
481
|
+
// Components with async oninit won't have their data, but that's expected
|
|
482
|
+
const result = serializeNodeSync(vnode, opts, promiseTracker)
|
|
483
|
+
html += result
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return html
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
renderToString,
|
|
492
|
+
renderToStringSync,
|
|
493
|
+
}
|
|
494
|
+
}
|