Repository: facebook/react
Pull request: Stop treating all Node.js builtins implicitly as externals

Noticed this when reviewing https://github.com/facebook/react/pull/34193. 

We have an `externals` property for each bundle we build. This is supposed to prevent bundling modules that are listed in that entry. 

However, all Node.js modules were also treated as externals by `@rollup/plugin-node-resolve`. This is wrong for bundles that are not targeting Node.js (e.g. Bun or edge environments). Instead, bundles need explicitly list the modules they support. For example, edge environments do support the `util` module so they can list that. But edge environments don't support  `stream` so it's not safe to mark that module as an external. It's not 100% accurate for modules that only have partial implementations. E.g. `async_hooks` is implemented in Bun and edge environments. But not all methods in that module (e.g. `createHook`) so you'd still have to be careful declaring `async_hooks` as an external in Bun.

I also removed some obvious unused externals from some bundles. We can't fail builds on unused externals since some bundle only use the external for a specific type e.g. `react` only uses `ReactNativeInternalFeatureFlags` for the RN bundles.

<details>
<summary>Error with unexpected externals</summary>

```
 BUILDING  react-server-dom-webpack-server.node.unbundled.development.js (node_dev)

"async_hooks" is imported by "packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js", but could not be resolved – treating it as an external dependency.

error Command failed with exit code 1.
```
</details>


Changed files:
filename: scripts/rollup/build.js
patch: @@ -144,7 +144,6 @@ function getBabelConfig(
   updateBabelOptions,
   bundleType,
   packageName,
-  externals,
   isDevelopment,
   bundle
 ) {
@@ -354,7 +353,6 @@ function forbidFBJSImports() {
 
 function getPlugins(
   entry,
-  externals,
   updateBabelOptions,
   filename,
   packageName,
@@ -402,8 +400,8 @@ function getPlugins(
       forbidFBJSImports(),
       // Use Node resolution mechanism.
       resolve({
-        // skip: externals, // TODO: options.skip was removed in @rollup/plugin-node-resolve 3.0.0
-        preferBuiltins: bundle.preferBuiltins,
+        // `external` rollup config takes care of marking builtins as externals
+        preferBuiltins: false,
       }),
       // Remove license headers from individual modules
       stripBanner({
@@ -415,7 +413,6 @@ function getPlugins(
           updateBabelOptions,
           bundleType,
           packageName,
-          externals,
           !isProduction,
           bundle
         )
@@ -690,7 +687,6 @@ async function createBundle(bundle, bundleType) {
     onwarn: handleRollupWarning,
     plugins: getPlugins(
       bundle.entry,
-      externals,
       bundle.babel,
       filename,
       packageName,

filename: scripts/rollup/bundles.js
patch: @@ -363,7 +363,14 @@ const bundles = [
     global: 'ReactDOMServer',
     minifyWithProdErrorCodes: false,
     wrapWithModuleBoundaries: false,
-    externals: ['react', 'util', 'crypto', 'async_hooks', 'react-dom'],
+    externals: [
+      'react',
+      'react-dom',
+      'async_hooks',
+      'crypto',
+      'stream',
+      'util',
+    ],
   },
   {
     bundleTypes: __EXPERIMENTAL__ ? [FB_WWW_DEV, FB_WWW_PROD] : [],
@@ -460,7 +467,14 @@ const bundles = [
     global: 'ReactServerDOMServer',
     minifyWithProdErrorCodes: false,
     wrapWithModuleBoundaries: false,
-    externals: ['react', 'util', 'crypto', 'async_hooks', 'react-dom'],
+    externals: [
+      'react',
+      'react-dom',
+      'async_hooks',
+      'crypto',
+      'stream',
+      'util',
+    ],
   },
   {
     bundleTypes: [NODE_DEV, NODE_PROD],
@@ -472,7 +486,14 @@ const bundles = [
     global: 'ReactServerDOMServer',
     minifyWithProdErrorCodes: false,
     wrapWithModuleBoundaries: false,
-    externals: ['react', 'util', 'crypto', 'async_hooks', 'react-dom'],
+    externals: [
+      'react',
+      'react-dom',
+      'async_hooks',
+      'crypto',
+      'stream',
+      'util',
+    ],
   },
   {
     bundleTypes: [NODE_DEV, NODE_PROD],
@@ -483,7 +504,7 @@ const bundles = [
     global: 'ReactServerDOMServer',
     minifyWithProdErrorCodes: false,
     wrapWithModuleBoundaries: false,
-    externals: ['react', 'util', 'crypto', 'async_hooks', 'react-dom'],
+    externals: ['react'],
   },
 
   /******* React Server DOM Webpack Client *******/
@@ -588,7 +609,14 @@ const bundles = [
     global: 'ReactServerDOMServer',
     minifyWithProdErrorCodes: false,
     wrapWithModuleBoundaries: false,
-    externals: ['react', 'util', 'async_hooks', 'react-dom'],
+    externals: [
+      'react',
+      'react-dom',
+      'async_hooks',
+      'crypto',
+      'stream',
+      'util',
+    ],
   },
   {
     bundleTypes: [NODE_DEV, NODE_PROD],
@@ -599,7 +627,7 @@ const bundles = [
     global: 'ReactServerDOMServer',
     minifyWithProdErrorCodes: false,
     wrapWithModuleBoundaries: false,
-    externals: ['react', 'util', 'async_hooks', 'react-dom'],
+    externals: ['react'],
   },
 
   /******* React Server DOM Turbopack Client *******/
@@ -656,7 +684,14 @@ const bundles = [
     global: 'ReactServerDOMServer',
     minifyWithProdErrorCodes: false,
     wrapWithModuleBoundaries: false,
-    externals: ['react', 'util', 'async_hooks', 'react-dom'],
+    externals: [
+      'react',
+      'react-dom',
+      'async_hooks',
+      'crypto',
+      'stream',
+      'util',
+    ],
   },
   {
     bundleTypes: [NODE_DEV, NODE_PROD],
@@ -667,7 +702,7 @@ const bundles = [
     global: 'ReactServerDOMServer',
     minifyWithProdErrorCodes: false,
     wrapWithModuleBoundaries: false,
-    externals: ['react', 'util', 'async_hooks', 'react-dom'],
+    externals: ['react', 'react-dom'],
   },
 
   /******* React Server DOM Parcel Client *******/
@@ -711,7 +746,14 @@ const bundles = [
     condition: 'react-server',
     minifyWithProdErrorCodes: false,
     wrapWithModuleBoundaries: false,
-    externals: ['react', 'util', 'crypto', 'async_hooks', 'react-dom'],
+    externals: [
+      'react',
+      'react-dom',
+      'async_hooks',
+      'crypto',
+      'stream',
+      'util',
+    ],
   },
 
   /******* React Server DOM ESM Client *******/
@@ -729,7 +771,7 @@ const bundles = [
     entry: 'react-server-dom-esm/client.node',
     minifyWithProdErrorCodes: false,
     wrapWithModuleBoundaries: false,
-    externals: ['react', 'react-dom', 'util', 'crypto'],
+    externals: ['react', 'react-dom', 'util'],
   },
 
   /******* React Server DOM ESM Node.js Loader *******/
@@ -1215,6 +1257,8 @@ const bundles = [
       'hermes-parser',
       'zod',
       'zod-validation-error',
+      'crypto',
+      'util',
     ],
     tsconfig: './packages/eslint-plugin-react-hooks/tsconfig.json',
     prebuild: `mkdir -p ./compiler/packages/babel-plugin-react-compiler/dist && echo "module.exports = require('../src/index.ts');" > ./compiler/packages/babel-plugin-react-compiler/dist/index.js`,