Turbopack: analyzer improvement followups (#92834)

- defer computing the `EsmAssetReference` issue source from the `Span`. means that we have to pass less arguments around as well
- make `mangle("default export")` a "constant" and use that everywhere instead

Some snapshots changed, because the test runner had `source: None`, so now there are some duplicate module references in there because the new always-set `span` field forces it to be a new entry in the `data.references` hashmap.
This commit is contained in:
Niklas Mischkulnig
2026-04-15 22:23:13 +02:00
committed by GitHub
parent 45e5ad7641
commit 69b9c4bc41
13 changed files with 42 additions and 58 deletions
@@ -66,7 +66,6 @@ pub fn benchmark(c: &mut Criterion) {
top_level_mark,
Default::default(),
None,
None,
);
let var_graph = create_graph(
&program,
@@ -22,8 +22,7 @@ use swc_core::{
},
};
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::ResolvedVc;
use turbopack_core::{resolve::ExportUsage, source::Source};
use turbopack_core::resolve::ExportUsage;
use super::{
ConstantValue, ImportMap, JsValue, ObjectPart, WellKnownFunctionKind, is_unresolved_id,
@@ -474,14 +473,11 @@ impl EvalContext {
top_level_mark: Mark,
force_free_values: Arc<FxHashSet<Id>>,
comments: Option<&dyn Comments>,
source: Option<ResolvedVc<Box<dyn Source>>>,
) -> Self {
Self {
unresolved_mark,
top_level_mark,
imports: module.map_or(ImportMap::default(), |m| {
ImportMap::analyze(m, source, comments)
}),
imports: module.map_or(ImportMap::default(), |m| ImportMap::analyze(m, comments)),
force_free_values,
}
}
@@ -914,14 +910,8 @@ impl EvalContext {
let js_value = try_with_handler(cm, Default::default(), |_| {
GLOBALS.set(&Default::default(), || {
let expr = parse_single_expr_lit(expr_lit);
let eval_context = EvalContext::new(
None,
Mark::new(),
Mark::new(),
Default::default(),
None,
None,
);
let eval_context =
EvalContext::new(None, Mark::new(), Mark::new(), Default::default(), None);
Ok(eval_context.eval(&expr))
})
@@ -6,7 +6,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::SmallVec;
use swc_core::{
atoms::Wtf8Atom,
common::{BytePos, Span, Spanned, SyntaxContext, comments::Comments, source_map::SmallPos},
common::{BytePos, Span, Spanned, SyntaxContext, comments::Comments},
ecma::{
ast::*,
atoms::{Atom, atom},
@@ -17,13 +17,13 @@ use swc_core::{
use turbo_frozenmap::FrozenMap;
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{FxIndexMap, FxIndexSet, ResolvedVc};
use turbopack_core::{issue::IssueSource, loader::WebpackLoaderItem, source::Source};
use turbopack_core::loader::WebpackLoaderItem;
use super::{JsValue, ModuleValue, top_level_await::has_top_level_await};
use crate::{
SpecifiedModuleType,
analyzer::{ConstantValue, ObjectPart, graph::VarGraph},
magic_identifier,
magic_identifier::{MAGIC_IDENTIFIER_DEFAULT_EXPORT, MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM},
references::{
esm::{EsmAssetReference, EsmExport, Liveness},
util::{SpecifiedChunkingType, parse_chunking_type_annotation},
@@ -410,7 +410,7 @@ pub(crate) struct ImportMapReference {
pub module_path: Wtf8Atom,
pub imported_symbol: ImportedSymbol,
pub annotations: Option<Arc<ImportAnnotations>>,
pub issue_source: Option<IssueSource>,
pub span: Span,
}
impl ImportMap {
@@ -524,15 +524,10 @@ impl ImportMap {
}
/// Analyze ES import
pub(super) fn analyze(
m: &Program,
source: Option<ResolvedVc<Box<dyn Source>>>,
comments: Option<&dyn Comments>,
) -> Self {
pub(super) fn analyze(m: &Program, comments: Option<&dyn Comments>) -> Self {
let mut data = ImportMap::default();
let mut analyzer = Analyzer {
data: &mut data,
source,
comments,
namespace_imports_to_specifier: FxIndexMap::default(),
};
@@ -650,7 +645,6 @@ impl ImportMap {
struct Analyzer<'a> {
data: &'a mut ImportMap,
source: Option<ResolvedVc<Box<dyn Source>>>,
comments: Option<&'a dyn Comments>,
/// Map from local identifier of namespace imports to module path, used temporarily during
/// analysis to detect dynamic accesses to namespace imports.
@@ -665,14 +659,10 @@ impl Analyzer<'_> {
imported_symbol: ImportedSymbol,
annotations: Option<ImportAnnotations>,
) -> usize {
let issue_source = self
.source
.map(|s| IssueSource::from_swc_offsets(s, span.lo.to_u32(), span.hi.to_u32()));
let r = ImportMapReference {
module_path,
imported_symbol,
issue_source,
span,
annotations: annotations.map(Arc::new),
};
if let Some(i) = self.data.references.get_index_of(&r) {
@@ -858,7 +848,7 @@ impl Visit for Analyzer<'_> {
ident.as_ref().map_or_else(
|| {
(
magic_identifier::mangle("default export").into(),
MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
SyntaxContext::empty(),
)
},
@@ -868,7 +858,7 @@ impl Visit for Analyzer<'_> {
DefaultDecl::TsInterfaceDecl(_) => {
// not matching, might happen due to eventual consistency
(
magic_identifier::mangle("default export").into(),
MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
SyntaxContext::empty(),
)
}
@@ -887,13 +877,13 @@ impl Visit for Analyzer<'_> {
self.data.exports.insert(
rcstr!("default"),
Export::LocalBinding(magic_identifier::mangle("default export").into(), false),
Export::LocalBinding(MAGIC_IDENTIFIER_DEFAULT_EXPORT.clone(), false),
);
self.data.exports_ids.insert(
rcstr!("default"),
(
// `EsmModuleItem::code_generation` inserts this variable.
magic_identifier::mangle("default export").into(),
MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
SyntaxContext::empty(),
),
);
@@ -3798,7 +3798,6 @@ mod tests {
top_level_mark,
Default::default(),
Some(&comments),
None,
);
let mut var_graph = create_graph(
@@ -5,6 +5,14 @@ use std::{
use once_cell::sync::Lazy;
use regex::{Captures, Regex, Replacer};
use swc_core::atoms::Atom;
use turbo_rcstr::RcStr;
pub static MAGIC_IDENTIFIER_DEFAULT_EXPORT: Lazy<RcStr> =
Lazy::new(|| RcStr::from(mangle("default export")));
pub static MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM: Lazy<Atom> =
Lazy::new(|| Atom::from(mangle("default export")));
pub fn mangle(content: &str) -> String {
let mut r = "__TURBOPACK__".to_string();
@@ -605,7 +605,6 @@ async fn parse_file_content(
top_level_mark,
Arc::new(var_with_ts_declare),
Some(&comments),
Some(source),
);
let (comments, source_mapping_url) =
@@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::BTreeMap, ops::ControlFlow};
use std::{collections::BTreeMap, ops::ControlFlow};
use anyhow::{Result, bail};
use bincode::{Decode, Encode};
@@ -31,7 +31,7 @@ use crate::{
analyzer::graph::EvalContext,
chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
code_gen::{CodeGeneration, CodeGenerationHoistedStmt},
magic_identifier,
magic_identifier::MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM,
references::esm::base::ReferencedAsset,
runtime_functions::{TURBOPACK_DYNAMIC, TURBOPACK_ESM},
tree_shake::part::module::EcmascriptModulePartAsset,
@@ -709,7 +709,7 @@ impl EsmExports {
let binding = if let Some((local, ctxt)) =
eval_context.imports.exports_ids.get(exported)
{
Some((Cow::Borrowed(local.as_str()), *ctxt))
Some((local.clone(), *ctxt))
} else {
bail!(
"Expected export to be in eval context {:?} {:?}",
@@ -721,15 +721,15 @@ impl EsmExports {
// Fallback, shouldn't happen in practice
(
if name == "default" {
Cow::Owned(magic_identifier::mangle("default export"))
MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone()
} else {
Cow::Borrowed(name.as_str())
name.as_str().into()
},
SyntaxContext::empty(),
)
});
let local = Ident::new(local.into(), DUMMY_SP, ctxt);
let local = Ident::new(local, DUMMY_SP, ctxt);
match (liveness, export_usage_info.is_circuit_breaker) {
(Liveness::Constant, false) => ExportBinding::Value(Expr::Ident(local)),
// If the value might change or we are a circuit breaker we must bind a
@@ -15,7 +15,8 @@ use turbopack_core::chunk::ChunkingContext;
use crate::{
code_gen::{CodeGen, CodeGeneration},
create_visitor, magic_identifier,
create_visitor,
magic_identifier::MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM,
references::AstPath,
};
@@ -68,7 +69,7 @@ impl EsmModuleItem {
decls: vec![VarDeclarator {
span: DUMMY_SP,
name: Ident::new(
magic_identifier::mangle("default export").into(),
MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
DUMMY_SP,
Default::default(),
)
@@ -86,8 +87,7 @@ impl EsmModuleItem {
ModuleItem::Stmt(Stmt::Decl(Decl::Class(ClassDecl {
ident: class.ident.unwrap_or_else(|| {
Ident::new(
magic_identifier::mangle("default export")
.into(),
MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
DUMMY_SP,
Default::default(),
)
@@ -100,7 +100,7 @@ impl EsmModuleItem {
*module_item = ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
ident: fn_expr.ident.unwrap_or_else(|| {
Ident::new(
magic_identifier::mangle("default export").into(),
MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
DUMMY_SP,
Default::default(),
)
@@ -811,8 +811,7 @@ async fn analyze_ecmascript_module_internal(
module,
ResolvedVc::upcast(module),
RcStr::from(&*r.module_path.to_string_lossy()),
r.issue_source
.unwrap_or_else(|| IssueSource::from_source_only(source)),
IssueSource::from_swc_offsets(source, r.span.lo.to_u32(), r.span.hi.to_u32()),
r.annotations.as_ref().map(|a| (**a).clone()),
match &r.imported_symbol {
ImportedSymbol::ModuleEvaluation => {
@@ -33,7 +33,10 @@ use super::{
Vars, collect_top_level_decls, ids_captured_by, ids_used_by, ids_used_by_ignoring_nested,
},
};
use crate::{magic_identifier, tree_shake::optimizations::GraphOptimizer};
use crate::{
magic_identifier::{self, MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM},
tree_shake::optimizations::GraphOptimizer,
};
/// The id of an item
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -1012,7 +1015,7 @@ impl DepGraph {
// bindings if the class/function has an identifier.
let default_var = id.unwrap_or_else(|| {
Ident::new(
magic_identifier::mangle("default export").into(),
MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
DUMMY_SP,
Default::default(),
)
@@ -1083,7 +1086,7 @@ impl DepGraph {
// Mirror what `EsmModuleItem::code_generation` does, these are live
// bindings if the class/function has an identifier.
let default_var = Ident::new(
magic_identifier::mangle("default export").into(),
MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
DUMMY_SP,
Default::default(),
);
@@ -492,7 +492,6 @@ pub(super) async fn split_module(asset: Vc<EcmascriptModuleAsset>) -> Result<Vc<
}
let parse_result = parsed.await?;
let source = asset.source().to_resolved().await?;
match &*parse_result {
ParseResult::Ok {
@@ -570,7 +569,6 @@ pub(super) async fn split_module(asset: Vc<EcmascriptModuleAsset>) -> Result<Vc<
eval_context.top_level_mark,
eval_context.force_free_values.clone(),
None,
Some(source),
);
ParseResult::resolved_cell(ParseResult::Ok {
@@ -696,7 +694,6 @@ pub(crate) async fn part_of_module(
eval_context.top_level_mark,
eval_context.force_free_values.clone(),
None,
None,
);
return Ok(ParseResult::Ok {
@@ -1268,7 +1268,7 @@
export_usage: All,
},
ImportedBinding {
esm_reference_index: 4,
esm_reference_index: 5,
export: Some(
"z",
),
@@ -1122,7 +1122,7 @@
span: 486..488,
},
ImportedBinding {
esm_reference_index: 2,
esm_reference_index: 3,
export: Some(
"join",
),