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.