Skip to content

Commit 81bc0a8

Browse files
WIP
1 parent 11e8208 commit 81bc0a8

File tree

3 files changed

+95
-61
lines changed

3 files changed

+95
-61
lines changed

src/constants.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ export const PARENT = '__';
1414
export const VNODE = '__v';
1515
export const DIRTY = '__d';
1616
export const NEXT_STATE = '__s';
17+
18+
// Rendering modes
19+
export const MODE_SYNC = 0;
20+
export const MODE_ASYNC = 1;
21+
export const MODE_STREAM = 2;

src/index.js

Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import {
1414
DIFF,
1515
DIFFED,
1616
DIRTY,
17+
MODE_ASYNC,
18+
MODE_STREAM,
19+
MODE_SYNC,
1720
NEXT_STATE,
1821
PARENT,
1922
RENDER,
@@ -79,7 +82,7 @@ export function renderToString(vnode, context) {
7982
false,
8083
undefined,
8184
parent,
82-
false
85+
MODE_SYNC
8386
);
8487
} catch (e) {
8588
if (e.then) {
@@ -92,14 +95,57 @@ export function renderToString(vnode, context) {
9295
}
9396
}
9497

95-
const DEFAULT_RENDER_SLOT = (idx) =>
96-
`<!--preact-slot:${idx}--><!--/preact-slot:${idx}-->`;
98+
/**
99+
* Render Preact JSX + Components to an HTML string.
100+
* @param {VNode} vnode JSX Element / VNode to render
101+
* @param {Object} [context={}] Initial root context object
102+
* @returns {Promise<string>} serialized HTML
103+
*/
104+
export async function renderToStringAsync(vnode, context) {
105+
const previousSkipEffects = prepare();
106+
107+
const parent = h(Fragment, null);
108+
parent[CHILDREN] = [vnode];
109+
110+
try {
111+
const rendered = _renderToString(
112+
vnode,
113+
context || EMPTY_OBJ,
114+
false,
115+
undefined,
116+
parent,
117+
MODE_ASYNC
118+
);
119+
120+
if (Array.isArray(rendered)) {
121+
let count = 0;
122+
let resolved = rendered;
123+
124+
// Resolving nested Promises with a maximum depth of 25
125+
while (
126+
resolved.some((element) => typeof element.then === 'function') &&
127+
count++ < 25
128+
) {
129+
resolved = (await Promise.all(resolved)).flat();
130+
}
131+
132+
return resolved.join('');
133+
}
134+
135+
return rendered;
136+
} finally {
137+
finalize(vnode, previousSkipEffects);
138+
}
139+
}
140+
141+
const DEFAULT_RENDER_SLOT = (idx, content) =>
142+
`<!--p:slot:${idx}-->${content}<!--/p:slot:${idx}-->`;
97143

98144
/**
99145
* Render Preact JSX + Components to an HTML string.
100146
* @param {VNode} vnode JSX Element / VNode to render
101147
* @param {Object} [context={}] Initial root context object
102-
* @param {(idx: number) => string} [renderSlot] Render slot marker
148+
* @param {(idx: number, content: string) => string} [renderSlot] Render slot marker
103149
* @returns {ReadableStream<string>|string} serialized HTML
104150
*/
105151
export function renderToStream(
@@ -119,7 +165,7 @@ export function renderToStream(
119165
false,
120166
undefined,
121167
parent,
122-
true
168+
MODE_STREAM
123169
);
124170

125171
if (Array.isArray(rendered)) {
@@ -180,49 +226,6 @@ export function renderToStream(
180226
}
181227
}
182228

183-
/**
184-
* Render Preact JSX + Components to an HTML string.
185-
* @param {VNode} vnode JSX Element / VNode to render
186-
* @param {Object} [context={}] Initial root context object
187-
* @returns {Promise<string>} serialized HTML
188-
*/
189-
export async function renderToStringAsync(vnode, context) {
190-
const previousSkipEffects = prepare();
191-
192-
const parent = h(Fragment, null);
193-
parent[CHILDREN] = [vnode];
194-
195-
try {
196-
const rendered = _renderToString(
197-
vnode,
198-
context || EMPTY_OBJ,
199-
false,
200-
undefined,
201-
parent,
202-
true
203-
);
204-
205-
if (Array.isArray(rendered)) {
206-
let count = 0;
207-
let resolved = rendered;
208-
209-
// Resolving nested Promises with a maximum depth of 25
210-
while (
211-
resolved.some((element) => typeof element.then === 'function') &&
212-
count++ < 25
213-
) {
214-
resolved = (await Promise.all(resolved)).flat();
215-
}
216-
217-
return resolved.join('');
218-
}
219-
220-
return rendered;
221-
} finally {
222-
finalize(vnode, previousSkipEffects);
223-
}
224-
}
225-
226229
// Installed as setState/forceUpdate for function components
227230
function markAsDirty() {
228231
this.__d = true;
@@ -290,6 +293,7 @@ function renderClassComponent(vnode, context) {
290293
* @param {any} selectValue
291294
* @param {VNode} parent
292295
* @param {boolean} asyncMode
296+
* @param {number} renderMode
293297
* @returns {string | Promise<string> | (string | Promise<string>)[]}
294298
*/
295299
function _renderToString(
@@ -298,7 +302,7 @@ function _renderToString(
298302
isSvgMode,
299303
selectValue,
300304
parent,
301-
asyncMode
305+
renderMode
302306
) {
303307
// Ignore non-rendered VNodes/values
304308
if (vnode == null || vnode === true || vnode === false || vnode === '') {
@@ -326,7 +330,7 @@ function _renderToString(
326330
isSvgMode,
327331
selectValue,
328332
parent,
329-
asyncMode
333+
renderMode
330334
);
331335

332336
if (typeof childRender === 'string') {
@@ -391,7 +395,7 @@ function _renderToString(
391395
isSvgMode,
392396
selectValue,
393397
vnode,
394-
asyncMode
398+
renderMode
395399
);
396400
} else {
397401
// Values are pre-escaped by the JSX transform
@@ -472,7 +476,7 @@ function _renderToString(
472476
isSvgMode,
473477
selectValue,
474478
vnode,
475-
asyncMode
479+
renderMode
476480
);
477481
return str;
478482
} catch (err) {
@@ -504,7 +508,7 @@ function _renderToString(
504508
isSvgMode,
505509
selectValue,
506510
vnode,
507-
asyncMode
511+
renderMode
508512
);
509513
}
510514

@@ -531,7 +535,7 @@ function _renderToString(
531535
isSvgMode,
532536
selectValue,
533537
vnode,
534-
asyncMode
538+
renderMode
535539
);
536540

537541
try {
@@ -545,10 +549,11 @@ function _renderToString(
545549

546550
return str;
547551
} catch (error) {
548-
if (!asyncMode) throw error;
552+
if (renderMode === MODE_SYNC) throw error;
549553

550554
if (!error || typeof error.then !== 'function') throw error;
551555

556+
console.log(renderMode, error);
552557
const renderNestedChildren = () => {
553558
try {
554559
return renderChildren();
@@ -699,7 +704,7 @@ function _renderToString(
699704
childSvgMode,
700705
selectValue,
701706
vnode,
702-
asyncMode
707+
renderMode
703708
);
704709
}
705710

test/compat/stream.test.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ async function drain(iter) {
1616
return out;
1717
}
1818

19-
describe('streaming renderToString', () => {
19+
describe('renderToStream', () => {
2020
it('should render JSX after a suspense boundary', async () => {
2121
const { Suspender, suspended } = createSuspender();
2222

2323
const promise = drain(
2424
renderToStream(
2525
<div>
2626
<h1>foo</h1>
27-
<Suspense fallback={<div>loading...</div>}>
27+
<Suspense>
2828
<Suspender>
2929
<div class="foo">bar</div>
3030
</Suspender>
@@ -36,7 +36,7 @@ describe('streaming renderToString', () => {
3636
suspended.resolve();
3737
const rendered = await promise;
3838
expect(rendered).to.deep.equal([
39-
'<div><h1>foo</h1><!--preact-slot:0--><!--/preact-slot:0--><p>baz</p></div>',
39+
'<div><h1>foo</h1><!--p:slot:0--><!--/p:slot:0--><p>baz</p></div>',
4040
'<div class="foo">bar</div>'
4141
]);
4242
});
@@ -50,7 +50,7 @@ describe('streaming renderToString', () => {
5050
<body>
5151
<div>
5252
<h1>foo</h1>
53-
<Suspense fallback={<div>loading...</div>}>
53+
<Suspense>
5454
<Suspender>
5555
<div class="foo">bar</div>
5656
</Suspender>
@@ -64,9 +64,33 @@ describe('streaming renderToString', () => {
6464
suspended.resolve();
6565
const rendered = await promise;
6666
expect(rendered).to.deep.equal([
67-
'<html><body><div><h1>foo</h1><!--preact-slot:0--><!--/preact-slot:0--><p>baz</p></div>',
67+
'<html><body><div><h1>foo</h1><!--p:slot:0--><!--/p:slot:0--><p>baz</p></div>',
6868
'<div class="foo">bar</div>',
6969
'</body></html>'
7070
]);
7171
});
72+
73+
it.only('should render suspense fallback in placeholder', async () => {
74+
const { Suspender, suspended } = createSuspender();
75+
76+
const promise = drain(
77+
renderToStream(
78+
<div>
79+
<h1>foo</h1>
80+
<Suspense fallback={<p>loading...</p>}>
81+
<Suspender>
82+
<div class="foo">bar</div>
83+
</Suspender>
84+
</Suspense>
85+
<p>baz</p>
86+
</div>
87+
)
88+
);
89+
suspended.resolve();
90+
const rendered = await promise;
91+
expect(rendered).to.deep.equal([
92+
'<div><h1>foo</h1><!--p:slot:0--><!--/p:slot:0--><p>baz</p></div>',
93+
'<div class="foo">bar</div>'
94+
]);
95+
});
7296
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy