Skip to content

Commit

Permalink
feat: code splitting rely on tree shaking. Closes #390
Browse files Browse the repository at this point in the history
  • Loading branch information
hyf0 committed Feb 20, 2024
1 parent ef4a4e5 commit 5e7491e
Show file tree
Hide file tree
Showing 88 changed files with 178 additions and 158 deletions.
7 changes: 7 additions & 0 deletions crates/rolldown/src/bundler/module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ impl Module {
Self::External(m) => &m.resource_id,
}
}

pub fn is_included(&self) -> bool {
match self {
Self::Normal(m) => m.is_included,
Self::External(_) => true,
}
}
}

pub struct ModuleRenderContext<'a> {
Expand Down
145 changes: 78 additions & 67 deletions crates/rolldown/src/bundler/stages/bundle_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,33 +53,36 @@ impl<'a> BundleStage<'a> {
chunk.de_conflict(self.link_output);
});

self.link_output.ast_table.iter_mut_enumerated().par_bridge().for_each(|(id, ast)| match &self
self
.link_output
.modules[id]
{
Module::Normal(module) => {
// TODO: should consider cases:
// - excluded normal modules in code splitting doesn't belong to any chunk.
let chunk_id = chunk_graph.module_to_chunk[module.id].unwrap();
let chunk = &chunk_graph.chunks[chunk_id];
let linking_info = &self.link_output.linking_infos[module.id];
module.finalize(
FinalizerContext {
canonical_names: &chunk.canonical_names,
id: module.id,
symbols: &self.link_output.symbols,
linking_info,
module,
modules: &self.link_output.modules,
linking_infos: &self.link_output.linking_infos,
runtime: &self.link_output.runtime,
chunk_graph: &chunk_graph,
},
ast,
);
}
Module::External(_) => {}
});
.ast_table
.iter_mut_enumerated()
.par_bridge()
.filter(|(id, _)| self.link_output.modules[*id].is_included())
.for_each(|(id, ast)| match &self.link_output.modules[id] {
Module::Normal(module) => {
// TODO: should consider cases:
// - excluded normal modules in code splitting doesn't belong to any chunk.
let chunk_id = chunk_graph.module_to_chunk[module.id].unwrap();
let chunk = &chunk_graph.chunks[chunk_id];
let linking_info = &self.link_output.linking_infos[module.id];
module.finalize(
FinalizerContext {
canonical_names: &chunk.canonical_names,
id: module.id,
symbols: &self.link_output.symbols,
linking_info,
module,
modules: &self.link_output.modules,
linking_infos: &self.link_output.linking_infos,
runtime: &self.link_output.runtime,
chunk_graph: &chunk_graph,
},
ast,
);
}
Module::External(_) => {}
});

let chunks = chunk_graph.chunks.iter().map(|c| {
let (content, rendered_modules) =
Expand Down Expand Up @@ -135,6 +138,20 @@ impl<'a> BundleStage<'a> {
);
}
});

module.stmt_infos.iter().for_each(|stmt_info| {
if !stmt_info.is_included {
return;
}
stmt_info.referenced_symbols.iter().for_each(|symbol_ref| {
let canonical_ref = self.link_output.symbols.par_canonical_ref_for(*symbol_ref);
self.determine_reachable_modules_for_entry(
canonical_ref.owner,
entry_index,
module_to_bits,
);
});
});
}

// TODO(hyf0): refactor this function
Expand Down Expand Up @@ -297,11 +314,11 @@ impl<'a> BundleStage<'a> {

fn generate_chunks(&self) -> ChunkGraph {
let entries_len: u32 = self.link_output.entries.len().try_into().unwrap();
let is_rolldown_test = std::env::var("ROLLDOWN_TEST").is_ok();

let mut module_to_bits = index_vec::index_vec![
BitSet::new(entries_len);
self.link_output.modules.len()
];
let entries_len = if is_rolldown_test { entries_len + 1 } else { entries_len };
let mut module_to_bits =
index_vec::index_vec![BitSet::new(entries_len); self.link_output.modules.len()];
let mut bits_to_chunk = FxHashMap::with_capacity_and_hasher(
self.link_output.entries.len(),
BuildHasherDefault::default(),
Expand All @@ -322,18 +339,16 @@ impl<'a> BundleStage<'a> {
bits_to_chunk.insert(bits, chunk);
}

// Determine which modules belong to which chunk. A module could belong to multiple chunks.
self.link_output.entries.iter().enumerate().for_each(|(i, entry_point)| {
// runtime module are shared by all chunks, so we mark it as reachable for all entries.
// FIXME: But this solution is not perfect. If we have two entries, one of them relies on runtime module, the other one doesn't.
// In this case, we only need to generate two chunks, but currently we will generate three chunks. We need to analyze the usage of runtime module
// to make sure only necessary chunks mark runtime module as reachable.
if is_rolldown_test {
self.determine_reachable_modules_for_entry(
self.link_output.runtime.id(),
i.try_into().unwrap(),
entries_len - 1,
&mut module_to_bits,
);
}

// Determine which modules belong to which chunk. A module could belong to multiple chunks.
self.link_output.entries.iter().enumerate().for_each(|(i, entry_point)| {
self.determine_reachable_modules_for_entry(
entry_point.id,
i.try_into().unwrap(),
Expand All @@ -346,30 +361,19 @@ impl<'a> BundleStage<'a> {
self.link_output.modules.len()
];

// FIXME(hyf0): This is a hack to make the runtime code doesn't show up in the snapshot.
let is_rolldown_test = std::env::var("ROLLDOWN_TEST").is_ok();
if is_rolldown_test {
let runtime_chunk_id = chunks.push(Chunk::new(
Some("_rolldown_runtime".to_string()),
None,
module_to_bits[self.link_output.runtime.id()].clone(),
vec![self.link_output.runtime.id()],
));
module_to_chunk[self.link_output.runtime.id()] = Some(runtime_chunk_id);
}

// 1. Assign modules to corresponding chunks
// 2. Create shared chunks to store modules that belong to multiple chunks.
for module in &self.link_output.modules {
let Module::Normal(_) = module else {
let Module::Normal(normal_module) = module else {
continue;
};

// FIXME(hyf0): This is a hack to make the runtime code doesn't show up in the snapshot.
if is_rolldown_test && module.id() == self.link_output.runtime.id() {
if !normal_module.is_included {
continue;
}

let bits = &module_to_bits[module.id()];
debug_assert!(!bits.is_empty());
if let Some(chunk_id) = bits_to_chunk.get(bits).copied() {
chunks[chunk_id].modules.push(module.id());
module_to_chunk[module.id()] = Some(chunk_id);
Expand All @@ -390,24 +394,31 @@ impl<'a> BundleStage<'a> {
}

fn generate_chunk_filenames(&self, chunk_graph: &mut ChunkGraph) {
let is_rolldown_test = std::env::var("ROLLDOWN_TEST").is_ok();
let mut used_chunk_names = FxHashSet::default();
chunk_graph.chunks.iter_mut().for_each(|chunk| {
let runtime_id = self.link_output.runtime.id();

let file_name_tmp = chunk.file_name_template(self.output_options);
let chunk_name = chunk.name.clone().unwrap_or_else(|| {
let module_id = if let Some(entry_point) = &chunk.entry_point {
debug_assert!(
matches!(entry_point.kind, EntryPointKind::DynamicImport),
"User-defined entry point should always have a name"
);
entry_point.id
} else {
// TODO: we currently use the first executed module to calculate the chunk name for common chunks
// This is not perfect, should investigate more to find a better solution
chunk.modules.first().copied().unwrap()
};
let module = &self.link_output.modules[module_id];
module.resource_id().expect_file().unique(&self.input_options.cwd)
});
let chunk_name = if is_rolldown_test && chunk.modules.first().copied() == Some(runtime_id) {
"$runtime$".to_string()
} else {
chunk.name.clone().unwrap_or_else(|| {
let module_id = if let Some(entry_point) = &chunk.entry_point {
debug_assert!(
matches!(entry_point.kind, EntryPointKind::DynamicImport),
"User-defined entry point should always have a name"
);
entry_point.id
} else {
// TODO: we currently use the first executed module to calculate the chunk name for common chunks
// This is not perfect, should investigate more to find a better solution
chunk.modules.first().copied().unwrap()
};
let module = &self.link_output.modules[module_id];
module.resource_id().expect_file().unique(&self.input_options.cwd)
})
};

let mut chunk_name = chunk_name;
while used_chunk_names.contains(&chunk_name) {
Expand Down
4 changes: 4 additions & 0 deletions crates/rolldown/src/bundler/utils/bitset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ impl BitSet {
pub fn set_bit(&mut self, bit: u32) {
self.entries[bit as usize / 8] |= 1 << (bit & 7);
}

pub fn is_empty(&self) -> bool {
self.entries.iter().all(|&e| e == 0)
}
}

impl Display for BitSet {
Expand Down
3 changes: 1 addition & 2 deletions crates/rolldown/tests/common/case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ impl Case {
assets.sort_by_key(|c| c.file_name().to_string());
let artifacts = assets
.iter()
// FIXME: should render the runtime module while tree shaking being supported
.filter(|asset| !asset.file_name().contains("rolldown_runtime"))
.filter(|asset| !asset.file_name().contains("$runtime$"))
.flat_map(|asset| {
[
Cow::Owned(format!("## {}\n", asset.file_name())),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/common_js_from_es6
## entry_js.mjs

```js
import { __esmMin, __export, __toCommonJS } from "./_rolldown_runtime.mjs";
import { __esmMin, __export, __toCommonJS } from "./$runtime$.mjs";
// foo.js
function foo$1() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ input_file: crates/rolldown/tests/esbuild/default/duplicate_entry_point
```js
```
## entry_js-3.mjs
## entry_js-2.mjs

```js
// entry.js
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/empty_export_clause_bundle_as_
## entry_js.mjs

```js
import { __esmMin, __export, __toCommonJS } from "./_rolldown_runtime.mjs";
import { __esmMin, __export, __toCommonJS } from "./$runtime$.mjs";
// types.mjs
var types_ns;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/es6_from_common_js
## entry_js.mjs

```js
import { __commonJSMin, __toESM } from "./_rolldown_runtime.mjs";
import { __commonJSMin, __toESM } from "./$runtime$.mjs";
// foo.js
var require_foo = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/export_forms_common_js
## entry_js.mjs

```js
import { __esmMin, __export, __toCommonJS } from "./_rolldown_runtime.mjs";
import { __esmMin, __export, __toCommonJS } from "./$runtime$.mjs";
// a.js
var abc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/export_forms_es6
## entry_js.mjs

```js
import { __export } from "./_rolldown_runtime.mjs";
import { __export } from "./$runtime$.mjs";
// a.js
const abc = undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/import_missing_common_js
## entry_js.mjs

```js
import { __commonJSMin, __toESM } from "./_rolldown_runtime.mjs";
import { __commonJSMin, __toESM } from "./$runtime$.mjs";
// foo.js
var require_foo = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/nested_common_js
## entry_js.mjs

```js
import { __commonJSMin } from "./_rolldown_runtime.mjs";
import { __commonJSMin } from "./$runtime$.mjs";
// foo.js
var require_foo = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/nested_es6_from_common_js
## entry_js.mjs

```js
import { __commonJSMin, __toESM } from "./_rolldown_runtime.mjs";
import { __commonJSMin, __toESM } from "./$runtime$.mjs";
// foo.js
var require_foo = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/new_expression_common_js
## entry_js.mjs

```js
import { __commonJSMin } from "./_rolldown_runtime.mjs";
import { __commonJSMin } from "./$runtime$.mjs";
// foo.js
var require_foo = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/re_export_common_js_as_es6
## entry_js.mjs

```js
import { __commonJSMin, __toESM } from "./_rolldown_runtime.mjs";
import { __commonJSMin, __toESM } from "./$runtime$.mjs";
// foo.js
var require_foo = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/require_child_dir_common_js
## entry_js.mjs

```js
import { __commonJSMin } from "./_rolldown_runtime.mjs";
import { __commonJSMin } from "./$runtime$.mjs";
// dir/index.js
var require_dir_index = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/require_main_cache_common_js
## entry_js.mjs

```js
import { __commonJSMin } from "./_rolldown_runtime.mjs";
import { __commonJSMin } from "./$runtime$.mjs";
// is-main.js
var require_is_main = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/require_parent_dir_common_js
## dir_entry_js.mjs

```js
import { __commonJSMin } from "./_rolldown_runtime.mjs";
import { __commonJSMin } from "./$runtime$.mjs";
// index.js
var require_require_parent_dir_common_js_index = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/require_with_call_inside_try
## entry_js.mjs

```js
import { __commonJSMin } from "./_rolldown_runtime.mjs";
import { __commonJSMin } from "./$runtime$.mjs";
// entry.js
var require_entry = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/simple_common_js
## entry_js.mjs

```js
import { __commonJSMin } from "./_rolldown_runtime.mjs";
import { __commonJSMin } from "./$runtime$.mjs";
// foo.js
var require_foo = __commonJSMin((exports, module) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ input_file: crates/rolldown/tests/esbuild/default/use_strict_directive_bundle_is
## entry_js.mjs

```js
import { __commonJSMin } from "./_rolldown_runtime.mjs";
import { __commonJSMin } from "./$runtime$.mjs";
// cjs.js
'use strict';var require_cjs = __commonJSMin((exports, module) => {
Expand Down

0 comments on commit 5e7491e

Please sign in to comment.