fixtures/flight-ssr-bench/bench-server.js JAVASCRIPT 351 lines View on github.com → Search inside
1'use strict';23require('@babel/register')({4  presets: [['@babel/preset-react', {runtime: 'automatic'}]],5  plugins: ['@babel/plugin-transform-modules-commonjs'],6  only: [/\/src\//],7});89const http = require('http');10const {Readable} = require('stream');11const webpack = require('webpack');1213const {clientManifest, ssrManifest} = require('./webpack-mock');14const {15  renderFizzNode,16  renderFizzEdge,17  renderFlightFizzNode,18  renderFlightFizzEdge,19} = require('./render-helpers');20const {printGrid} = require('./print-helpers');2122// ---------------------------------------------------------------------------23// Build24// ---------------------------------------------------------------------------2526function build() {27  const config = require('./webpack.config');28  return new Promise(function (resolve, reject) {29    webpack(config, function (err, stats) {30      if (err) {31        reject(err);32        return;33      }34      if (stats.hasErrors()) {35        reject(new Error(stats.toString({errors: true})));36        return;37      }38      console.log(39        stats.toString({colors: true, modules: false, entrypoints: false})40      );41      resolve();42    });43  });44}4546// ---------------------------------------------------------------------------47// Server48// ---------------------------------------------------------------------------4950const ITEM_COUNT = 200;51const PORT = 3001;5253async function main() {54  console.log('Building RSC bundle...\n');55  await build();5657  const {58    renderRSCNode,59    renderRSCEdge,60    App: RSCApp,61    AppAsync: RSCAppAsync,62  } = require('./build/rsc-bundle.js');63  const App = require('./src/App.js').default;64  const AppAsync = require('./src/AppAsync.js').default;6566  function pipeStreamToRes(stream, res) {67    if (typeof stream.pipe === 'function') {68      // Node Readable stream69      stream.pipe(res);70    } else {71      // Web ReadableStream — convert to Node stream for HTTP response72      Readable.fromWeb(stream).pipe(res);73    }74  }7576  function pipeToRes(streamOrPromise, res) {77    if (typeof streamOrPromise.then === 'function') {78      streamOrPromise.then(79        function (stream) {80          pipeStreamToRes(stream, res);81        },82        function (err) {83          console.error(err);84          if (!res.headersSent) res.writeHead(500);85          res.end();86        }87      );88    } else {89      pipeStreamToRes(streamOrPromise, res);90    }91  }9293  const routes = {94    '/fizz-node-sync': function (res) {95      pipeToRes(renderFizzNode(App, ITEM_COUNT), res);96    },97    '/fizz-node-async': function (res) {98      pipeToRes(renderFizzNode(AppAsync, ITEM_COUNT), res);99    },100    '/fizz-edge-sync': function (res) {101      pipeToRes(renderFizzEdge(App, ITEM_COUNT), res);102    },103    '/fizz-edge-async': function (res) {104      pipeToRes(renderFizzEdge(AppAsync, ITEM_COUNT), res);105    },106    '/flight-node-sync': function (res) {107      pipeToRes(108        renderFlightFizzNode(109          renderRSCNode,110          RSCApp,111          ITEM_COUNT,112          clientManifest,113          ssrManifest114        ),115        res116      );117    },118    '/flight-node-sync.rsc': function (res) {119      pipeStreamToRes(renderRSCNode(clientManifest, RSCApp, ITEM_COUNT), res);120    },121    '/flight-node-async': function (res) {122      pipeToRes(123        renderFlightFizzNode(124          renderRSCNode,125          RSCAppAsync,126          ITEM_COUNT,127          clientManifest,128          ssrManifest129        ),130        res131      );132    },133    '/flight-node-async.rsc': function (res) {134      pipeStreamToRes(135        renderRSCNode(clientManifest, RSCAppAsync, ITEM_COUNT),136        res137      );138    },139    '/flight-edge-sync': function (res) {140      pipeToRes(141        renderFlightFizzEdge(142          renderRSCEdge,143          RSCApp,144          ITEM_COUNT,145          clientManifest,146          ssrManifest147        ),148        res149      );150    },151    '/flight-edge-sync.rsc': function (res) {152      pipeStreamToRes(renderRSCEdge(clientManifest, RSCApp, ITEM_COUNT), res);153    },154    '/flight-edge-async': function (res) {155      pipeToRes(156        renderFlightFizzEdge(157          renderRSCEdge,158          RSCAppAsync,159          ITEM_COUNT,160          clientManifest,161          ssrManifest162        ),163        res164      );165    },166    '/flight-edge-async.rsc': function (res) {167      pipeStreamToRes(168        renderRSCEdge(clientManifest, RSCAppAsync, ITEM_COUNT),169        res170      );171    },172  };173174  const server = http.createServer(function (req, res) {175    const handler = routes[req.url];176    if (!handler) {177      if (req.url === '/' || req.url === '') {178        res.writeHead(200, {'Content-Type': 'text/html'});179        res.end(180          '<html><body><h1>Flight SSR Bench</h1><ul>' +181            Object.keys(routes)182              .map(function (r) {183                return '<li><a href="' + r + '">' + r + '</a></li>';184              })185              .join('') +186            '</ul></body></html>'187        );188        return;189      }190      res.writeHead(404);191      res.end('Not found');192      return;193    }194    const contentType = req.url.endsWith('.rsc')195      ? 'text/x-component'196      : 'text/html';197    res.writeHead(200, {'Content-Type': contentType});198    handler(res);199  });200201  await new Promise(function (resolve) {202    server.listen(PORT, resolve);203  });204205  console.log('\nServer listening on http://localhost:%d', PORT);206  console.log('Endpoints:');207  for (const route of Object.keys(routes)) {208    console.log('  http://localhost:%d%s', PORT, route);209  }210211  if (!process.argv.includes('--bench')) {212    return;213  }214215  // Run autocannon against each endpoint.216  // Use a fixed request count (amount) instead of duration so that all217  // in-flight requests complete before autocannon closes connections.218  const autocannon = require('autocannon');219  const concurrencyLevels = [1, 10];220  const WARMUP_AMOUNT = 200;221  const BENCH_AMOUNT = 1000;222223  function runAutocannon(benchUrl, connections, amount) {224    return new Promise(function (resolve, reject) {225      const instance = autocannon({url: benchUrl, connections, amount});226      autocannon.track(instance, {227        renderProgressBar: false,228        renderResultsTable: false,229      });230      instance.on('done', resolve);231      instance.on('error', reject);232    });233  }234235  for (const c of concurrencyLevels) {236    console.log(237      '\n--- HTTP Benchmark (%d warmup, c=%d, %d requests) ---\n',238      WARMUP_AMOUNT,239      c,240      BENCH_AMOUNT241    );242243    const results = {};244    const benchRoutes = Object.keys(routes).filter(function (r) {245      return !r.endsWith('.rsc');246    });247    const labelWidth = Math.max(248      ...benchRoutes.map(function (r) {249        return r.length - 1;250      })251    );252253    const header =254      ''.padEnd(labelWidth) +255      '  ' +256      'req/s'.padStart(14) +257      '  ' +258      'p50'.padStart(8) +259      '  ' +260      'p99'.padStart(8);261    console.log('  ' + header);262    console.log('  ' + '-'.repeat(header.length));263264    for (const route of benchRoutes) {265      const label = route.slice(1);266      const benchUrl = 'http://localhost:' + PORT + route;267268      // Warmup269      await runAutocannon(benchUrl, c, WARMUP_AMOUNT);270271      const data = await runAutocannon(benchUrl, c, BENCH_AMOUNT);272      const reqPerSec = (1000 / data.latency.mean) * data.connections;273      const latencyMedian = data.latency.p50;274      const latencyP99 = data.latency.p99;275      const errors = data.errors + data.timeouts;276277      results[label] = {reqPerSec, latencyMedian, latencyP99};278279      let line =280        '  ' +281        label.padEnd(labelWidth) +282        '  ' +283        String(reqPerSec.toFixed(1)).padStart(8) +284        ' req/s' +285        '  ' +286        String(latencyMedian).padStart(5) +287        ' ms' +288        '  ' +289        String(latencyP99).padStart(5) +290        ' ms';291      if (errors > 0) {292        line += '  (' + errors + ' errors)';293      }294      console.log(line);295    }296297    const rps = function (r) {298      return r.reqPerSec;299    };300301    console.log('\n--- Flight overhead (c=%d) ---\n', c);302    printGrid(303      ['Fizz', 'Flight+Fizz'],304      [305        ['Node sync', results['fizz-node-sync'], results['flight-node-sync']],306        [307          'Node async',308          results['fizz-node-async'],309          results['flight-node-async'],310        ],311        ['Edge sync', results['fizz-edge-sync'], results['flight-edge-sync']],312        [313          'Edge async',314          results['fizz-edge-async'],315          results['flight-edge-async'],316        ],317      ],318      rps,319      'req/s'320    );321322    console.log('\n--- Edge vs Node (c=%d) ---\n', c);323    printGrid(324      ['Node', 'Edge'],325      [326        ['Fizz sync', results['fizz-node-sync'], results['fizz-edge-sync']],327        ['Fizz async', results['fizz-node-async'], results['fizz-edge-async']],328        [329          'Flight+Fizz sync',330          results['flight-node-sync'],331          results['flight-edge-sync'],332        ],333        [334          'Flight+Fizz async',335          results['flight-node-async'],336          results['flight-edge-async'],337        ],338      ],339      rps,340      'req/s'341    );342  }343344  server.close();345}346347main().catch(function (err) {348  console.error(err);349  process.exit(1);350});

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.