'use strict';
require('@babel/register')({
presets: [['@babel/preset-react', {runtime: 'automatic'}]],
plugins: ['@babel/plugin-transform-modules-commonjs'],
only: [/\/src\//],
});
const http = require('http');
const {Readable} = require('stream');
const webpack = require('webpack');
const {clientManifest, ssrManifest} = require('./webpack-mock');
const {
renderFizzNode,
renderFizzEdge,
renderFlightFizzNode,
renderFlightFizzEdge,
} = require('./render-helpers');
const {printGrid} = require('./print-helpers');
function build() {
const config = require('./webpack.config');
return new Promise(function (resolve, reject) {
webpack(config, function (err, stats) {
if (err) {
reject(err);
return;
}
if (stats.hasErrors()) {
reject(new Error(stats.toString({errors: true})));
return;
}
console.log(
stats.toString({colors: true, modules: false, entrypoints: false})
);
resolve();
});
});
}
const ITEM_COUNT = 200;
const PORT = 3001;
async function main() {
console.log('Building RSC bundle...\n');
await build();
const {
renderRSCNode,
renderRSCEdge,
App: RSCApp,
AppAsync: RSCAppAsync,
} = require('./build/rsc-bundle.js');
const App = require('./src/App.js').default;
const AppAsync = require('./src/AppAsync.js').default;
function pipeStreamToRes(stream, res) {
if (typeof stream.pipe === 'function') {
stream.pipe(res);
} else {
Readable.fromWeb(stream).pipe(res);
}
}
function pipeToRes(streamOrPromise, res) {
if (typeof streamOrPromise.then === 'function') {
streamOrPromise.then(
function (stream) {
pipeStreamToRes(stream, res);
},
function (err) {
console.error(err);
if (!res.headersSent) res.writeHead(500);
res.end();
}
);
} else {
pipeStreamToRes(streamOrPromise, res);
}
}
const routes = {
'/fizz-node-sync': function (res) {
pipeToRes(renderFizzNode(App, ITEM_COUNT), res);
},
'/fizz-node-async': function (res) {
pipeToRes(renderFizzNode(AppAsync, ITEM_COUNT), res);
},
'/fizz-edge-sync': function (res) {
pipeToRes(renderFizzEdge(App, ITEM_COUNT), res);
},
'/fizz-edge-async': function (res) {
pipeToRes(renderFizzEdge(AppAsync, ITEM_COUNT), res);
},
'/flight-node-sync': function (res) {
pipeToRes(
renderFlightFizzNode(
renderRSCNode,
RSCApp,
ITEM_COUNT,
clientManifest,
ssrManifest
),
res
);
},
'/flight-node-sync.rsc': function (res) {
pipeStreamToRes(renderRSCNode(clientManifest, RSCApp, ITEM_COUNT), res);
},
'/flight-node-async': function (res) {
pipeToRes(
renderFlightFizzNode(
renderRSCNode,
RSCAppAsync,
ITEM_COUNT,
clientManifest,
ssrManifest
),
res
);
},
'/flight-node-async.rsc': function (res) {
pipeStreamToRes(
renderRSCNode(clientManifest, RSCAppAsync, ITEM_COUNT),
res
);
},
'/flight-edge-sync': function (res) {
pipeToRes(
renderFlightFizzEdge(
renderRSCEdge,
RSCApp,
ITEM_COUNT,
clientManifest,
ssrManifest
),
res
);
},
'/flight-edge-sync.rsc': function (res) {
pipeStreamToRes(renderRSCEdge(clientManifest, RSCApp, ITEM_COUNT), res);
},
'/flight-edge-async': function (res) {
pipeToRes(
renderFlightFizzEdge(
renderRSCEdge,
RSCAppAsync,
ITEM_COUNT,
clientManifest,
ssrManifest
),
res
);
},
'/flight-edge-async.rsc': function (res) {
pipeStreamToRes(
renderRSCEdge(clientManifest, RSCAppAsync, ITEM_COUNT),
res
);
},
};
const server = http.createServer(function (req, res) {
const handler = routes[req.url];
if (!handler) {
if (req.url === '/' || req.url === '') {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(
'<html><body><h1>Flight SSR Bench</h1><ul>' +
Object.keys(routes)
.map(function (r) {
return '<li><a href="' + r + '">' + r + '</a></li>';
})
.join('') +
'</ul></body></html>'
);
return;
}
res.writeHead(404);
res.end('Not found');
return;
}
const contentType = req.url.endsWith('.rsc')
? 'text/x-component'
: 'text/html';
res.writeHead(200, {'Content-Type': contentType});
handler(res);
});
await new Promise(function (resolve) {
server.listen(PORT, resolve);
});
console.log('\nServer listening on http://localhost:%d', PORT);
console.log('Endpoints:');
for (const route of Object.keys(routes)) {
console.log(' http://localhost:%d%s', PORT, route);
}
if (!process.argv.includes('--bench')) {
return;
}
const autocannon = require('autocannon');
const concurrencyLevels = [1, 10];
const WARMUP_AMOUNT = 200;
const BENCH_AMOUNT = 1000;
function runAutocannon(benchUrl, connections, amount) {
return new Promise(function (resolve, reject) {
const instance = autocannon({url: benchUrl, connections, amount});
autocannon.track(instance, {
renderProgressBar: false,
renderResultsTable: false,
});
instance.on('done', resolve);
instance.on('error', reject);
});
}
for (const c of concurrencyLevels) {
console.log(
'\n--- HTTP Benchmark (%d warmup, c=%d, %d requests) ---\n',
WARMUP_AMOUNT,
c,
BENCH_AMOUNT
);
const results = {};
const benchRoutes = Object.keys(routes).filter(function (r) {
return !r.endsWith('.rsc');
});
const labelWidth = Math.max(
...benchRoutes.map(function (r) {
return r.length - 1;
})
);
const header =
''.padEnd(labelWidth) +
' ' +
'req/s'.padStart(14) +
' ' +
'p50'.padStart(8) +
' ' +
'p99'.padStart(8);
console.log(' ' + header);
console.log(' ' + '-'.repeat(header.length));
for (const route of benchRoutes) {
const label = route.slice(1);
const benchUrl = 'http://localhost:' + PORT + route;
await runAutocannon(benchUrl, c, WARMUP_AMOUNT);
const data = await runAutocannon(benchUrl, c, BENCH_AMOUNT);
const reqPerSec = (1000 / data.latency.mean) * data.connections;
const latencyMedian = data.latency.p50;
const latencyP99 = data.latency.p99;
const errors = data.errors + data.timeouts;
results[label] = {reqPerSec, latencyMedian, latencyP99};
let line =
' ' +
label.padEnd(labelWidth) +
' ' +
String(reqPerSec.toFixed(1)).padStart(8) +
' req/s' +
' ' +
String(latencyMedian).padStart(5) +
' ms' +
' ' +
String(latencyP99).padStart(5) +
' ms';
if (errors > 0) {
line += ' (' + errors + ' errors)';
}
console.log(line);
}
const rps = function (r) {
return r.reqPerSec;
};
console.log('\n--- Flight overhead (c=%d) ---\n', c);
printGrid(
['Fizz', 'Flight+Fizz'],
[
['Node sync', results['fizz-node-sync'], results['flight-node-sync']],
[
'Node async',
results['fizz-node-async'],
results['flight-node-async'],
],
['Edge sync', results['fizz-edge-sync'], results['flight-edge-sync']],
[
'Edge async',
results['fizz-edge-async'],
results['flight-edge-async'],
],
],
rps,
'req/s'
);
console.log('\n--- Edge vs Node (c=%d) ---\n', c);
printGrid(
['Node', 'Edge'],
[
['Fizz sync', results['fizz-node-sync'], results['fizz-edge-sync']],
['Fizz async', results['fizz-node-async'], results['fizz-edge-async']],
[
'Flight+Fizz sync',
results['flight-node-sync'],
results['flight-edge-sync'],
],
[
'Flight+Fizz async',
results['flight-node-async'],
results['flight-edge-async'],
],
],
rps,
'req/s'
);
}
server.close();
}
main().catch(function (err) {
console.error(err);
process.exit(1);
});