1
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
2
1
import { convert } from "html-to-text" ;
3
- import type { ReactDOMServerReadableStream } from "react-dom/server" ;
2
+ import type {
3
+ PipeableStream ,
4
+ ReactDOMServerReadableStream ,
5
+ } from "react-dom/server" ;
4
6
import { pretty } from "./utils/pretty" ;
5
7
import { plainTextSelectors } from "./plain-text-selectors" ;
6
8
import type { Options } from "./options" ;
7
9
8
10
const decoder = new TextDecoder ( "utf-8" ) ;
9
11
10
12
const readStream = async (
11
- readableStream : NodeJS . ReadableStream | ReactDOMServerReadableStream ,
13
+ stream : PipeableStream | ReactDOMServerReadableStream ,
12
14
) => {
13
15
let result = "" ;
14
16
15
- if ( "allReady" in readableStream ) {
16
- const reader = readableStream . getReader ( ) ;
17
-
18
- // eslint-disable-next-line no-constant-condition
19
- while ( true ) {
20
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, no-await-in-loop
21
- const { value, done } = await reader . read ( ) ;
22
- if ( done ) {
23
- break ;
24
- }
25
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
26
- result += decoder . decode ( value ) ;
27
- }
17
+ if ( "pipeTo" in stream ) {
18
+ // means it's a readable stream
19
+ const writableStream = new WritableStream ( {
20
+ write ( chunk : BufferSource ) {
21
+ result += decoder . decode ( chunk ) ;
22
+ } ,
23
+ } ) ;
24
+ await stream . pipeTo ( writableStream ) ;
28
25
} else {
29
- for await ( const chunk of readableStream ) {
30
- result += decoder . decode ( Buffer . from ( chunk ) ) ;
31
- }
26
+ const {
27
+ default : { Writable } ,
28
+ } = await import ( "node:stream" ) ;
29
+ const writable = new Writable ( {
30
+ write ( chunk : BufferSource , _encoding , callback ) {
31
+ result += decoder . decode ( chunk ) ;
32
+
33
+ callback ( ) ;
34
+ } ,
35
+ } ) ;
36
+ stream . pipe ( writable ) ;
37
+
38
+ return new Promise < string > ( ( resolve , reject ) => {
39
+ writable . on ( "error" , reject ) ;
40
+ writable . on ( "close" , ( ) => {
41
+ resolve ( result ) ;
42
+ } ) ;
43
+ } ) ;
32
44
}
33
45
34
46
return result ;
@@ -39,18 +51,25 @@ export const renderAsync = async (
39
51
options ?: Options ,
40
52
) => {
41
53
const reactDOMServer = await import ( "react-dom/server" ) ;
42
- const renderToStream = Object . hasOwn ( reactDOMServer , "renderToReadableStream" )
43
- ? reactDOMServer . renderToReadableStream // means this is using react-dom/server.browser
44
- : reactDOMServer . renderToStaticNodeStream ;
45
54
46
- const doctype =
47
- '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' ;
48
-
49
- const htmlOrReadableStream = await renderToStream ( component ) ;
50
- const html =
51
- typeof htmlOrReadableStream === "string"
52
- ? htmlOrReadableStream
53
- : await readStream ( htmlOrReadableStream ) ;
55
+ let html ! : string ;
56
+ if ( Object . hasOwn ( reactDOMServer , "renderToReadableStream" ) ) {
57
+ html = await readStream (
58
+ await reactDOMServer . renderToReadableStream ( component ) ,
59
+ ) ;
60
+ } else {
61
+ await new Promise < void > ( ( resolve , reject ) => {
62
+ const stream = reactDOMServer . renderToPipeableStream ( component , {
63
+ async onAllReady ( ) {
64
+ html = await readStream ( stream ) ;
65
+ resolve ( ) ;
66
+ } ,
67
+ onError ( error ) {
68
+ reject ( error as Error ) ;
69
+ } ,
70
+ } ) ;
71
+ } ) ;
72
+ }
54
73
55
74
if ( options ?. plainText ) {
56
75
return convert ( html , {
@@ -59,6 +78,9 @@ export const renderAsync = async (
59
78
} ) ;
60
79
}
61
80
81
+ const doctype =
82
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' ;
83
+
62
84
const document = `${ doctype } ${ html } ` ;
63
85
64
86
if ( options ?. pretty ) {
0 commit comments