1- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
21import { 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" ;
46import { pretty } from "./utils/pretty" ;
57import { plainTextSelectors } from "./plain-text-selectors" ;
68import type { Options } from "./options" ;
79
810const decoder = new TextDecoder ( "utf-8" ) ;
911
1012const readStream = async (
11- readableStream : NodeJS . ReadableStream | ReactDOMServerReadableStream ,
13+ stream : PipeableStream | ReactDOMServerReadableStream ,
1214) => {
1315 let result = "" ;
1416
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 ) ;
2825 } 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+ } ) ;
3244 }
3345
3446 return result ;
@@ -39,18 +51,25 @@ export const renderAsync = async (
3951 options ?: Options ,
4052) => {
4153 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 ;
4554
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+ }
5473
5574 if ( options ?. plainText ) {
5675 return convert ( html , {
@@ -59,6 +78,9 @@ export const renderAsync = async (
5978 } ) ;
6079 }
6180
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+
6284 const document = `${ doctype } ${ html } ` ;
6385
6486 if ( options ?. pretty ) {
0 commit comments