[react-dom] Include all Node.js APIs in Bun entrypoint for /server
Project: facebook/react | Language: JavaScript | Concepts: api-design, backwards-compatibility | Difficulty: mid
Link: https://github.com/reactjs/react/pull/34193
The problem
The Bun-specific entrypoint for React's server-side rendering (react-dom/server.bun) was missing several APIs that are present in the standard Node.js entrypoint. This caused errors for users running React server code on Bun, since some expected exports (like renderToPipeableStream and related streaming APIs) were unavailable.
Why it's non-trivial
A naive fix might only add the missing exports in one place, but the React codebase uses multiple layers of entrypoints and re-exports for different environments (Node, Bun, stable, experimental, etc). Ensuring consistency and compatibility requires updating several files and types, and making sure that the Bun entrypoint matches Node's API surface, even for new or less-common APIs.
The approach
1. Add missing exports to the Bun entrypoint:
exports.renderToReadableStream = b.renderToReadableStream;
+exports.renderToPipeableStream = b.renderToPipeableStream;
+exports.resumeToPipeableStream = b.resumeToPipeableStream;
exports.resume = b.resume;
This adds renderToPipeableStream and resumeToPipeableStream to the Bun server export, matching Node's API.
2. Re-export Node APIs from the Bun server source:
+export function renderToPipeableStream() {
+ return require('./src/server/react-dom-server.bun').renderToPipeableStream.apply(
+ this,
+ arguments,
+ );
+}
+
+export function resumeToPipeableStream() {
+ return require('./src/server/react-dom-server.bun').resumeToPipeableStream.apply(
+ this,
+ arguments,
+ );
+}
These functions forward calls to the implementation, ensuring the Bun entrypoint exposes the same streaming APIs as Node.
3. Update module re-exports to include all relevant APIs:
+export {
+ renderToPipeableStream,
+ resumeToPipeableStream,
+ resume,
+} from './ReactDOMFizzServerNode.js';
+export {
+ prerenderToNodeStream,
+ prerender,
+ resumeAndPrerenderToNodeStream,
+ resumeAndPrerender,
+} from './ReactDOMFizzStaticNode.js';
These changes ensure that the Bun server modules re-export all the APIs available in the Node version, including static and prerendering functions.
4. Update type definitions and compatibility for stream handling:
+import type {Writable} from 'stream';
...
+interface MightBeFlushable {
+ flush?: () => void;
+}
+
+export type Destination =
+ | BunReadableStreamController
+ | (Writable & MightBeFlushable);
This expands the Destination type to support both Bun and Node stream types, improving compatibility.
5. Adjust stream error handling to support more stream interfaces:
+ } else if (typeof destination.destroy === 'function') {
+ // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types.
+ destination.destroy(error);
+
+ // $FlowFixMe[incompatible-use]
+ // $FlowFixMe[method-unbinding]
+ } else if (typeof destination.close === 'function') {
// Earlier implementations doesn't support this method. In that environment you're
// supposed to throw from a promise returned but we don't return a promise in our
// approach. We could fork this implementation but this is environment is an edge
This makes the error handling logic compatible with both Bun and Node stream interfaces, covering more edge cases.
6. Update build configuration to mark Node core modules as externals:
- externals: ['react', 'react-dom'],
+ externals: ['react', 'react-dom', 'crypto', 'stream', 'util'],
This ensures the Bun bundle doesn't try to include or polyfill Node core modules, relying on Bun's compatibility.
7. Add new server files to the inlined host configs:
+ 'react-dom/src/server/ReactDOMFizzServerNode.js',
+ 'react-dom/src/server/ReactDOMFizzStaticNode.js',
This makes sure the build system recognizes and processes the new/updated files.
From the review thread
No notable review discussion on this PR.
Takeaways
- When maintaining cross-environment compatibility, ensure all public APIs are consistently exported across all supported platforms.
- Updating type definitions and error handling logic is crucial when supporting multiple runtime stream interfaces.
- Build and bundling configurations should explicitly handle external dependencies to avoid runtime issues in alternate environments.