Skip to content

Commit

Permalink
feat: basic code splitting (#97)
Browse files Browse the repository at this point in the history
* wip: basic code splitting

* Fix tests

* Adjust location of `chunk_graph.rs`

* Fix import from source

* snapshots

* skip rollup test

* render chunk exports

* Update status

* print test status in ci

* enable more test
  • Loading branch information
hyf0 committed Oct 26, 2023
1 parent 901fbcf commit 395abb8
Show file tree
Hide file tree
Showing 32 changed files with 267 additions and 28 deletions.
108 changes: 102 additions & 6 deletions crates/rolldown/src/bundler/bundle/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use std::hash::BuildHasherDefault;
use std::{borrow::Cow, hash::BuildHasherDefault};

use super::asset::Asset;
use crate::bundler::{
bitset::BitSet,
chunk::{chunk::Chunk, chunk_graph::ChunkGraph, ChunkId, ChunksVec},
chunk::{
chunk::{Chunk, CrossChunkImportItem},
ChunkId, ChunksVec,
},
chunk_graph::ChunkGraph,
graph::graph::Graph,
module::module::Module,
options::{
Expand All @@ -12,9 +16,9 @@ use crate::bundler::{
},
};
use anyhow::Ok;
use index_vec::IndexVec;
use rolldown_common::{ImportKind, ModuleId};
use rustc_hash::FxHashMap;
use index_vec::{index_vec, IndexVec};
use rolldown_common::{ImportKind, ModuleId, SymbolRef};
use rustc_hash::{FxHashMap, FxHashSet};

pub struct Bundle<'a> {
graph: &'a mut Graph,
Expand All @@ -38,7 +42,7 @@ impl<'a> Bundle<'a> {
module_to_bits[module_id].set_bit(entry_index);
let Module::Normal(module) = &self.graph.modules[module_id] else { return };
module.import_records.iter().for_each(|rec| {
// Module imported dynamically will be considered as a entry,
// Module imported dynamically will be considered as an entry,
// so we don't need to include it in this chunk
if rec.kind != ImportKind::DynamicImport {
self.determine_reachable_modules_for_entry(
Expand All @@ -50,6 +54,96 @@ impl<'a> Bundle<'a> {
});
}

fn compute_cross_chunk_links(&mut self, chunk_graph: &mut ChunkGraph) {
// Determine which symbols belong to which chunk
let mut chunk_meta_imports_vec =
index_vec![FxHashSet::<SymbolRef>::default(); chunk_graph.chunks.len()];
let mut chunk_meta_exports_vec =
index_vec![FxHashSet::<SymbolRef>::default(); chunk_graph.chunks.len()];
for (chunk_id, chunk) in chunk_graph.chunks.iter_enumerated() {
let chunk_meta_imports = &mut chunk_meta_imports_vec[chunk_id];

for module_id in chunk.modules.iter().copied() {
match &self.graph.modules[module_id] {
Module::Normal(module) => {
let linking_info = &self.graph.linker_modules[module_id];

for stmt_info in module.stmt_infos.iter().chain(linking_info.facade_stmt_infos.iter()) {
for declared in &stmt_info.declared_symbols {
self.graph.symbols.get_mut(*declared).chunk_id = Some(chunk_id);
}

for referenced in &stmt_info.referenced_symbols {
let canonical_ref = self.graph.symbols.get_canonical_ref(*referenced);
chunk_meta_imports.insert(canonical_ref);
}
}
}
Module::External(_) => {
// TODO: process external module
}
}
}
}

for (chunk_id, chunk) in chunk_graph.chunks.iter_mut_enumerated() {
let chunk_meta_imports = &chunk_meta_imports_vec[chunk_id];
for import_ref in chunk_meta_imports.iter().copied() {
let importee_chunk_id = self.graph.symbols.get(import_ref).chunk_id.unwrap();
if chunk_id != importee_chunk_id {
chunk
.imports_from_other_chunks
.entry(importee_chunk_id)
.or_default()
.push(CrossChunkImportItem { import_ref, export_alias: None });
chunk_meta_exports_vec[importee_chunk_id].insert(import_ref);
}
}

if chunk.entry_module.is_none() {
continue;
}
// If this is an entry point, make sure we import all chunks belonging to
// this entry point, even if there are no imports. We need to make sure
// these chunks are evaluated for their side effects too.
// TODO: ensure chunks are evaluated for their side effects too.
}
// Generate cross-chunk exports. These must be computed before cross-chunk
// imports because of export alias renaming, which must consider all export
// aliases simultaneously to avoid collisions.
let mut name_count = FxHashMap::default();
for (chunk_id, chunk) in chunk_graph.chunks.iter_mut_enumerated() {
for export in chunk_meta_exports_vec[chunk_id].iter().copied() {
let original_name = self.graph.symbols.get_original_name(export);
let count = name_count.entry(Cow::Borrowed(original_name)).or_insert(0u32);
let alias = if *count == 0 {
original_name.clone()
} else {
format!("{original_name}${count}").into()
};
chunk.exports_to_other_chunks.insert(export, alias.clone());
*count += 1;
}
}
for chunk_id in chunk_graph.chunks.indices() {
for (importee_chunk_id, import_items) in
&chunk_graph.chunks[chunk_id].imports_from_other_chunks
{
for item in import_items {
if let Some(alias) =
chunk_graph.chunks[*importee_chunk_id].exports_to_other_chunks.get(&item.import_ref)
{
// safety: no other mutable reference to `item` exists
unsafe {
let item = (item as *const CrossChunkImportItem).cast_mut();
(*item).export_alias = Some(alias.clone());
}
}
}
}
}
}

fn generate_chunks(&self) -> ChunkGraph {
let entries_len: u32 = self.graph.entries.len().try_into().unwrap();

Expand Down Expand Up @@ -136,6 +230,8 @@ impl<'a> Bundle<'a> {
}
});

self.compute_cross_chunk_links(&mut chunk_graph);

let assets = chunk_graph
.chunks
.iter()
Expand Down
27 changes: 15 additions & 12 deletions crates/rolldown/src/bundler/chunk/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use string_wizard::{Joiner, JoinerOptions};

use crate::bundler::{
bitset::BitSet,
chunk_graph::ChunkGraph,
graph::{
graph::Graph,
linker::LinkerModuleVec,
Expand All @@ -16,7 +17,13 @@ use crate::bundler::{
},
};

use super::{chunk_graph::ChunkGraph, ChunkId};
use super::ChunkId;

#[derive(Debug)]
pub struct CrossChunkImportItem {
pub export_alias: Option<Atom>,
pub import_ref: SymbolRef,
}

#[derive(Debug, Default)]
pub struct Chunk {
Expand All @@ -27,6 +34,8 @@ pub struct Chunk {
pub canonical_names: FxHashMap<SymbolRef, Atom>,
pub exports_str: Option<String>,
pub bits: BitSet,
pub imports_from_other_chunks: FxHashMap<ChunkId, Vec<CrossChunkImportItem>>,
pub exports_to_other_chunks: FxHashMap<SymbolRef, Atom>,
}

impl Chunk {
Expand Down Expand Up @@ -96,6 +105,7 @@ impl Chunk {
pub fn render(&self, graph: &Graph, chunk_graph: &ChunkGraph) -> anyhow::Result<String> {
use rayon::prelude::*;
let mut joiner = Joiner::with_options(JoinerOptions { separator: Some("\n".to_string()) });
joiner.append(self.render_imports_for_esm(graph, chunk_graph));
self
.modules
.par_iter()
Expand All @@ -109,21 +119,14 @@ impl Chunk {
.for_each(|item| {
joiner.append(item);
});

if let Some(exports) = self.render_exports_for_esm(graph) {
joiner.append(exports);
}
if let Some(exports) = self.exports_str.clone() {
joiner.append_raw(exports);
}

Ok(joiner.join())
}
}

#[derive(Debug, Clone)]
pub struct ImportChunkMeta {
pub chunk_id: ChunkId,
// pub symbols: usize,
}

#[derive(Debug, Clone, Default)]
pub struct ChunkMeta {
pub imports: Vec<ImportChunkMeta>,
}
3 changes: 2 additions & 1 deletion crates/rolldown/src/bundler/chunk/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#[allow(clippy::module_inception)]
pub mod chunk;
pub mod chunk_graph;
mod de_conflict;
mod render_chunk_exports;
mod render_chunk_imports;

use index_vec::IndexVec;

Expand Down
30 changes: 30 additions & 0 deletions crates/rolldown/src/bundler/chunk/render_chunk_exports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use string_wizard::MagicString;

use crate::bundler::graph::graph::Graph;

use super::chunk::Chunk;

impl Chunk {
pub fn render_exports_for_esm(&self, graph: &Graph) -> Option<MagicString<'static>> {
if self.exports_to_other_chunks.is_empty() {
return None;
}
let mut s = MagicString::new("");
let mut export_items = self
.exports_to_other_chunks
.iter()
.map(|(export_ref, alias)| {
let canonical_ref = graph.symbols.par_get_canonical_ref(*export_ref);
let canonical_name = &self.canonical_names[&canonical_ref];
if canonical_name == alias {
format!("{canonical_name}")
} else {
format!("{canonical_name} as {alias}")
}
})
.collect::<Vec<_>>();
export_items.sort();
s.append(format!("export {{ {} }};", export_items.join(", "),));
Some(s)
}
}
41 changes: 41 additions & 0 deletions crates/rolldown/src/bundler/chunk/render_chunk_imports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use string_wizard::MagicString;

use crate::bundler::{chunk_graph::ChunkGraph, graph::graph::Graph};

use super::chunk::Chunk;

impl Chunk {
pub fn render_imports_for_esm(
&self,
graph: &Graph,
chunk_graph: &ChunkGraph,
) -> MagicString<'static> {
let mut s = MagicString::new("");
self.imports_from_other_chunks.iter().for_each(|(chunk_id, items)| {
let chunk = &chunk_graph.chunks[*chunk_id];
let mut import_items = items
.iter()
.map(|item| {
let imported = chunk
.canonical_names
.get(&graph.symbols.par_get_canonical_ref(item.import_ref))
.cloned()
.unwrap();
let alias = item.export_alias.as_ref().unwrap();
if imported == alias {
format!("{imported}")
} else {
format!("{imported} as {alias}")
}
})
.collect::<Vec<_>>();
import_items.sort();
s.append(format!(
"import {{ {} }} from \"./{}\";",
import_items.join(", "),
chunk.file_name.as_ref().unwrap()
));
});
s
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use index_vec::IndexVec;
use rolldown_common::ModuleId;

use super::{ChunkId, ChunksVec};
use super::chunk::{ChunkId, ChunksVec};

#[derive(Debug)]
pub struct ChunkGraph {
Expand Down
8 changes: 6 additions & 2 deletions crates/rolldown/src/bundler/graph/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ use rolldown_common::{ModuleId, SymbolRef};
use rolldown_utils::reserved_word::is_reserved_word;
use rustc_hash::FxHashMap;

use crate::bundler::chunk::ChunkId;

#[derive(Debug)]
pub struct Symbol {
pub name: Atom,
/// The symbol that this symbol is linked to.
pub link: Option<SymbolRef>,
/// The chunk that this symbol is defined in.
pub chunk_id: Option<ChunkId>,
}

#[derive(Debug, Default)]
Expand Down Expand Up @@ -51,15 +55,15 @@ impl Symbols {
.into_iter()
.map(|table| {
reference_table.push(table.references);
table.names.into_iter().map(|name| Symbol { name, link: None }).collect()
table.names.into_iter().map(|name| Symbol { name, link: None, chunk_id: None }).collect()
})
.collect();

Self { inner, references_table: reference_table }
}

pub fn create_symbol(&mut self, owner: ModuleId, name: Atom) -> SymbolRef {
let symbol_id = self.inner[owner].push(Symbol { name, link: None });
let symbol_id = self.inner[owner].push(Symbol { name, link: None, chunk_id: None });
SymbolRef { owner, symbol: symbol_id }
}

Expand Down
1 change: 1 addition & 0 deletions crates/rolldown/src/bundler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod bitset;
pub mod bundle;
mod chunk_graph;
mod graph;
mod module;
pub mod options;
Expand Down
2 changes: 1 addition & 1 deletion crates/rolldown/src/bundler/module/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rustc_hash::FxHashMap;
use string_wizard::MagicString;

use crate::bundler::{
chunk::chunk_graph::ChunkGraph,
chunk_graph::ChunkGraph,
graph::{graph::Graph, linker::LinkerModule},
};

Expand Down
2 changes: 1 addition & 1 deletion crates/rolldown/src/bundler/visitors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
use string_wizard::{MagicString, UpdateOptions};

use super::{
chunk::chunk_graph::ChunkGraph,
chunk_graph::ChunkGraph,
graph::{graph::Graph, linker::LinkerModule, symbols::get_symbol_final_name},
module::{module::Module, NormalModule},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
source: crates/rolldown/tests/common/case.rs
expression: content
input_file: crates/rolldown/tests/esbuild/splitting/assign-to-local
---
# 2.js

```js
// shared.js
let foo
function setFoo(value) {
foo = value
}
export { foo, setFoo };
```
# a.js

```js
import { foo, setFoo } from "./2.js";
// a.js
setFoo(123)
console.log(foo)
```
# b.js

```js
import { foo } from "./2.js";
// b.js
console.log(foo)
```
Loading

0 comments on commit 395abb8

Please sign in to comment.