Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
1213676bc2 fix(bundler): skip barrel optimization for entry points with sideEffects
The barrel imports optimization incorrectly applied to entry point files
when the project's package.json had a `sideEffects` field. Since entry
points have no `requested_exports` (nothing imports them within the
bundle), the optimization marked all their imports as unused, producing
empty output with dangling export references.

Skip barrel optimization for entry points so all their exports are
preserved as the public API of the bundle.

Closes #27709

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-03 08:57:30 +00:00
2 changed files with 100 additions and 0 deletions

View File

@@ -53,6 +53,14 @@ fn applyBarrelOptimizationImpl(this: *BundleV2, parse_result: *ParseTask.Result)
if (ast.import_records.len == 0) return;
if (ast.named_exports.count() == 0 and ast.export_star_import_records.len == 0) return;
// Entry points need all their exports preserved — they are the public API
// of the bundle. Since nothing imports from them within the bundle,
// requested_exports will be empty, causing barrel optimization to
// incorrectly mark all their imports as unused.
for (this.graph.entry_points.items) |ep| {
if (ep.get() == source_index) return;
}
const named_exports = ast.named_exports;
const named_imports = ast.named_imports;

View File

@@ -0,0 +1,92 @@
import { expect, test } from "bun:test";
import { tempDir } from "harness";
// https://github.com/oven-sh/bun/issues/27709
// sideEffects field in project's own package.json should not cause the bundler
// to tree-shake away entry point re-exports.
test("sideEffects array does not drop entry point re-exports", async () => {
using dir = tempDir("issue-27709", {
"a.ts": `
import { createContext, createElement, useContext } from "react"
const Ctx = createContext(null)
function Root({ children }) {
return createElement(Ctx.Provider, { value: {} }, createElement("div", null, children))
}
function Trigger({ children }) {
const ctx = useContext(Ctx)
return createElement("button", null, children)
}
export const A = { Root, Trigger }
`,
"b.ts": `
import { createContext, createElement, useContext } from "react"
const Ctx = createContext(null)
function Root({ children }) {
return createElement(Ctx.Provider, { value: {} }, createElement("div", null, children))
}
function Trigger({ children }) {
const ctx = useContext(Ctx)
return createElement("button", null, children)
}
export const B = { Root, Trigger }
`,
"index.ts": `
export { A } from "./a.js"
export { B } from "./b.js"
`,
"package.json": JSON.stringify({
name: "test",
type: "module",
sideEffects: ["./dist/index.js"],
}),
});
const result = await Bun.build({
entrypoints: [`${dir}/index.ts`],
outdir: `${dir}/out`,
format: "esm",
external: ["react"],
minify: true,
});
expect(result.success).toBe(true);
expect(result.outputs.length).toBeGreaterThan(0);
const output = await result.outputs[0].text();
// The output must contain the actual function bodies, not just dangling references
expect(output).toContain("createContext");
expect(output).toContain("createElement");
});
test("sideEffects false does not drop entry point re-exports", async () => {
using dir = tempDir("issue-27709-false", {
"a.ts": `export const A = "alpha";`,
"b.ts": `export const B = "beta";`,
"index.ts": `
export { A } from "./a.js"
export { B } from "./b.js"
`,
"package.json": JSON.stringify({
name: "test",
type: "module",
sideEffects: false,
}),
});
const result = await Bun.build({
entrypoints: [`${dir}/index.ts`],
outdir: `${dir}/out`,
format: "esm",
minify: true,
});
expect(result.success).toBe(true);
const output = await result.outputs[0].text();
// Both exported values must be present in the output
expect(output).toContain("alpha");
expect(output).toContain("beta");
});