Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions src/bundler/linker_context/computeChunks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,10 +615,22 @@ pub fn compute_chunks(this: &mut LinkerContext, unique_key: u64) -> crate::Resul
}
}

let pathname = bun_fs::PathName::init(
output_paths[chunk.entry_point.entry_point_id() as usize].slice(),
);
chunk.template.placeholder.name = pathname.base.to_vec().into_boxed_slice();
let pathname: Option<bun_fs::PathName<'_>> = if chunk.entry_point.is_entry_point() {
let p = bun_fs::PathName::init(
output_paths[chunk.entry_point.entry_point_id() as usize].slice(),
);
chunk.template.placeholder.name = p.base.to_vec().into_boxed_slice();
Some(p)
} else {
chunk.template.placeholder.name = if chunk.entry_bits.count() == 1 {
let ep = chunk.entry_bits.find_first_set().unwrap();
let base = bun_fs::PathName::init(output_paths[ep].slice()).base;
[base, b"-chunk"].concat().into_boxed_slice()
} else {
b"shared".to_vec().into_boxed_slice()
};
None
};
chunk.template.placeholder.ext = chunk.content.ext().to_vec().into_boxed_slice();

if chunk.template.needs(PlaceholderField::Target) {
Expand All @@ -633,7 +645,9 @@ pub fn compute_chunks(this: &mut LinkerContext, unique_key: u64) -> crate::Resul
};
}

if chunk.template.needs(PlaceholderField::Dir) {
if let Some(ref pathname) = pathname
&& chunk.template.needs(PlaceholderField::Dir)
{
// this if check is a specific fix for `bun build hi.ts --external '*'`, without leading `./`
let dir_path: &[u8] = if !pathname.dir.is_empty() {
pathname.dir
Expand Down
20 changes: 6 additions & 14 deletions src/bundler/linker_context/generateChunksInParallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,6 @@ pub fn generate_chunks_in_parallel<const IS_DEV_SERVER: bool>(

let mut entry_naming: Option<&[u8]> = None;
let mut chunk_naming: Option<&[u8]> = None;
let mut asset_naming: Option<&[u8]> = None;

writeln!(&mut msg, "Multiple files share the same output path")?;

Expand All @@ -415,16 +414,13 @@ pub fn generate_chunks_in_parallel<const IS_DEV_SERVER: bool>(
{
writeln!(&mut msg, " {}:", bstr::BStr::new(key))?;
for chunk in dup.sources.iter() {
if chunk.entry_point.is_entry_point() {
if kinds[chunk.entry_point.source_index() as usize]
if chunk.entry_point.is_entry_point()
&& kinds[chunk.entry_point.source_index() as usize]
== EntryPoint::Kind::UserSpecified
{
entry_naming = Some(&chunk.template.data);
} else {
chunk_naming = Some(&chunk.template.data);
}
{
entry_naming = Some(&chunk.template.data);
} else {
asset_naming = Some(&chunk.template.data);
chunk_naming = Some(&chunk.template.data);
}

let source_index = chunk.entry_point.source_index();
Expand All @@ -440,11 +436,7 @@ pub fn generate_chunks_in_parallel<const IS_DEV_SERVER: bool>(

c.log_mut().add_error(None, bun_ast::Loc::EMPTY, msg);

for (name, template) in [
("entry", entry_naming),
("chunk", chunk_naming),
("asset", asset_naming),
] {
for (name, template) in [("entry", entry_naming), ("chunk", chunk_naming)] {
let Some(template) = template else { continue };

let mut text: Vec<u8> = Vec::new();
Expand Down
65 changes: 64 additions & 1 deletion test/bundler/bundler_naming.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe } from "bun:test";
import { describe, expect } from "bun:test";
import { readdirSync } from "node:fs";
import { ESBUILD, itBundled } from "./expectBundled";

describe("bundler", () => {
Expand Down Expand Up @@ -302,6 +303,68 @@ describe("bundler", () => {
},
],
});
// A non-entry split chunk's `[name]` is the owning entry point's basename
// plus `-chunk` when exactly one entry reaches it, or `shared` otherwise,
// never the first-listed entrypoint. Dynamic-import chunks are entry points
// and keep their own source basename.
for (const order of ["MainFirst", "ModnameFirst"] as const) {
itBundled(`naming/ChunkNamePlaceholderSharedChunk${order}`, {
files: {
"/src/main.js": /* js */ `
import { v } from "./sub/modname.js";
import("./lazy.js").then(m => console.log(v, m.z));
`,
"/src/sub/modname.js": /* js */ `
export const v = "V";
`,
"/src/lazy.js": /* js */ `
export const z = "Z";
`,
},
entryPointsRaw:
order === "MainFirst" ? ["./src/main.js", "./src/sub/modname.js"] : ["./src/sub/modname.js", "./src/main.js"],
outdir: "/out",
splitting: true,
chunkNaming: "chunks/[name]-[hash].[ext]",
onAfterBundle(api) {
const chunks = readdirSync(api.outdir + "/chunks").sort();
const lazy = chunks.filter(f => f.startsWith("lazy-"));
const shared = chunks.filter(f => !f.startsWith("lazy-"));
expect(lazy.length).toBe(1);
expect(shared.length).toBeGreaterThan(0);
for (const f of shared) {
expect(f).toMatch(/^shared-[a-z0-9]+\.js$/);
}
},
run: {
file: "/out/main.js",
stdout: "V Z",
},
});
}
itBundled("naming/ChunkNamingCollisionHint", {
backend: "api",
files: {
"/src/main.js": /* js */ `
import { v } from "./sub/modname.js";
import("./lazy.js").then(m => console.log(v, m.z));
`,
"/src/sub/modname.js": `export const v = "V";`,
"/src/lazy.js": `export const z = "Z";`,
},
entryPointsRaw: ["./src/main.js", "./src/sub/modname.js"],
outdir: "/out",
splitting: true,
chunkNaming: "chunks/[name].[ext]",
bundleErrors: {
"<bun>": ["Multiple files share the same output path"],
},
onAfterApiBundle(build) {
const text = build.logs.map(l => l.message).join("\n");
expect(text).toContain("chunk naming is");
expect(text).not.toContain("asset naming is");
},
});
// A non-ASCII ID_Continue basename char is preserved in the generated
// CommonJS wrapper symbol, not replaced per-code-point (nor per-UTF-8-byte,
// which once regressed to `require_caf__utils`).
Expand Down
Loading