fixtures/flight-ssr-bench/render-helpers.js JAVASCRIPT 330 lines View on github.com → Search inside
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.