Skip to content

Commit 19ee728

Browse files
committed
Denote suspenseful components with comment makers
1 parent ae6450b commit 19ee728

File tree

2 files changed

+78
-37
lines changed

2 files changed

+78
-37
lines changed

src/index.js

+45-18
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export function renderToString(vnode, context, _rendererState) {
6565
_rendererState
6666
);
6767

68-
if (Array.isArray(rendered)) {
68+
if (isArray(rendered)) {
6969
return rendered.join('');
7070
}
7171
return rendered;
@@ -119,7 +119,7 @@ export async function renderToStringAsync(vnode, context) {
119119
undefined
120120
);
121121

122-
if (Array.isArray(rendered)) {
122+
if (isArray(rendered)) {
123123
let count = 0;
124124
let resolved = rendered;
125125

@@ -150,6 +150,8 @@ function markAsDirty() {
150150
}
151151

152152
const EMPTY_OBJ = {};
153+
const BEGIN_SUSPENSE_DENOMINATOR = '<!-- $s -->';
154+
const END_SUSPENSE_DENOMINATOR = '<!-- /$s -->';
153155

154156
/**
155157
* @param {VNode} vnode
@@ -368,7 +370,12 @@ function _renderToString(
368370

369371
if (renderHook) renderHook(vnode);
370372

371-
rendered = type.call(component, props, cctx);
373+
try {
374+
rendered = type.call(component, props, cctx);
375+
} catch (e) {
376+
if (asyncMode) vnode._suspended = true;
377+
throw e;
378+
}
372379
}
373380
component[DIRTY] = true;
374381
}
@@ -398,6 +405,7 @@ function _renderToString(
398405
selectValue,
399406
vnode,
400407
asyncMode,
408+
false,
401409
renderer
402410
);
403411
return str;
@@ -472,6 +480,21 @@ function _renderToString(
472480

473481
if (options.unmount) options.unmount(vnode);
474482

483+
if (vnode._suspended) {
484+
if (typeof str === 'string') {
485+
return BEGIN_SUSPENSE_DENOMINATOR + str + END_SUSPENSE_DENOMINATOR;
486+
} else if (isArray(str)) {
487+
str.unshift(BEGIN_SUSPENSE_DENOMINATOR);
488+
str.push(END_SUSPENSE_DENOMINATOR);
489+
return str;
490+
}
491+
492+
return str.then(
493+
(resolved) =>
494+
BEGIN_SUSPENSE_DENOMINATOR + resolved + END_SUSPENSE_DENOMINATOR
495+
);
496+
}
497+
475498
return str;
476499
} catch (error) {
477500
if (!asyncMode && renderer && renderer.onError) {
@@ -500,7 +523,7 @@ function _renderToString(
500523

501524
const renderNestedChildren = () => {
502525
try {
503-
return _renderToString(
526+
const result = _renderToString(
504527
rendered,
505528
context,
506529
isSvgMode,
@@ -509,26 +532,30 @@ function _renderToString(
509532
asyncMode,
510533
renderer
511534
);
535+
return vnode._suspended
536+
? BEGIN_SUSPENSE_DENOMINATOR + result + END_SUSPENSE_DENOMINATOR
537+
: result;
512538
} catch (e) {
513539
if (!e || typeof e.then !== 'function') throw e;
514540

515-
return e.then(
516-
() =>
517-
_renderToString(
518-
rendered,
519-
context,
520-
isSvgMode,
521-
selectValue,
522-
vnode,
523-
asyncMode,
524-
renderer
525-
),
526-
() => renderNestedChildren()
527-
);
541+
return e.then(() => {
542+
const result = _renderToString(
543+
rendered,
544+
context,
545+
isSvgMode,
546+
selectValue,
547+
vnode,
548+
asyncMode,
549+
renderer
550+
);
551+
return vnode._suspended
552+
? BEGIN_SUSPENSE_DENOMINATOR + result + END_SUSPENSE_DENOMINATOR
553+
: result;
554+
}, renderNestedChildren);
528555
}
529556
};
530557

531-
return error.then(() => renderNestedChildren());
558+
return error.then(renderNestedChildren);
532559
}
533560
}
534561

test/compat/async.test.jsx

+33-19
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('Async renderToString', () => {
1616
</Suspense>
1717
);
1818

19-
const expected = `<div class="foo">bar</div>`;
19+
const expected = `<!-- $s --><div class="foo">bar</div><!-- /$s -->`;
2020

2121
suspended.resolve();
2222

@@ -26,10 +26,14 @@ describe('Async renderToString', () => {
2626
});
2727

2828
it('should render JSX with nested suspended components', async () => {
29-
const { Suspender: SuspenderOne, suspended: suspendedOne } =
30-
createSuspender();
31-
const { Suspender: SuspenderTwo, suspended: suspendedTwo } =
32-
createSuspender();
29+
const {
30+
Suspender: SuspenderOne,
31+
suspended: suspendedOne
32+
} = createSuspender();
33+
const {
34+
Suspender: SuspenderTwo,
35+
suspended: suspendedTwo
36+
} = createSuspender();
3337

3438
const promise = renderToStringAsync(
3539
<ul>
@@ -45,7 +49,7 @@ describe('Async renderToString', () => {
4549
</ul>
4650
);
4751

48-
const expected = `<ul><li>one</li><li>two</li><li>three</li></ul>`;
52+
const expected = `<ul><!-- $s --><li>one</li><!-- $s --><li>two</li><!-- /$s --><li>three</li><!-- /$s --></ul>`;
4953

5054
suspendedOne.resolve();
5155
suspendedTwo.resolve();
@@ -56,10 +60,14 @@ describe('Async renderToString', () => {
5660
});
5761

5862
it('should render JSX with nested suspense boundaries', async () => {
59-
const { Suspender: SuspenderOne, suspended: suspendedOne } =
60-
createSuspender();
61-
const { Suspender: SuspenderTwo, suspended: suspendedTwo } =
62-
createSuspender();
63+
const {
64+
Suspender: SuspenderOne,
65+
suspended: suspendedOne
66+
} = createSuspender();
67+
const {
68+
Suspender: SuspenderTwo,
69+
suspended: suspendedTwo
70+
} = createSuspender();
6371

6472
const promise = renderToStringAsync(
6573
<ul>
@@ -77,7 +85,7 @@ describe('Async renderToString', () => {
7785
</ul>
7886
);
7987

80-
const expected = `<ul><li>one</li><li>two</li><li>three</li></ul>`;
88+
const expected = `<ul><!-- $s --><li>one</li><!-- $s --><li>two</li><!-- /$s --><li>three</li><!-- /$s --></ul>`;
8189

8290
suspendedOne.resolve();
8391
suspendedTwo.resolve();
@@ -88,12 +96,18 @@ describe('Async renderToString', () => {
8896
});
8997

9098
it('should render JSX with multiple suspended direct children within a single suspense boundary', async () => {
91-
const { Suspender: SuspenderOne, suspended: suspendedOne } =
92-
createSuspender();
93-
const { Suspender: SuspenderTwo, suspended: suspendedTwo } =
94-
createSuspender();
95-
const { Suspender: SuspenderThree, suspended: suspendedThree } =
96-
createSuspender();
99+
const {
100+
Suspender: SuspenderOne,
101+
suspended: suspendedOne
102+
} = createSuspender();
103+
const {
104+
Suspender: SuspenderTwo,
105+
suspended: suspendedTwo
106+
} = createSuspender();
107+
const {
108+
Suspender: SuspenderThree,
109+
suspended: suspendedThree
110+
} = createSuspender();
97111

98112
const promise = renderToStringAsync(
99113
<ul>
@@ -113,7 +127,7 @@ describe('Async renderToString', () => {
113127
</ul>
114128
);
115129

116-
const expected = `<ul><li>one</li><li>two</li><li>three</li></ul>`;
130+
const expected = `<ul><!-- $s --><li>one</li><!-- /$s --><!-- $s --><li>two</li><!-- /$s --><!-- $s --><li>three</li><!-- /$s --></ul>`;
117131

118132
suspendedOne.resolve();
119133
suspendedTwo.resolve();
@@ -173,6 +187,6 @@ describe('Async renderToString', () => {
173187

174188
suspended.resolve();
175189
const rendered = await promise;
176-
expect(rendered).to.equal('<p>ok</p>');
190+
expect(rendered).to.equal('<!-- $s --><p>ok</p><!-- /$s -->');
177191
});
178192
});

0 commit comments

Comments
 (0)