Skip to content

Commit

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

* chore: update

* fix: ci

* Use `BitSet`

---------

Co-authored-by: Yunfei He <[email protected]>
  • Loading branch information
underfin and hyf0 authored Oct 6, 2023
1 parent 222c5a3 commit 74a7ccf
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 23 deletions.
72 changes: 72 additions & 0 deletions crates/rolldown/src/bundler/bitset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::fmt::{Debug, Display};

#[derive(Clone, PartialEq, Eq, Default, Hash)]
pub struct BitSet {
entries: Vec<u8>,
}

impl BitSet {
pub fn new(max_bit_count: u32) -> BitSet {
BitSet {
entries: vec![0; ((max_bit_count + 7) / 8) as usize],
}
}

pub fn has_bit(&self, bit: u32) -> bool {
(self.entries[bit as usize / 8] & (1 << (bit & 7))) != 0
}

pub fn set_bit(&mut self, bit: u32) {
self.entries[bit as usize / 8] |= 1 << (bit & 7);
}
}

impl Display for BitSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let bit_string = self
.entries
.iter()
.map(|e| format!("{:08b}", e))
.collect::<Vec<String>>()
.join("_");
f.write_str(&bit_string)
}
}

impl Debug for BitSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let bit_string = self
.entries
.iter()
.map(|e| format!("{:08b}", e))
.collect::<Vec<String>>()
.join("_");
f.debug_tuple("BitSet").field(&bit_string).finish()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn basic() {
let mut bs = BitSet::new(1);
assert_eq!(bs.to_string(), "00000000");
bs.set_bit(0);
bs.set_bit(1);
bs.set_bit(7);
assert_eq!(bs.to_string(), "10000011");

let mut bs = BitSet::new(9);
assert_eq!(bs.to_string(), "00000000_00000000");
bs.set_bit(0);
bs.set_bit(1);
bs.set_bit(7);
assert_eq!(bs.to_string(), "10000011_00000000");
bs.set_bit(8);
assert_eq!(bs.to_string(), "10000011_00000001");
bs.set_bit(15);
assert_eq!(bs.to_string(), "10000011_10000001");
}
}
102 changes: 89 additions & 13 deletions crates/rolldown/src/bundler/bundle/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
use anyhow::Ok;

use super::asset::Asset;
use crate::bundler::{
chunk::{chunk::Chunk, ChunksVec},
bitset::BitSet,
chunk::{
chunk::{Chunk, CrossChunksMeta},
ChunksVec,
},
graph::graph::Graph,
module::module::ModuleFinalizeContext,
module::module::{Module, ModuleFinalizeContext},
options::{
normalized_input_options::NormalizedInputOptions,
normalized_output_options::NormalizedOutputOptions,
},
};
use anyhow::Ok;
use index_vec::IndexVec;
use rolldown_common::ModuleId;
use rustc_hash::FxHashMap;

pub struct Bundle<'a> {
graph: &'a mut Graph,
Expand All @@ -24,22 +30,91 @@ impl<'a> Bundle<'a> {
}
}

pub fn generate_chunks(graph: &Graph) -> ChunksVec {
let mut chunks = ChunksVec::with_capacity(graph.entries.len());
let mut modules = graph.modules.iter().map(|m| m.id()).collect::<Vec<_>>();
modules.sort_by_key(|id| graph.modules[*id].exec_order());
let chunk = Chunk::new(Some("main".to_string()), true, modules);
chunks.push(chunk);
pub fn mark_modules_entry_bit(
&self,
module_id: ModuleId,
index: usize,
modules_entry_bit: &mut IndexVec<ModuleId, BitSet>,
) {
if modules_entry_bit[module_id].has_bit(index as u32) {
return;
}
modules_entry_bit[module_id].set_bit(index as u32);
if let Module::Normal(m) = &self.graph.modules[module_id] {
m.import_records
.iter()
.for_each(|i| self.mark_modules_entry_bit(i.resolved_module, index, modules_entry_bit));
}
}

pub fn generate_chunks(&self) -> ChunksVec {
let mut module_to_bits = index_vec::index_vec![
BitSet::new(self.graph.entries.len().try_into().unwrap());
self.graph.modules.len()
];

let mut chunks = FxHashMap::default();
chunks.shrink_to(self.graph.entries.len());

for (i, (name, _)) in self.graph.entries.iter().enumerate() {
let count: u32 = i as u32;
let mut entry_bits = BitSet::new(self.graph.entries.len() as u32);
entry_bits.set_bit(count);
let c = Chunk::new(name.clone(), true, entry_bits.clone(), vec![]);
chunks.insert(entry_bits, c);
}

self
.graph
.entries
.iter()
.enumerate()
.for_each(|(i, (_, entry))| {
self.mark_modules_entry_bit(*entry, i, &mut module_to_bits);
});

self.graph.modules.iter().for_each(|module| {
let bits = &module_to_bits[module.id()];
if let Some(chunk) = chunks.get_mut(bits) {
chunk.modules.push(module.id());
} else {
// TODO share chunk name
let len = chunks.len();
chunks.insert(
bits.clone(),
Chunk::new(
Some(len.to_string()),
false,
bits.clone(),
vec![module.id()],
),
);
}
});

chunks
.into_values()
.map(|mut chunk| {
chunk
.modules
.sort_by_key(|id| self.graph.modules[*id].exec_order());
chunk
})
.collect::<ChunksVec>()
}

pub fn generate_cross_chunks_meta(&mut self, _chunks: &ChunksVec) -> CrossChunksMeta {
// TODO: cross chunk imports
Default::default()
}

pub fn generate(
&mut self,
input_options: &'a NormalizedInputOptions,
) -> anyhow::Result<Vec<Asset>> {
use rayon::prelude::*;
let mut chunks = Self::generate_chunks(self.graph);

let mut chunks = self.generate_chunks();
let _generate_cross_chunks_meta = self.generate_cross_chunks_meta(&chunks);
chunks
.iter_mut()
.par_bridge()
Expand Down Expand Up @@ -69,7 +144,8 @@ impl<'a> Bundle<'a> {

let assets = chunks
.iter()
.map(|c| {
.enumerate()
.map(|(_chunk_id, c)| {
let content = c.render(self.graph, input_options).unwrap();

Asset {
Expand Down
24 changes: 20 additions & 4 deletions crates/rolldown/src/bundler/chunk/chunk.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use index_vec::IndexVec;
use oxc::span::Atom;
use rolldown_common::{ModuleId, SymbolRef};
use rustc_hash::FxHashMap;
use string_wizard::{Joiner, JoinerOptions};

use crate::bundler::{
bitset::BitSet,
graph::{graph::Graph, symbols::Symbols},
module::{module_id::ModuleVec, render::RenderModuleContext},
options::{
Expand All @@ -12,6 +14,8 @@ use crate::bundler::{
},
};

use super::ChunkId;

#[derive(Debug, Default)]
pub struct Chunk {
pub is_entry: bool,
Expand All @@ -20,13 +24,15 @@ pub struct Chunk {
pub file_name: Option<String>,
pub canonical_names: FxHashMap<SymbolRef, Atom>,
pub exports_str: Option<String>,
pub bits: BitSet,
}

impl Chunk {
pub fn new(name: Option<String>, is_entry: bool, modules: Vec<ModuleId>) -> Self {
pub fn new(name: Option<String>, is_entry: bool, bits: BitSet, modules: Vec<ModuleId>) -> Self {
Self {
name,
is_entry,
bits,
modules,
..Default::default()
}
Expand All @@ -42,9 +48,6 @@ impl Chunk {
)
}

/// - import symbols from other chunks and external modules
// pub fn generate_cross_chunk_links(&mut self) {}

pub fn initialize_exports(&mut self, modules: &mut ModuleVec, symbols: &Symbols) {
let entry = &mut modules[*self.modules.last().unwrap()];

Expand Down Expand Up @@ -113,3 +116,16 @@ impl Chunk {
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>,
}

pub type CrossChunksMeta = IndexVec<ChunkId, ChunkMeta>;
5 changes: 2 additions & 3 deletions crates/rolldown/src/bundler/graph/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::bundler::{
#[derive(Default, Debug)]
pub struct Graph {
pub modules: ModuleVec,
pub entries: Vec<ModuleId>,
pub entries: Vec<(Option<String>, ModuleId)>,
pub sorted_modules: Vec<ModuleId>,
pub symbols: Symbols,
}
Expand All @@ -37,8 +37,7 @@ impl Graph {
let mut stack = self
.entries
.iter()
.copied()
.map(Action::Enter)
.map(|(_, m)| Action::Enter(*m))
.rev()
.collect::<Vec<_>>();

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,3 +1,4 @@
pub mod bitset;
pub mod bundle;
mod graph;
mod module;
Expand Down
13 changes: 10 additions & 3 deletions crates/rolldown/src/bundler/module_loader/module_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ impl<'a> ModuleLoader<'a> {

self.graph.entries = resolved_entries
.into_iter()
.map(|p| self.try_spawn_new_task(&p, &mut intermediate_modules))
.map(|(name, info)| {
(
name,
self.try_spawn_new_task(&info, &mut intermediate_modules),
)
})
.collect();

let mut tables: IndexVec<ModuleId, SymbolMap> = Default::default();
Expand Down Expand Up @@ -101,7 +106,9 @@ impl<'a> ModuleLoader<'a> {
Ok(())
}

async fn resolve_entries(&mut self) -> anyhow::Result<Vec<ResolvedRequestInfo>> {
async fn resolve_entries(
&mut self,
) -> anyhow::Result<Vec<(Option<String>, ResolvedRequestInfo)>> {
let resolver = &self.resolver;

let resolved_ids = block_on_spawn_all(self.input_options.input.iter().map(
Expand All @@ -117,7 +124,7 @@ impl<'a> ModuleLoader<'a> {
return Err(BuildError::entry_cannot_be_external(info.path.as_str()));
}

Ok(info)
Ok((input_item.name.clone(), info))
},
));

Expand Down
21 changes: 21 additions & 0 deletions crates/rolldown/tests/fixtures/code_splitting/artifacts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
source: crates/rolldown/tests/common/case.rs
expression: content
input_file: crates/rolldown/tests/fixtures/code_splitting
---
# 2.js

```js
// share.js
console.log('shared');
```
# main1.js

```js
// main1.js
```
# main2.js

```js
// main2.js
```
1 change: 1 addition & 0 deletions crates/rolldown/tests/fixtures/code_splitting/main1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './share'
1 change: 1 addition & 0 deletions crates/rolldown/tests/fixtures/code_splitting/main2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './share'
1 change: 1 addition & 0 deletions crates/rolldown/tests/fixtures/code_splitting/share.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('shared');
14 changes: 14 additions & 0 deletions crates/rolldown/tests/fixtures/code_splitting/test.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"input": {
"input": [
{
"name": "main1",
"import": "main1.js"
},
{
"name": "main2",
"import": "main2.js"
}
]
}
}

0 comments on commit 74a7ccf

Please sign in to comment.