This PR adds a benchmark fixture for measuring the performance overhead of the React Server Components (RSC) Flight rendering compared to plain Fizz server-side rendering. ### Motivation Performance discussions around RSC (e.g. #36143, #35125) have highlighted the need for reproducible benchmarks that accurately measure the cost that Flight adds on top of Fizz. This fixture provides multiple benchmark modes that can be used to track performance improvements across commits, compare Node vs Edge (web streams) overhead, and identify bottlenecks in Flight serialization and deserialization. ### What it measures The benchmark renders a dashboard app with ~25 components (16 client components), 200 product rows with nested data (~325KB Flight payload), and ~250 Suspense boundaries in the async variant. It compares 8 render variants: Fizz-only and Flight+Fizz, across Node and Edge stream APIs, with both synchronous and asynchronous apps. ### Benchmark modes - **`yarn bench`** runs a sequential in-process benchmark with realistic Flight script injection (tee + `TransformStream`/`Transform` buffered injection), matching what real frameworks do when inlining the RSC payload into the HTML response for hydration. - **`yarn bench:bare`** runs the same benchmark without script injection, isolating the React-internal rendering cost. This is best for tracking changes to Flight serialization or Fizz rendering. - **`yarn bench:server`** starts an HTTP server and uses `autocannon` to measure real req/s at `c=1` and `c=10`. The `c=1` results provide a clean signal for tracking React-internal changes, while `c=10` reflects throughput under concurrent load. - **`yarn bench:concurrent`** runs an in-process concurrent benchmark with 50 in-flight renders via `Promise.all`, measuring throughput without HTTP overhead. - **`yarn bench:profile`** collects CPU profiles via the V8 inspector and reports the top functions by self-time along with GC pause data. - **`yarn start`** starts the HTTP server for manual browser testing. Appending `.rsc` to any Flight URL serves the raw Flight payload. ### Key findings during development On Node 22, the Flight+Fizz overhead compared to Fizz-only rendering is roughly: - **Without script injection** (`bench:bare`): ~2.2x for sync, ~1.3x for async - **With script injection** (`bench:server`, c=1): ~2.9x for sync, ~1.8x for async - **Edge vs Node** adds another ~30% for sync and ~10% for async, driven by the stream plumbing for script injection (tee + `TransformStream` buffering) The async variant better represents real-world applications where server components fetch data asynchronously. Its lower overhead reflects the fact that Flight serialization and Fizz rendering can overlap with I/O wait times, making the added Flight cost a smaller fraction of total request time. The benchmark also revealed that the Edge vs Node gap is negligible for Fizz-only rendering (~1-2%) but grows to ~15% for Flight+Fizz sync even without script injection. With script injection (tee + `TransformStream` buffering), the gap roughly doubles to ~30% for sync. The async variants show smaller gaps (~5% without, ~10% with injection).
166 lines
8.5 KiB
JSON
166 lines
8.5 KiB
JSON
{
|
|
"private": true,
|
|
"workspaces": [
|
|
"packages/*"
|
|
],
|
|
"devDependencies": {
|
|
"@babel/cli": "^7.10.5",
|
|
"@babel/code-frame": "^7.10.4",
|
|
"@babel/core": "^7.11.1",
|
|
"@babel/helper-define-map": "^7.18.6",
|
|
"@babel/helper-module-imports": "^7.10.4",
|
|
"@babel/parser": "^7.11.3",
|
|
"@babel/plugin-external-helpers": "^7.10.4",
|
|
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
|
"@babel/plugin-proposal-object-rest-spread": "^7.11.0",
|
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
|
"@babel/plugin-syntax-jsx": "^7.23.3",
|
|
"@babel/plugin-syntax-typescript": "^7.14.5",
|
|
"@babel/plugin-transform-arrow-functions": "^7.10.4",
|
|
"@babel/plugin-transform-block-scoped-functions": "^7.10.4",
|
|
"@babel/plugin-transform-block-scoping": "^7.11.1",
|
|
"@babel/plugin-transform-class-properties": "^7.25.9",
|
|
"@babel/plugin-transform-classes": "^7.10.4",
|
|
"@babel/plugin-transform-computed-properties": "^7.10.4",
|
|
"@babel/plugin-transform-destructuring": "^7.10.4",
|
|
"@babel/plugin-transform-for-of": "^7.10.4",
|
|
"@babel/plugin-transform-literals": "^7.10.4",
|
|
"@babel/plugin-transform-modules-commonjs": "^7.10.4",
|
|
"@babel/plugin-transform-object-super": "^7.10.4",
|
|
"@babel/plugin-transform-parameters": "^7.10.5",
|
|
"@babel/plugin-transform-private-methods": "^7.10.4",
|
|
"@babel/plugin-transform-react-jsx": "^7.23.4",
|
|
"@babel/plugin-transform-react-jsx-development": "^7.22.5",
|
|
"@babel/plugin-transform-react-jsx-source": "^7.10.5",
|
|
"@babel/plugin-transform-shorthand-properties": "^7.10.4",
|
|
"@babel/plugin-transform-spread": "^7.11.0",
|
|
"@babel/plugin-transform-template-literals": "^7.10.5",
|
|
"@babel/preset-env": "^7.26.9",
|
|
"@babel/preset-flow": "^7.10.4",
|
|
"@babel/preset-react": "^7.23.3",
|
|
"@babel/preset-typescript": "^7.26.0",
|
|
"@babel/traverse": "^7.11.0",
|
|
"@rollup/plugin-babel": "^6.0.3",
|
|
"@rollup/plugin-commonjs": "^24.0.1",
|
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
|
"@rollup/plugin-replace": "^5.0.2",
|
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
"@types/invariant": "^2.2.35",
|
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
"@typescript-eslint/parser": "^6.21.0",
|
|
"abortcontroller-polyfill": "^1.7.5",
|
|
"art": "0.10.1",
|
|
"babel-plugin-syntax-hermes-parser": "^0.32.0",
|
|
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
|
|
"chalk": "^3.0.0",
|
|
"cli-table": "^0.3.1",
|
|
"coffee-script": "^1.12.7",
|
|
"confusing-browser-globals": "^1.0.9",
|
|
"core-js": "^3.6.4",
|
|
"create-react-class": "^15.6.3",
|
|
"danger": "^11.2.3",
|
|
"error-stack-parser": "^2.0.6",
|
|
"eslint": "^7.7.0",
|
|
"eslint-config-prettier": "^6.9.0",
|
|
"eslint-plugin-babel": "^5.3.0",
|
|
"eslint-plugin-es": "^4.1.0",
|
|
"eslint-plugin-eslint-plugin": "^3.5.3",
|
|
"eslint-plugin-ft-flow": "^2.0.3",
|
|
"eslint-plugin-jest": "28.4.0",
|
|
"eslint-plugin-no-for-of-loops": "^1.0.0",
|
|
"eslint-plugin-no-function-declare-after-return": "^1.0.0",
|
|
"eslint-plugin-react": "^6.7.1",
|
|
"eslint-plugin-react-hooks-published": "npm:eslint-plugin-react-hooks@^5.2.0",
|
|
"eslint-plugin-react-internal": "link:./scripts/eslint-rules",
|
|
"fbjs-scripts": "^3.0.1",
|
|
"filesize": "^6.0.1",
|
|
"flow-bin": "^0.279.0",
|
|
"flow-remove-types": "^2.279.0",
|
|
"flow-typed": "^4.1.1",
|
|
"glob": "^7.1.6",
|
|
"glob-stream": "^6.1.0",
|
|
"google-closure-compiler": "^20230206.0.0",
|
|
"gzip-size": "^5.1.1",
|
|
"hermes-eslint": "^0.32.0",
|
|
"hermes-parser": "^0.32.0",
|
|
"jest": "^29.4.2",
|
|
"jest-cli": "^29.4.2",
|
|
"jest-diff": "^29.4.2",
|
|
"jest-environment-jsdom": "^29.4.2",
|
|
"jest-silent-reporter": "^0.6.0",
|
|
"jest-snapshot-serializer-raw": "^1.2.0",
|
|
"minimatch": "^3.0.4",
|
|
"minimist": "^1.2.3",
|
|
"mkdirp": "^0.5.1",
|
|
"ncp": "^2.0.0",
|
|
"prettier": "^3.3.3",
|
|
"prettier-2": "npm:prettier@^2",
|
|
"pretty-format": "^29.4.1",
|
|
"prop-types": "^15.6.2",
|
|
"random-seed": "^0.3.0",
|
|
"react-lifecycles-compat": "^3.0.4",
|
|
"rimraf": "^3.0.0",
|
|
"rollup": "^3.29.5",
|
|
"rollup-plugin-dts": "^6.1.1",
|
|
"rollup-plugin-prettier": "^4.1.1",
|
|
"rollup-plugin-strip-banner": "^3.0.0",
|
|
"semver": "^7.1.1",
|
|
"shelljs": "^0.8.5",
|
|
"signedsource": "^2.0.0",
|
|
"targz": "^1.0.1",
|
|
"through2": "^3.0.1",
|
|
"tmp": "^0.1.0",
|
|
"to-fast-properties": "^2.0.0",
|
|
"tsup": "^8.4.0",
|
|
"typescript": "^5.4.3",
|
|
"undici": "^5.28.4",
|
|
"web-streams-polyfill": "^3.1.1",
|
|
"yargs": "^15.3.1"
|
|
},
|
|
"jest": {
|
|
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
|
|
},
|
|
"scripts": {
|
|
"prebuild": "./scripts/react-compiler/link-compiler.sh",
|
|
"build": "node ./scripts/rollup/build-all-release-channels.js",
|
|
"build-for-devtools": "cross-env yarn build react/index,react/jsx,react/compiler-runtime,react-dom/index,react-dom/client,react-dom/unstable_testing,react-dom/test-utils,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh,react-art --type=NODE --release-channel=experimental",
|
|
"build-for-devtools-dev": "yarn build-for-devtools --type=NODE_DEV",
|
|
"build-for-devtools-prod": "yarn build-for-devtools --type=NODE_PROD",
|
|
"build-for-flight-dev": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react.react-server,react-dom/index,react-dom/client,react-dom/server,react-dom.react-server,react-dom-server.node,react-dom-server-legacy.node,scheduler,react-server-dom-webpack/,react-server-dom-unbundled/ --type=NODE_DEV,ESM_PROD,NODE_ES2015 && mv ./build/node_modules ./build/oss-experimental",
|
|
"build-for-flight-prod": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react.react-server,react-dom/index,react-dom/client,react-dom/server,react-dom.react-server,react-dom-server.node,react-dom-server-legacy.node,scheduler,react-server-dom-webpack/,react-server-dom-unbundled/ --type=NODE_PROD,ESM_PROD,NODE_ES2015 && mv ./build/node_modules ./build/oss-experimental",
|
|
"build-for-vt-dev": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react-dom/index,react-dom/client,react-dom/server,react-dom-server.node,react-dom-server-legacy.node,scheduler --type=NODE_DEV && mv ./build/node_modules ./build/oss-experimental",
|
|
"flow-typed-install": "yarn flow-typed install --skip --skipFlowRestart --ignore-deps=dev",
|
|
"linc": "node ./scripts/tasks/linc.js",
|
|
"lint": "node ./scripts/tasks/eslint.js",
|
|
"lint-build": "node ./scripts/rollup/validate/index.js",
|
|
"extract-errors": "node scripts/error-codes/extract-errors.js",
|
|
"postinstall": "node ./scripts/flow/createFlowConfigs.js",
|
|
"test": "node ./scripts/jest/jest-cli.js",
|
|
"test-stable": "node ./scripts/jest/jest-cli.js --release-channel=stable",
|
|
"test-www": "node ./scripts/jest/jest-cli.js --release-channel=www-modern",
|
|
"test-classic": "node ./scripts/jest/jest-cli.js --release-channel=www-classic",
|
|
"test-build-devtools": "node ./scripts/jest/jest-cli.js --build --project devtools --release-channel=experimental",
|
|
"test-dom-fixture": "cd fixtures/dom && yarn && yarn test",
|
|
"flow": "node ./scripts/tasks/flow.js",
|
|
"flow-ci": "node ./scripts/tasks/flow-ci.js",
|
|
"prettier": "node ./scripts/prettier/index.js write-changed",
|
|
"prettier-all": "node ./scripts/prettier/index.js write",
|
|
"prettier-check": "node ./scripts/prettier/index.js",
|
|
"version-check": "node ./scripts/tasks/version-check.js",
|
|
"publish-prereleases": "echo 'This command has been deprecated. Please refer to https://github.com/facebook/react/tree/main/scripts/release#trigger-an-automated-prerelease'",
|
|
"download-build": "node ./scripts/release/download-experimental-build.js",
|
|
"download-build-for-head": "node ./scripts/release/download-experimental-build.js --commit=$(git rev-parse HEAD)",
|
|
"download-build-in-codesandbox-ci": "yarn build --type=node react/index react.react-server react-dom/index react-dom/client react-dom/src/server react-dom/test-utils react-dom.react-server scheduler/index react/jsx-runtime react/jsx-dev-runtime react-server-dom-webpack",
|
|
"check-release-dependencies": "node ./scripts/release/check-release-dependencies",
|
|
"generate-inline-fizz-runtime": "node ./scripts/rollup/generate-inline-fizz-runtime.js",
|
|
"generate-changelog": "node ./scripts/tasks/generate-changelog/index.js",
|
|
"flags": "node ./scripts/flags/flags.js"
|
|
},
|
|
"resolutions": {
|
|
"react-is": "npm:react-is",
|
|
"jsdom": "22.1.0"
|
|
},
|
|
"packageManager": "yarn@1.22.22"
|
|
}
|