oven-sh / bun

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one
https://bun.sh
Other
73.42k stars 2.7k forks source link

bundler does not simplify cross-file inlined enum values #14022

Open Atulin opened 1 week ago

Atulin commented 1 week ago

What version of Bun is running?

1.1.28+cf4e9cb69

What platform is your computer?

Microsoft Windows NT 10.0.19045.0 x64

What steps can reproduce the bug?

Build the following code:

Show code ```ts enum Action { bold = "bold", italic = "italic", underline = "underline", spoiler = "spoiler", link = "link", } interface PrefixSuffix { prefix: string; suffix: string; } const tpl = ` `; const map: Record = { bold: { prefix: "**", suffix: "**" }, italic: { prefix: "*", suffix: "*" }, underline: { prefix: "_", suffix: "_" }, spoiler: { prefix: "||", suffix: "||" }, link: { prefix: "[", suffix: "]()" }, }; const areas = [...document.querySelectorAll("[data-md=true]")] as (HTMLTextAreaElement | HTMLInputElement)[]; for (const area of areas) { const vDom = new DOMParser().parseFromString(tpl, "text/html").body.childNodes[0] as HTMLElement; for (const btn of [...vDom.querySelectorAll("button.btn[data-action]")] as HTMLElement[]) { const action: Action = Action[btn.dataset.action]; btn.addEventListener("click", (_) => { const { prefix, suffix } = map[action]; const start = area.selectionStart; const end = area.selectionEnd; const text = area.value.substring(start, end); area.setRangeText(`${prefix}${text}${suffix}`, start, end, "preserve"); area.selectionStart = area.selectionEnd = end + prefix.length; area.focus(); }); } area.before(vDom); } ```

using the following settings:

const result = await Bun.build({
    entrypoints: files,
    outdir: dest,
    root: source,
    minify: true,
    sourcemap: "linked",
    splitting: false,
});

What is the expected behavior?

With the following settings:

await esbuild.build({
    outdir: '.',
    minify: true,
    sourcemap: true,
    tsconfig: `${roots.js}/tsconfig.json`,
    bundle: true,
}),

ESBuild outputs the following code:

Show code ```js (()=>{var l=(n=>(n.bold="bold",n.italic="italic",n.underline="underline",n.spoiler="spoiler",n.link="link",n))(l||{}),d=` `,f=[...document.querySelectorAll("[data-md=true]")];for(let t of f){let i=new DOMParser().parseFromString(d,"text/html").body.childNodes[0];for(let o of[...i.querySelectorAll("button.btn[data-action]")])o.addEventListener("click",b=>{let c=l[o.dataset.action],n={bold:{prefix:"**",suffix:"**"},italic:{prefix:"*",suffix:"*"},underline:{prefix:"_",suffix:"_"},spoiler:{prefix:"||",suffix:"||"},link:{prefix:"[",suffix:"]()"}},{prefix:a,suffix:r}=n[c],s=t.selectionStart,e=t.selectionEnd,u=t.value.substring(s,e);t.setRangeText(`${a}${u}${r}`,s,e,"preserve"),t.selectionStart=t.selectionEnd=e+a.length,t.focus()});t.before(i)}})(); //# sourceMappingURL=markdown-toolbar.js.map ```

and, as you can see, the enum values are inlined into the template string just fine.

What do you see instead?

Bun outputs the following:

Show code ```js var s;((n)=>{n.bold="bold";n.italic="italic";n.underline="underline";n.spoiler="spoiler";n.link="link"})(s||={});var u=` `,d={bold:{prefix:"**",suffix:"**"},italic:{prefix:"*",suffix:"*"},underline:{prefix:"_",suffix:"_"},spoiler:{prefix:"||",suffix:"||"},link:{prefix:"[",suffix:"]()"}},f=[...document.querySelectorAll("[data-md=true]")];for(let t of f){const i=new DOMParser().parseFromString(u,"text/html").body.childNodes[0];for(let o of[...i.querySelectorAll("button.btn[data-action]")]){const l=s[o.dataset.action];o.addEventListener("click",(b)=>{const{prefix:n,suffix:c}=d[l],a=t.selectionStart,e=t.selectionEnd,r=t.value.substring(a,e);t.setRangeText(`${n}${r}${c}`,a,e,"preserve"),t.selectionStart=t.selectionEnd=e+n.length,t.focus()})}t.before(i)} //# debugId=C1CF57C826E1467164756E2164756E21 //# sourceMappingURL=markdown-toolbar.js.map ```

where the enum values are embedded in a non-optimal way

Additional information

tl;dr of the issue:

ESbuild produces

`foo bar baz`

while Bun produces

`foo ${"bar"} baz`

at the same time, trying to create a simple reproduction always results with Bun producing the same code, for example trying to build

enum Foo {
    bar = "bar",
}
const s = `Hello ${Foo.bar}`;
console.log(s);

produces

console.log("Hello bar");

in either case, ESBuild and Bun.

paperdave commented 1 week ago

i remember when implementing enum inlining i scoped out this feature. it is "late constant folding", which is performed right at the end of the bundle.

equivilent of porting this code from esbuild

image

the reason this does not reproduce in a single file is because the constant folding does happen there. the reason it needs extra work for cross file inlining is because each file is processed individually, in parallel; so there is no way to communicate across files until all are done.