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
+2
View File
@@ -0,0 +1,2 @@
import * as esbuild from "esbuild";
export default function reactVendorChunkPlugin(): esbuild.Plugin;
+97
View File
@@ -0,0 +1,97 @@
// 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 = {
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) {
const filename = specifier.replace(/\//g, "_") + ".js";
return `${VENDOR_BASE}/${filename}`;
}
function vendorOutfile(specifier) {
const filename = specifier.replace(/\//g, "_") + ".js";
return path.join(BUNEXT_VENDOR_DIR, filename);
}
export default function reactVendorChunkPlugin() {
let vendorReady;
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) {
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,
platform: "browser",
define: parentOptions.define,
mainFields: ["module", "main"],
conditions: ["import", "default"],
})));
}
+3
View File
@@ -0,0 +1,3 @@
import type { Plugin } from "esbuild";
declare const reactAliasPlugin: Plugin;
export default reactAliasPlugin;
+22
View File
@@ -0,0 +1,22 @@
import path from "path";
import grabDirNames from "../../../utils/grab-dir-names";
const { ROOT_DIR } = grabDirNames();
const reactAliasPlugin = {
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;
+12
View File
@@ -11,6 +11,18 @@ export default function virtualFilesPlugin({ entryToPage }) {
namespace: "hydration-virtual",
};
});
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) => {
const target = entryToPage.get(args.path);
if (!target?.tsx)