Bugfix. Extract react imports from main bundler to vendor static files

This commit is contained in:
2026-04-08 05:39:11 +01:00
parent c19b7c9607
commit 4b3a4dbc77
38 changed files with 825 additions and 253 deletions
@@ -0,0 +1,117 @@
// plugins/react-vendor-chunk-plugin.ts
import * as esbuild from "esbuild";
import path from "path";
import grabDirNames from "../../../utils/grab-dir-names";
const { BUNEXT_VENDOR_DIR } = grabDirNames();
const REACT_MODULES = new Set([
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
"react/jsx-dev-runtime",
]);
const VENDOR_BASE = "/.bunext/public/vendor";
const REACT_ENTRIES: Record<string, string> = {
react: `
import React from "react";
export const {
Children, Component, Fragment, Profiler, PureComponent, StrictMode,
Suspense, cloneElement, createContext, createElement, createFactory,
createRef, forwardRef, isValidElement, lazy, memo, startTransition,
useCallback, useContext, useDebugValue, useDeferredValue, useEffect,
useId, useImperativeHandle, useInsertionEffect, useLayoutEffect,
useMemo, useReducer, useRef, useState, useSyncExternalStore,
useTransition, version,
} = React;
export default React;
`,
"react-dom": `
import ReactDOM from "react-dom";
export const {
createPortal, flushSync, findDOMNode, hydrate, render,
unmountComponentAtNode, version,
} = ReactDOM;
export default ReactDOM;
`,
"react-dom/client": `
import ReactDOMClient from "react-dom/client";
export const { createRoot, hydrateRoot } = ReactDOMClient;
export default ReactDOMClient;
`,
"react/jsx-runtime": `
import JSXRuntime from "react/jsx-runtime";
export const { jsx, jsxs, Fragment } = JSXRuntime;
export default JSXRuntime;
`,
"react/jsx-dev-runtime": `
import JSXDevRuntime from "react/jsx-dev-runtime";
export const { jsxDEV, Fragment } = JSXDevRuntime;
export default JSXDevRuntime;
`,
};
// Map bare specifier -> browser path
function vendorPath(specifier: string): string {
const filename = specifier.replace(/\//g, "_") + ".js";
return `${VENDOR_BASE}/${filename}`;
}
function vendorOutfile(specifier: string): string {
const filename = specifier.replace(/\//g, "_") + ".js";
return path.join(BUNEXT_VENDOR_DIR, filename);
}
export default function reactVendorChunkPlugin(): esbuild.Plugin {
let vendorReady: Promise<void>;
return {
name: "react-vendor-chunk",
setup(build) {
vendorReady ??= buildAllVendorChunks(build.initialOptions);
build.onResolve(
{ filter: /^react(-dom)?(\/.*)?$/ },
async (args) => {
const bare = args.path.replace(/\/index(\.m?js)?$/, "");
if (!(bare in REACT_ENTRIES)) return;
await vendorReady;
return {
path: vendorPath(bare),
external: true,
};
},
);
},
};
}
async function buildAllVendorChunks(
parentOptions: esbuild.BuildOptions,
): Promise<void> {
await Promise.all(
Object.entries(REACT_ENTRIES).map(([specifier, contents]) =>
esbuild.build({
stdin: {
contents,
resolveDir: process.cwd(),
loader: "tsx",
},
outfile: vendorOutfile(specifier),
bundle: true,
minify: parentOptions.minify,
format: "esm",
target: parentOptions.target as string,
platform: "browser",
define: parentOptions.define,
mainFields: ["module", "main"],
conditions: ["import", "default"],
}),
),
);
}
@@ -0,0 +1,30 @@
import type { Plugin } from "esbuild";
import path from "path";
import grabDirNames from "../../../utils/grab-dir-names";
const { ROOT_DIR } = grabDirNames();
const reactAliasPlugin: Plugin = {
name: "react-alias",
setup(build) {
const reactPath = path.join(ROOT_DIR, "node_modules");
build.onResolve({ filter: /^react$/ }, () => ({
path: path.join(reactPath, "react", "index.js"),
}));
build.onResolve({ filter: /^react-dom$/ }, () => ({
path: path.join(reactPath, "react-dom", "index.js"),
}));
build.onResolve({ filter: /^react\/jsx-runtime$/ }, () => ({
path: path.join(reactPath, "react", "jsx-runtime.js"),
}));
build.onResolve({ filter: /^react\/jsx-dev-runtime$/ }, () => ({
path: path.join(reactPath, "react", "jsx-dev-runtime.js"),
}));
},
};
export default reactAliasPlugin;
@@ -24,6 +24,22 @@ export default function virtualFilesPlugin({ entryToPage }: Params) {
};
});
build.onResolve(
{ filter: /node_modules\/react(-dom)?/ },
(args) => ({
path: args.path.includes("react-dom")
? args.path.includes("client")
? "react-dom/client"
: "react-dom"
: args.path.includes("jsx-dev")
? "react/jsx-dev-runtime"
: args.path.includes("jsx")
? "react/jsx-runtime"
: "react",
external: true,
}),
);
build.onLoad(
{ filter: /.*/, namespace: "hydration-virtual" },
(args) => {