Skip to content
Merged
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
4 changes: 3 additions & 1 deletion docs/bundler/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,9 @@ With `splitting`, also fold small side-effect-free chunks into a chunk that more
</Tab>
</Tabs>

Code splitting gives each distinct set of importers its own chunk, then folds chunks that are always loaded together: a module that `index.tsx` imports and that a lazily `import()`ed module also imports lives in `index.js`, and the lazy chunk imports it from there, because the lazy module can only load after `index.tsx` has run. That fold never makes an entrypoint load more code, so it is always on.
Code splitting gives each distinct set of importers its own chunk, then folds chunks that are always loaded together: a module that `index.tsx` imports and that a lazily `import()`ed module also imports lives in the chunk of `index.tsx`, and the lazy chunk imports it from there, because the lazy module can only load after `index.tsx` has run. That fold never makes an entrypoint load more code, so it is always on.

Nothing folds into the chunk of an entrypoint whose name has no `[hash]` (the default `naming`). Whatever loads that file can add a query string to its URL (`index.js?v=123`), and an `import "./index.js"` in another chunk would then run the file a second time. The shared code goes into one hashed chunk next to the entrypoint. Entrypoints that an HTML file loads, and entrypoints with `[hash]` in `naming.entry`, still take the fold.

`minChunkSize` goes further for chunks whose source files add up to fewer than this many bytes and whose modules run nothing at the top level: only declarations, `"sideEffects": false` in their `package.json`, a lazily initialized CommonJS/ESM wrapper, or an import of a CommonJS module (such as `react`) that is already initialized wherever the chunk's code ends up. Such a chunk folds into a chunk loaded by a superset of its importers. Everything it imports must already be loaded whenever that target is, or be side-effect free as well. The extra entrypoints then carry some unused definitions, capped at about 1.5% of what each entrypoint loaded to begin with. No side effect runs earlier than before and nothing lazy becomes eager. Because these rules bound what an entrypoint can end up loading, a large `minChunkSize` is reasonable.

Expand Down
1 change: 1 addition & 0 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3210,6 +3210,7 @@ declare module "bun" {
* that absorbs a folded chunk exports the symbols other chunks import
* from it. Requires `splitting: true`. CLI: `--min-chunk-size`. For browser
* builds, where every chunk is a request, 16384 is a good value.
* Nothing folds into an entry point without `[hash]` in its name.
*
* @default 0 (disabled)
*/
Expand Down
2 changes: 2 additions & 0 deletions src/bundler/LinkerContext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1445,6 +1445,7 @@ pub struct LinkerOptions {
/// See `merge_small_chunks`.
pub(crate) min_chunk_size: u64,
pub(crate) fold_chunks: bool,
pub(crate) entry_naming_has_hash: bool,
pub(crate) module_preload: bool,
pub(crate) source_maps: SourceMapOption,
pub(crate) target: Target,
Expand Down Expand Up @@ -1493,6 +1494,7 @@ impl Default for LinkerOptions {
css_chunking: false,
min_chunk_size: 0,
fold_chunks: true,
entry_naming_has_hash: false,
module_preload: true,
source_maps: SourceMapOption::None,
target: Target::Browser,
Expand Down
4 changes: 4 additions & 0 deletions src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3302,6 +3302,10 @@ pub mod bv2_impl {
crate::options::default_min_chunk_size(this.transpiler.options.target)
});
this.linker.options.fold_chunks = this.transpiler.options.fold_chunks;
this.linker.options.entry_naming_has_hash = crate::options::path_template_needs(
&this.transpiler.options.entry_naming,
crate::options::PlaceholderField::Hash,
);
this.linker.options.module_preload = this.transpiler.options.module_preload;
this.linker.options.source_maps = this.transpiler.options.source_map;
this.linker.options.tree_shaking = this.transpiler.options.tree_shaking;
Expand Down
3 changes: 2 additions & 1 deletion src/bundler/linker_context/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,7 @@ The renamed symbols are then used during final code generation to produce output
- Computes, per `import()` entry point, which other entry points are guaranteed to be loaded already whenever it loads
- Reduces each chunk key (`File.entry_bits`) to its load-condition class by dropping such redundant dynamic entries
- Rewrites the entry bits of files to those of the class's parent chunk (the chunk keyed by the reduced set, else the largest member) before `computeChunks()` groups files
- Leaves an entry point's own chunk alone when the entry point has exports, with `--compile`, and for an entry point without `[hash]` in its name (its host can load it as `entry.js?v=1`, so no chunk may import it)
- Keeps a chunk out of the fold when it can be in the middle of being evaluated while an entry of its class loads (it, or a file that statically imports its way to it, `require()`s a split ES module): the entry's chunk reads the other members then
- With `--min-chunk-size`, additionally folds small chunks with no top-level side effects into a chunk loaded by a superset of their entries when every dependency is already loaded wherever the target is, no static import cycle between chunks results, and every CommonJS/ESM wrapper the moved code initializes at the top level is already initialized by a chunk the target imports

Expand All @@ -762,7 +763,7 @@ The renamed symbols are then used during final code generation to produce output
**Key functions**:

- Walks the import graph in evaluation order (`EntryWalk`): depth first along every `import` statement, also through files that tree shaking dropped, so a file prints after the files it imports
- One walk per entry point, in parallel. A chunk has one owner: the entry point that loads first among the chunk's entry points (`LoadOrder`). The owner's walk places the files of the chunk, so no two walks write the same list. Without code splitting, each chunk is owned by its own entry point
- One walk per entry point, in parallel. A chunk has one owner: the entry point that loads first among the chunk's entry points (`load_rank`). `load_rank` and `EntryWalk` read the same edges (`for_each_edge`). The owner's walk places the files of the chunk, so no two walks write the same list. Without code splitting, each chunk is owned by its own entry point
- Places the parts of a file in runs: a part prints after the files it imports and before the files that the next part imports
- Collects the live parts of each run into part ranges
- Records the other chunks in the order a walk from the chunk's files reaches their first file with side effects (`reached_chunks_in_order`), which orders the chunk's cross-chunk `import` statements
Expand Down
Loading
Loading