1'use strict';23const {PassThrough, Transform} = require('stream');45// ---------------------------------------------------------------------------6// Fizz (Node) — renders App directly via Node streams.7// Returns a Node Readable stream of HTML.8// ---------------------------------------------------------------------------910function renderFizzNode(AppComponent, itemCount) {11 const React = require('react');12 const {renderToPipeableStream} = require('react-dom/server');1314 const output = new PassThrough();15 const {pipe} = renderToPipeableStream(16 React.createElement(AppComponent, {itemCount}),17 {18 onShellReady() {19 pipe(output);20 },21 onError(e) {22 console.error('Fizz Node error:', e);23 output.destroy(e);24 },25 }26 );27 return output;28}2930// ---------------------------------------------------------------------------31// Fizz (Edge) — renders App directly via web streams.32// Returns a promise that resolves to a web ReadableStream of HTML.33// ---------------------------------------------------------------------------3435function renderFizzEdge(AppComponent, itemCount) {36 const React = require('react');37 const {renderToReadableStream} = require('react-dom/server');3839 return renderToReadableStream(React.createElement(AppComponent, {itemCount}));40}4142// ---------------------------------------------------------------------------43// Flight + Fizz (Node) — RSC render → tee → Fizz + script injection.44// HTML chunks are buffered within a tick to avoid injecting scripts mid-tag.45// Returns a Node Readable stream of HTML with injected Flight scripts.46// ---------------------------------------------------------------------------4748function renderFlightFizzNode(49 renderRSCNode,50 AppComponent,51 itemCount,52 clientManifest,53 ssrManifest,54 opts55) {56 const inject = !opts || opts.inject !== false;57 const React = require('react');58 const {renderToPipeableStream} = require('react-dom/server');59 const {createFromNodeStream} = require('react-server-dom-webpack/client');6061 const {pipe: rscPipe} = renderRSCNode(62 clientManifest,63 AppComponent,64 itemCount65 );6667 let flightStream;68 let flightScripts = '';69 if (inject) {70 // Tee the Flight stream into SSR + script injection71 const trunk = new PassThrough();72 const forSsr = new PassThrough();73 const forInline = new PassThrough();74 trunk.pipe(forSsr);75 trunk.pipe(forInline);7677 forInline.on('data', function (chunk) {78 flightScripts +=79 '<script>(self.__FLIGHT_DATA||=[]).push(' +80 JSON.stringify(chunk.toString()) +81 ')</script>';82 });8384 rscPipe(trunk);85 flightStream = forSsr;86 } else {87 flightStream = new PassThrough();88 rscPipe(flightStream);89 }9091 let cachedResult;92 function Root() {93 if (!cachedResult) {94 cachedResult = createFromNodeStream(flightStream, ssrManifest);95 }96 return React.use(cachedResult);97 }9899 const output = new PassThrough();100101 const {pipe} = renderToPipeableStream(React.createElement(Root), {102 onShellReady() {103 if (inject) {104 // Buffer HTML chunks within a tick to avoid injecting scripts mid-tag.105 const trailer = '</body></html>';106 let buffered = [];107 let timeout = null;108 const injector = new Transform({109 transform(chunk, _encoding, cb) {110 buffered.push(chunk);111 if (!timeout) {112 timeout = setTimeout(() => {113 for (const buf of buffered) {114 let str = buf.toString();115 if (str.endsWith(trailer)) {116 str = str.slice(0, -trailer.length);117 }118 this.push(str);119 }120 buffered.length = 0;121 timeout = null;122 if (flightScripts) {123 this.push(flightScripts);124 flightScripts = '';125 }126 }, 0);127 }128 cb();129 },130 flush(cb) {131 if (timeout) {132 clearTimeout(timeout);133 for (const buf of buffered) {134 let str = buf.toString();135 if (str.endsWith(trailer)) {136 str = str.slice(0, -trailer.length);137 }138 this.push(str);139 }140 buffered.length = 0;141 }142 if (flightScripts) {143 this.push(flightScripts);144 flightScripts = '';145 }146 this.push(trailer);147 cb();148 },149 });150 pipe(injector);151 injector.pipe(output);152 } else {153 pipe(output);154 }155 },156 onError(e) {157 console.error('Flight+Fizz Node error:', e);158 output.destroy(e);159 },160 });161162 return output;163}164165// ---------------------------------------------------------------------------166// Flight + Fizz (Edge) — RSC render → tee → Fizz + script injection via web167// streams. HTML chunks are buffered within a tick to avoid injecting scripts168// mid-tag. The </body></html> trailer is stripped, Flight scripts injected,169// and the trailer re-added at flush.170// Returns a promise that resolves to a web ReadableStream.171// ---------------------------------------------------------------------------172173function renderFlightFizzEdge(174 renderRSCEdge,175 AppComponent,176 itemCount,177 clientManifest,178 ssrManifest,179 opts180) {181 const inject = !opts || opts.inject !== false;182 const React = require('react');183 const {renderToReadableStream} = require('react-dom/server');184 const {185 createFromReadableStream,186 } = require('react-server-dom-webpack/client.edge');187188 const webStream = renderRSCEdge(clientManifest, AppComponent, itemCount);189190 let forSsr;191 let injector;192193 if (inject) {194 const htmlTrailer = '</body></html>';195 const enc = new TextEncoder();196197 let forInline;198 [forSsr, forInline] = webStream.tee();199200 let resolveInline;201 const inlinePromise = new Promise(function (r) {202 resolveInline = r;203 });204 const htmlDecoder = new TextDecoder();205 let buffered = [];206 let timeout = null;207208 function flushBuffered(controller) {209 for (const chunk of buffered) {210 let buf = htmlDecoder.decode(chunk, {stream: true});211 if (buf.endsWith(htmlTrailer)) {212 buf = buf.slice(0, -htmlTrailer.length);213 }214 controller.enqueue(enc.encode(buf));215 }216 const remaining = htmlDecoder.decode();217 if (remaining.length) {218 let buf = remaining;219 if (buf.endsWith(htmlTrailer)) {220 buf = buf.slice(0, -htmlTrailer.length);221 }222 controller.enqueue(enc.encode(buf));223 }224 buffered.length = 0;225 timeout = null;226 }227228 function writeFlightChunk(data, controller) {229 controller.enqueue(230 enc.encode(231 '<script>(self.__FLIGHT_DATA||=[]).push(' +232 JSON.stringify(data) +233 ')</script>'234 )235 );236 }237238 injector = new TransformStream({239 start(controller) {240 (async function () {241 const reader = forInline.getReader();242 const decoder = new TextDecoder('utf-8', {fatal: true});243 for (;;) {244 const {done, value} = await reader.read();245 if (done) break;246 writeFlightChunk(decoder.decode(value, {stream: true}), controller);247 }248 const remaining = decoder.decode();249 if (remaining.length) {250 writeFlightChunk(remaining, controller);251 }252 resolveInline();253 })();254 },255 transform(chunk, controller) {256 buffered.push(chunk);257 if (!timeout) {258 timeout = setTimeout(function () {259 flushBuffered(controller);260 }, 0);261 }262 },263 async flush(controller) {264 await inlinePromise;265 if (timeout) {266 clearTimeout(timeout);267 flushBuffered(controller);268 }269 controller.enqueue(enc.encode(htmlTrailer));270 },271 });272 } else {273 forSsr = webStream;274 }275276 const cachedResult = createFromReadableStream(forSsr, {277 serverConsumerManifest: ssrManifest,278 });279 function Root() {280 return React.use(cachedResult);281 }282283 return renderToReadableStream(React.createElement(Root)).then(284 function (htmlStream) {285 return injector ? htmlStream.pipeThrough(injector) : htmlStream;286 }287 );288}289290// ---------------------------------------------------------------------------291// Utilities: collect streams into strings.292// ---------------------------------------------------------------------------293294function nodeStreamToString(nodeStream) {295 return new Promise(function (resolve, reject) {296 const chunks = [];297 nodeStream.on('data', function (chunk) {298 chunks.push(chunk);299 });300 nodeStream.on('end', function () {301 resolve(Buffer.concat(chunks).toString('utf-8'));302 });303 nodeStream.on('error', reject);304 });305}306307function webStreamToString(webStream) {308 const reader = webStream.getReader();309 const chunks = [];310 function read() {311 return reader.read().then(function ({done, value}) {312 if (done) {313 return Buffer.concat(chunks).toString('utf-8');314 }315 chunks.push(Buffer.from(value));316 return read();317 });318 }319 return read();320}321322module.exports = {323 renderFizzNode,324 renderFizzEdge,325 renderFlightFizzNode,326 renderFlightFizzEdge,327 nodeStreamToString,328 webStreamToString,329};
Findings
✓ No findings reported for this file.