preact-render-to-string 6.3.1 → 6.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -60,8 +60,74 @@ export function renderToString(vnode, context) {
60
60
  context || EMPTY_OBJ,
61
61
  false,
62
62
  undefined,
63
- parent
63
+ parent,
64
+ false
64
65
  );
66
+ } catch (e) {
67
+ if (e.then) {
68
+ throw new Error('Use "renderToStringAsync" for suspenseful rendering.');
69
+ }
70
+
71
+ throw e;
72
+ } finally {
73
+ // options._commit, we don't schedule any effects in this library right now,
74
+ // so we can pass an empty queue to this hook.
75
+ if (options[COMMIT]) options[COMMIT](vnode, EMPTY_ARR);
76
+ options[SKIP_EFFECTS] = previousSkipEffects;
77
+ EMPTY_ARR.length = 0;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Render Preact JSX + Components to an HTML string.
83
+ * @param {VNode} vnode JSX Element / VNode to render
84
+ * @param {Object} [context={}] Initial root context object
85
+ * @returns {string} serialized HTML
86
+ */
87
+ export async function renderToStringAsync(vnode, context) {
88
+ // Performance optimization: `renderToString` is synchronous and we
89
+ // therefore don't execute any effects. To do that we pass an empty
90
+ // array to `options._commit` (`__c`). But we can go one step further
91
+ // and avoid a lot of dirty checks and allocations by setting
92
+ // `options._skipEffects` (`__s`) too.
93
+ const previousSkipEffects = options[SKIP_EFFECTS];
94
+ options[SKIP_EFFECTS] = true;
95
+
96
+ // store options hooks once before each synchronous render call
97
+ beforeDiff = options[DIFF];
98
+ afterDiff = options[DIFFED];
99
+ renderHook = options[RENDER];
100
+ ummountHook = options.unmount;
101
+
102
+ const parent = h(Fragment, null);
103
+ parent[CHILDREN] = [vnode];
104
+
105
+ try {
106
+ const rendered = _renderToString(
107
+ vnode,
108
+ context || EMPTY_OBJ,
109
+ false,
110
+ undefined,
111
+ parent,
112
+ true
113
+ );
114
+
115
+ if (Array.isArray(rendered)) {
116
+ let count = 0;
117
+ let resolved = rendered;
118
+
119
+ // Resolving nested Promises with a maximum depth of 25
120
+ while (
121
+ resolved.some((element) => typeof element.then === 'function') &&
122
+ count++ < 25
123
+ ) {
124
+ resolved = (await Promise.all(resolved)).flat();
125
+ }
126
+
127
+ return resolved.join('');
128
+ }
129
+
130
+ return rendered;
65
131
  } finally {
66
132
  // options._commit, we don't schedule any effects in this library right now,
67
133
  // so we can pass an empty queue to this hook.
@@ -137,9 +203,17 @@ function renderClassComponent(vnode, context) {
137
203
  * @param {boolean} isSvgMode
138
204
  * @param {any} selectValue
139
205
  * @param {VNode} parent
140
- * @returns {string}
206
+ * @param {boolean} asyncMode
207
+ * @returns {string | Promise<string> | (string | Promise<string>)[]}
141
208
  */
142
- function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
209
+ function _renderToString(
210
+ vnode,
211
+ context,
212
+ isSvgMode,
213
+ selectValue,
214
+ parent,
215
+ asyncMode
216
+ ) {
143
217
  // Ignore non-rendered VNodes/values
144
218
  if (vnode == null || vnode === true || vnode === false || vnode === '') {
145
219
  return '';
@@ -153,16 +227,44 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
153
227
 
154
228
  // Recurse into children / Arrays
155
229
  if (isArray(vnode)) {
156
- let rendered = '';
230
+ let rendered = '',
231
+ renderArray;
157
232
  parent[CHILDREN] = vnode;
158
233
  for (let i = 0; i < vnode.length; i++) {
159
234
  let child = vnode[i];
160
235
  if (child == null || typeof child === 'boolean') continue;
161
236
 
162
- rendered =
163
- rendered +
164
- _renderToString(child, context, isSvgMode, selectValue, parent);
237
+ const childRender = _renderToString(
238
+ child,
239
+ context,
240
+ isSvgMode,
241
+ selectValue,
242
+ parent,
243
+ asyncMode
244
+ );
245
+
246
+ if (typeof childRender === 'string') {
247
+ rendered += childRender;
248
+ } else {
249
+ renderArray = renderArray || [];
250
+
251
+ if (rendered) renderArray.push(rendered);
252
+
253
+ rendered = '';
254
+
255
+ if (Array.isArray(childRender)) {
256
+ renderArray.push(...childRender);
257
+ } else {
258
+ renderArray.push(childRender);
259
+ }
260
+ }
261
+ }
262
+
263
+ if (renderArray) {
264
+ if (rendered) renderArray.push(rendered);
265
+ return renderArray;
165
266
  }
267
+
166
268
  return rendered;
167
269
  }
168
270
 
@@ -202,7 +304,8 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
202
304
  context,
203
305
  isSvgMode,
204
306
  selectValue,
205
- vnode
307
+ vnode,
308
+ asyncMode
206
309
  );
207
310
  } else {
208
311
  // Values are pre-escaped by the JSX transform
@@ -282,7 +385,8 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
282
385
  context,
283
386
  isSvgMode,
284
387
  selectValue,
285
- vnode
388
+ vnode,
389
+ asyncMode
286
390
  );
287
391
  return str;
288
392
  } catch (err) {
@@ -313,14 +417,15 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
313
417
  context,
314
418
  isSvgMode,
315
419
  selectValue,
316
- vnode
420
+ vnode,
421
+ asyncMode
317
422
  );
318
423
  }
319
424
 
320
425
  return str;
321
426
  } finally {
322
427
  if (afterDiff) afterDiff(vnode);
323
- vnode[PARENT] = undefined;
428
+ vnode[PARENT] = null;
324
429
 
325
430
  if (ummountHook) ummountHook(vnode);
326
431
  }
@@ -333,20 +438,46 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
333
438
  rendered != null && rendered.type === Fragment && rendered.key == null;
334
439
  rendered = isTopLevelFragment ? rendered.props.children : rendered;
335
440
 
336
- // Recurse into children before invoking the after-diff hook
337
- const str = _renderToString(
338
- rendered,
339
- context,
340
- isSvgMode,
341
- selectValue,
342
- vnode
343
- );
344
- if (afterDiff) afterDiff(vnode);
345
- vnode[PARENT] = undefined;
441
+ const renderChildren = () =>
442
+ _renderToString(
443
+ rendered,
444
+ context,
445
+ isSvgMode,
446
+ selectValue,
447
+ vnode,
448
+ asyncMode
449
+ );
450
+
451
+ try {
452
+ // Recurse into children before invoking the after-diff hook
453
+ const str = renderChildren();
454
+
455
+ if (afterDiff) afterDiff(vnode);
456
+ vnode[PARENT] = null;
346
457
 
347
- if (ummountHook) ummountHook(vnode);
458
+ if (ummountHook) ummountHook(vnode);
348
459
 
349
- return str;
460
+ return str;
461
+ } catch (error) {
462
+ if (!asyncMode) throw error;
463
+
464
+ if (!error || typeof error.then !== 'function') throw error;
465
+
466
+ const renderNestedChildren = () => {
467
+ try {
468
+ return renderChildren();
469
+ } catch (e) {
470
+ if (!e || typeof e.then !== 'function') throw e;
471
+
472
+ return e.then(
473
+ () => renderChildren(),
474
+ () => renderNestedChildren()
475
+ );
476
+ }
477
+ };
478
+
479
+ return error.then(() => renderNestedChildren());
480
+ }
350
481
  }
351
482
 
352
483
  // Serialize Element VNodes to HTML
@@ -476,11 +607,18 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
476
607
  // recurse into this element VNode's children
477
608
  let childSvgMode =
478
609
  type === 'svg' || (type !== 'foreignObject' && isSvgMode);
479
- html = _renderToString(children, context, childSvgMode, selectValue, vnode);
610
+ html = _renderToString(
611
+ children,
612
+ context,
613
+ childSvgMode,
614
+ selectValue,
615
+ vnode,
616
+ asyncMode
617
+ );
480
618
  }
481
619
 
482
620
  if (afterDiff) afterDiff(vnode);
483
- vnode[PARENT] = undefined;
621
+ vnode[PARENT] = null;
484
622
  if (ummountHook) ummountHook(vnode);
485
623
 
486
624
  // Emit self-closing tag for empty void elements:
@@ -488,7 +626,13 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
488
626
  return s + '/>';
489
627
  }
490
628
 
491
- return s + '>' + html + '</' + type + '>';
629
+ const endTag = '</' + type + '>';
630
+ const startTag = s + '>';
631
+
632
+ if (Array.isArray(html)) return [startTag, ...html, endTag];
633
+ else if (typeof html !== 'string') return [startTag, html, endTag];
634
+
635
+ return startTag + html + endTag;
492
636
  }
493
637
 
494
638
  const SELF_CLOSING = new Set([
package/src/util.js CHANGED
@@ -150,3 +150,17 @@ export function createComponent(vnode, context) {
150
150
  __h: []
151
151
  };
152
152
  }
153
+
154
+ /**
155
+ * @template T
156
+ */
157
+ export class Deferred {
158
+ constructor() {
159
+ // eslint-disable-next-line lines-around-comment
160
+ /** @type {Promise<T>} */
161
+ this.promise = new Promise((resolve, reject) => {
162
+ this.resolve = resolve;
163
+ this.reject = reject;
164
+ });
165
+ }
166
+ }