fix(cli): use build plugin to redirect morphsdk ESM barrel to CJS

The createRequire approach from #10955 did not prevent the Bun ESM
splitter from generating invalid output. Bun 1.3.14 with
`conditions: ["browser"]` resolves @morphllm/morphsdk via the "import"
condition (pre-split ESM barrel) even inside createRequire() calls,
pulling in 52 chunk-*.js files that cause:

  SyntaxError: Exported binding 'G9' needs to refer to a top-level declared variable.

Fix: add a morphsdkCjsPlugin in script/build.ts using Bun's onResolve
API to redirect the module specifier to the absolute path of client.cjs
before the ESM splitter is invoked. client.cjs is a self-contained
~2300-line CJS bundle with no chunk-*.js imports.

morphsdk.ts is simplified to a plain re-export — the CJS redirection
happens at build time, no source-level workaround needed.
This commit is contained in:
Catriel Müller
2026-06-05 15:52:14 -03:00
parent d9cbaa31e6
commit bd1e3e4a14
2 changed files with 33 additions and 32 deletions
+15 -1
View File
@@ -287,10 +287,24 @@ for (const item of targets) {
const bunfsRoot = item.os === "win32" ? "B:/~BUN/root/" : "/$bunfs/root/"
const workerRelativePath = path.relative(dir, parserWorker).replaceAll("\\", "/")
// kilocode_change start - redirect @morphllm/morphsdk ESM barrel to self-contained CJS bundle
// Bun 1.3.14 (with conditions:["browser"]) resolves via the "import" condition, pulling in the
// pre-split ESM barrel (client.js) whose 52 chunk-*.js side-imports make the ESM splitter emit
// invalid minified output: SyntaxError: Exported binding 'G9' needs to refer to a top-level...
// Redirecting onResolve to client.cjs (2300-line self-contained CJS bundle) bypasses the splitter.
const morphsdkCjsPlugin: import("bun").BunPlugin = {
name: "morphsdk-cjs",
setup(build) {
build.onResolve({ filter: /^@morphllm\/morphsdk\/tools\/warp-grep\/client$/ }, () => ({
path: require.resolve("@morphllm/morphsdk/dist/tools/warp_grep/client.cjs"),
}))
},
}
// kilocode_change end
await Bun.build({
conditions: ["browser"],
tsconfig: "./tsconfig.json",
plugins: [plugin], // kilocode_change
plugins: [plugin, morphsdkCjsPlugin], // kilocode_change
// kilocode_change start - skip sourcemaps for release builds (each .js.map adds ~50 MB per target → ~600 MB total)
sourcemap: Script.release ? "none" : "external",
// kilocode_change end
@@ -1,42 +1,29 @@
import { createRequire } from "module"
// Force Bun to load @morphllm/morphsdk/tools/warp-grep/client via its self-contained
// CJS bundle instead of the pre-split ESM barrel.
// kilocode_change - new file
// Re-exports from @morphllm/morphsdk/tools/warp-grep/client.
//
// WHY THIS EXISTS
// ---------------
// @morphllm/morphsdk ships a pre-split ESM distribution:
// dist/tools/warp_grep/client.js (805 bytes, barrel)
// └─ imports from ../../chunk-P7G3CJB2.js
// └─ side-effects ../../chunk-63VHBANJ.js ... (12 more chunks, 52 total)
//
// When Bun bundles the CLI with `splitting: true + minify: true`, it merges
// these external pre-split chunks into its own chunk graph. Bun 1.3.14 intermittently
// generates invalid minified ESM in that process:
// WHY THIS INDIRECTION EXISTS
// ----------------------------
// @morphllm/morphsdk ships a pre-split ESM distribution for this path:
// dist/tools/warp_grep/client.js (805-byte barrel)
// └─ imports from ../../chunk-P7G3CJB2.js ... (52 total pre-split chunks)
//
// Bun 1.3.14 bundling with `conditions: ["browser"]` resolves via the "import" condition
// (ESM barrel) even inside createRequire() calls. When its ESM splitter merges those
// external pre-split chunks into the bundle, it generates invalid minified output:
// SyntaxError: Exported binding 'G9' needs to refer to a top-level declared variable.
//
// The error is non-deterministic (Bun's parallel bundler uses different orderings
// per run), so the build sometimes succeeds and sometimes fails on Windows x64.
//
// The CJS bundle (client.cjs, ~2300 lines) is fully self-contained with no external
// chunk imports. `createRequire` lets Bun inline the CJS module directly without
// running it through the ESM splitter.
// FIX: script/build.ts adds a morphsdkCjsPlugin (onResolve) that redirects this module
// specifier to client.cjs — a fully self-contained CJS bundle (~2300 lines, no chunk-*.js
// imports). The plugin runs at bundle time before the ESM splitter is invoked.
//
// HOW TO DETECT THIS FOR FUTURE DEPS
// -----------------------------------
// If a new dependency causes `SyntaxError: Exported binding '...' needs to refer to
// a top-level declared variable` in release builds, check whether its ESM entry point
// is a barrel that re-imports from internal `chunk-*.js` files:
// ------------------------------------
// If a new dependency causes the SyntaxError above in release builds, check whether its
// ESM entry point is a barrel that re-imports from internal `chunk-*.js` files:
//
// head -5 node_modules/<pkg>/dist/index.js
// → imports { ... } from "./chunk-XYZ123.js" ← pre-split ESM
//
// If so, add a CJS bridge here and re-export from it instead of importing the package
// directly. Always verify there is a `.cjs` (or CJS `main`) alternative.
const req = createRequire(import.meta.url)
// If so, add a matching onResolve redirect to the CJS counterpart in build.ts.
// Type-cast via the package's own .d.ts so callers get full type safety.
const mod = req("@morphllm/morphsdk/tools/warp-grep/client") as typeof import("@morphllm/morphsdk/tools/warp-grep/client")
export const WarpGrepClient = mod.WarpGrepClient
export { WarpGrepClient } from "@morphllm/morphsdk/tools/warp-grep/client"