Skip to content

perf(turbopack): Use last side effect as ModuleEvaluation fragment #76940

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Mar 11, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ impl EcmascriptModulePartAsset {
| ModulePart::InternalEvaluation(..)
| ModulePart::Facade
| ModulePart::Exports
| ModulePart::Evaluation
) {
return Ok(EcmascriptModulePartAsset {
full_module: module,
Expand Down
67 changes: 44 additions & 23 deletions turbopack/crates/turbopack-ecmascript/src/tree_shake/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub(crate) enum ItemId {

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum ItemIdGroupKind {
/// Used only for testing
#[cfg(test)]
ModuleEvaluation,
/// `(local, export_name)``
Export(Id, Atom),
Expand Down Expand Up @@ -130,6 +132,8 @@ pub(crate) struct ItemData {

/// Server actions breaks when we merge exports.
pub disable_export_merging: bool,

pub is_module_evaluation: bool,
}

impl fmt::Debug for ItemData {
Expand All @@ -145,6 +149,8 @@ impl fmt::Debug for ItemData {
.field("side_effects", &self.side_effects)
.field("export", &self.export)
.field("explicit_deps", &self.explicit_deps)
.field("disable_export_merging", &self.disable_export_merging)
.field("is_module_evaluation", &self.is_module_evaluation)
.finish()
}
}
Expand All @@ -165,6 +171,7 @@ impl Default for ItemData {
binding_source: Default::default(),
explicit_deps: Default::default(),
disable_export_merging: Default::default(),
is_module_evaluation: Default::default(),
}
}
}
Expand Down Expand Up @@ -328,6 +335,8 @@ impl DepGraph {
.clone()
};

let mut module_evaluation_ix = None;

for (ix, group) in groups.graph_ix.iter().enumerate() {
let mut chunk = Module {
span: DUMMY_SP,
Expand Down Expand Up @@ -389,10 +398,10 @@ impl DepGraph {
}
}

for item in group {
match item {
for item_id in group {
match item_id {
ItemId::Group(ItemIdGroupKind::Export(..)) => {
if let Some(export) = &data[item].export {
if let Some(export) = &data[item_id].export {
outputs.insert(Key::Export(export.as_str().into()), ix as u32);

let s = ExportSpecifier::Named(ExportNamedSpecifier {
Expand All @@ -418,11 +427,14 @@ impl DepGraph {
));
}
}
ItemId::Group(ItemIdGroupKind::ModuleEvaluation) => {
outputs.insert(Key::ModuleEvaluation, ix as u32);
}

_ => {}
_ => {
if data[item_id].is_module_evaluation {
debug_assert_eq!(module_evaluation_ix, None);
module_evaluation_ix = Some(ix as u32);
outputs.insert(Key::ModuleEvaluation, ix as u32);
}
}
}
}

Expand Down Expand Up @@ -686,6 +698,31 @@ impl DepGraph {

modules.push(exports_module);

// Currently we need to have `Key::ModuleEvaluation` in the outputs
// even if it is empty.
if module_evaluation_ix.is_none() {
outputs.insert(Key::ModuleEvaluation, modules.len() as u32);
module_evaluation_ix = Some(modules.len() as u32);
modules.push(Module {
span: DUMMY_SP,
body: vec![],
shebang: None,
});
}

// Push `export {}` to the module evaluation to make it module.
modules[module_evaluation_ix.unwrap() as usize]
.body
.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
NamedExport {
span: DUMMY_SP,
specifiers: Default::default(),
src: None,
type_only: false,
with: None,
},
)));

SplitModuleResult {
entrypoints: outputs,
part_deps,
Expand Down Expand Up @@ -1370,22 +1407,6 @@ impl DepGraph {
}
}

{
// `module evaluation side effects` Node
let id = ItemId::Group(ItemIdGroupKind::ModuleEvaluation);
ids.push(id.clone());
items.insert(
id,
ItemData {
content: ModuleItem::Stmt(Stmt::Expr(ExprStmt {
span: DUMMY_SP,
expr: "module evaluation".into(),
})),
..Default::default()
},
);
}

for (local, export_name, disable_export_merging) in exports {
let id = ItemId::Group(ItemIdGroupKind::Export(local.clone(), export_name.clone()));
ids.push(id.clone());
Expand Down
25 changes: 11 additions & 14 deletions turbopack/crates/turbopack-ecmascript/src/tree_shake/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,23 +350,20 @@ impl Analyzer<'_> {

/// Phase 4: Exports
fn handle_exports(&mut self, _module: &Module) {
for item_id in self.item_ids.iter() {
if let ItemId::Group(kind) = item_id {
match kind {
ItemIdGroupKind::ModuleEvaluation => {
// Create a strong dependency to LAST_SIDE_EFFECTS
// We use the last side effect as a module evaluation
if let Some(last) = self.last_side_effects.last() {
if let Some(item) = self.items.get_mut(last) {
item.is_module_evaluation = true;
}
}

self.g
.add_strong_deps(item_id, self.last_side_effects.last());
}
ItemIdGroupKind::Export(local, _) => {
// Create a strong dependency to LAST_WRITES for this var
for item_id in self.item_ids.iter() {
if let ItemId::Group(ItemIdGroupKind::Export(local, _)) = item_id {
// Create a strong dependency to LAST_WRITES for this var

let state = self.vars.entry(local.clone()).or_default();
let state = self.vars.entry(local.clone()).or_default();

self.g.add_strong_deps(item_id, state.last_writes.iter());
}
}
self.g.add_strong_deps(item_id, state.last_writes.iter());
}
}
}
Expand Down
Loading
Loading