Turbopack: Use FrozenMap for module export information (#92802)

This commit is contained in:
Benjamin Woodruff
2026-04-15 10:24:23 +02:00
committed by GitHub
parent 836fa63fa4
commit 8a2c5e017e
7 changed files with 64 additions and 70 deletions
Generated
+1
View File
@@ -10481,6 +10481,7 @@ dependencies = [
"tracing",
"turbo-bincode",
"turbo-esregex",
"turbo-frozenmap",
"turbo-rcstr",
"turbo-tasks",
"turbo-tasks-backend",
@@ -49,6 +49,7 @@ tokio = { workspace = true }
tracing = { workspace = true }
turbo-bincode = { workspace = true }
turbo-esregex = { workspace = true }
turbo-frozenmap = { workspace = true }
turbo-rcstr = { workspace = true }
turbo-tasks = { workspace = true }
turbo-tasks-fs = { workspace = true }
@@ -11,6 +11,7 @@ use swc_core::{
},
quote, quote_expr,
};
use turbo_frozenmap::FrozenMap;
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{
FxIndexMap, NonLocalValue, ResolvedVc, TryFlatJoinIterExt, Vc, trace::TraceRawVcs, turbofmt,
@@ -529,7 +530,7 @@ async fn emit_star_exports_issue(source_ident: Vc<AssetIdent>, message: RcStr) -
#[derive(Hash, Debug)]
pub struct EsmExports {
/// Explicit exports
pub exports: BTreeMap<RcStr, EsmExport>,
pub exports: FrozenMap<RcStr, EsmExport>,
/// Unexpanded `export * from ...` statements (expanded in `expand_star_exports`)
pub star_exports: Vec<ResolvedVc<Box<dyn ModuleReference>>>,
}
@@ -541,7 +542,7 @@ pub struct EsmExports {
#[turbo_tasks::value(shared)]
#[derive(Hash, Debug)]
pub struct ExpandedExports {
pub exports: BTreeMap<RcStr, EsmExport>,
pub exports: FrozenMap<RcStr, EsmExport>,
/// Modules we couldn't analyze all exports of.
pub dynamic_exports: Vec<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
}
@@ -559,16 +560,16 @@ impl EsmExports {
module_reference: Vc<Box<dyn ModuleReference>>,
) -> Result<Vc<EcmascriptExports>> {
let module_reference = module_reference.to_resolved().await?;
let mut exports = BTreeMap::new();
let mut exports = Vec::new();
let default = rcstr!("default");
exports.insert(
exports.push((
default.clone(),
EsmExport::ImportedBinding(module_reference, default, false),
);
));
Ok(EcmascriptExports::EsmExports(
EsmExports {
exports,
exports: FrozenMap::from(exports),
star_exports: vec![module_reference],
}
.resolved_cell(),
@@ -581,7 +582,11 @@ impl EsmExports {
&self,
export_usage_info: Vc<ModuleExportUsageInfo>,
) -> Result<Vc<ExpandedExports>> {
let mut exports: BTreeMap<RcStr, EsmExport> = self.exports.clone();
let mut exports: BTreeMap<_, _> = self
.exports
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let mut dynamic_exports = vec![];
let export_usage_info = export_usage_info.await?;
@@ -608,12 +613,10 @@ impl EsmExports {
continue;
}
if !exports.contains_key(export) {
exports.insert(
export.clone(),
EsmExport::ImportedBinding(esm_ref, export.clone(), false),
);
}
// the spec indicates first-one-wins: https://tc39.es/ecma262/#_ref_9060
exports
.entry(export.clone())
.or_insert_with(|| EsmExport::ImportedBinding(esm_ref, export.clone(), false));
}
if !export_info.dynamic_exporting_modules.is_empty() {
@@ -622,7 +625,7 @@ impl EsmExports {
}
Ok(ExpandedExports {
exports,
exports: FrozenMap::from(exports),
dynamic_exports,
}
.cell())
@@ -22,7 +22,6 @@ pub mod util;
pub mod worker;
use std::{
collections::BTreeMap,
future::Future,
mem::take,
ops::Deref,
@@ -62,6 +61,7 @@ use swc_core::{
};
use tokio::sync::OnceCell;
use tracing::Instrument;
use turbo_frozenmap::FrozenMap;
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{
FxIndexMap, FxIndexSet, NonLocalValue, PrettyPrintError, ReadRef, ResolvedVc, TaskInput,
@@ -907,21 +907,21 @@ async fn analyze_ecmascript_module_internal(
analysis.add_esm_reexport_reference(i);
}
Reexport::Namespace { exported: n } => {
esm_exports.insert(
esm_exports.push((
n.as_str().into(),
EsmExport::ImportedNamespace(ResolvedVc::upcast(reference)),
);
));
analysis.add_esm_reexport_reference(i);
}
Reexport::Named { imported, exported } => {
esm_exports.insert(
esm_exports.push((
exported.as_str().into(),
EsmExport::ImportedBinding(
ResolvedVc::upcast(reference),
imported.to_string().into(),
false,
),
);
));
analysis.add_esm_reexport_reference(i);
}
}
@@ -939,7 +939,7 @@ async fn analyze_ecmascript_module_internal(
}
let esm_exports = EsmExports {
exports: esm_exports,
exports: FrozenMap::from(esm_exports),
star_exports: esm_star_exports,
}
.cell();
@@ -4003,7 +4003,7 @@ struct ModuleReferencesVisitor<'a> {
old_analyzer: StaticAnalyser,
import_references: &'a [ResolvedVc<EsmAssetReference>],
analysis: &'a mut AnalyzeEcmascriptModuleResultBuilder,
esm_exports: BTreeMap<RcStr, EsmExport>,
esm_exports: Vec<(RcStr, EsmExport)>,
webpack_runtime: Option<(RcStr, Span)>,
webpack_entry: bool,
webpack_chunks: Vec<Lit>,
@@ -4026,7 +4026,7 @@ impl<'a> ModuleReferencesVisitor<'a> {
old_analyzer: StaticAnalyser::default(),
import_references,
analysis,
esm_exports: BTreeMap::new(),
esm_exports: Vec::new(),
webpack_runtime: None,
webpack_entry: false,
webpack_chunks: Vec::new(),
@@ -4186,7 +4186,7 @@ impl VisitAstPath for ModuleReferencesVisitor<'_> {
)
}
};
self.esm_exports.insert(key, export);
self.esm_exports.push((key, export));
}
}
}
@@ -4212,7 +4212,7 @@ impl VisitAstPath for ModuleReferencesVisitor<'_> {
let liveness = self.get_export_ident_liveness((id.clone(), ctx));
let name: RcStr = id.as_str().into();
self.esm_exports
.insert(name.clone(), EsmExport::LocalBinding(name, liveness));
.push((name.clone(), EsmExport::LocalBinding(name, liveness)));
};
match decl {
Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {
@@ -4252,14 +4252,14 @@ impl VisitAstPath for ModuleReferencesVisitor<'_> {
export: &'ast ExportDefaultExpr,
ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
) {
self.esm_exports.insert(
self.esm_exports.push((
rcstr!("default"),
EsmExport::LocalBinding(
magic_identifier::mangle("default export").into(),
// The expression passed to `export default` cannot be mutated
Liveness::Constant,
),
);
));
if self.analyze_mode.is_code_gen() {
self.analysis.add_code_gen(EsmModuleItem::new(
as_parent_path(ast_path).into(),
@@ -4287,7 +4287,7 @@ impl VisitAstPath for ModuleReferencesVisitor<'_> {
Liveness::Constant,
),
};
self.esm_exports.insert(rcstr!("default"), export);
self.esm_exports.push((rcstr!("default"), export));
}
DefaultDecl::TsInterfaceDecl(..) => {
// ignore
@@ -1,6 +1,5 @@
use std::collections::BTreeMap;
use anyhow::{Result, bail};
use turbo_frozenmap::FrozenMap;
use turbo_tasks::{ResolvedVc, Vc};
use turbo_tasks_fs::{File, FileContent};
use turbopack_core::{
@@ -208,33 +207,28 @@ impl EcmascriptChunkPlaceable for EcmascriptModuleRenameModule {
#[turbo_tasks::function]
async fn get_exports(&self) -> Result<Vc<EcmascriptExports>> {
let reference = self.module_reference().await?;
let mut exports = BTreeMap::new();
match &self.part {
let export = match &self.part {
ModulePart::RenamedExport {
original_export,
export,
} => {
exports.insert(
export.clone(),
EsmExport::ImportedBinding(
ResolvedVc::upcast(reference),
original_export.clone(),
false,
),
);
}
ModulePart::RenamedNamespace { export } => {
exports.insert(
export.clone(),
EsmExport::ImportedNamespace(ResolvedVc::upcast(reference)),
);
}
} => (
export.clone(),
EsmExport::ImportedBinding(
ResolvedVc::upcast(reference),
original_export.clone(),
false,
),
),
ModulePart::RenamedNamespace { export } => (
export.clone(),
EsmExport::ImportedNamespace(ResolvedVc::upcast(reference)),
),
_ => bail!("Unexpected ModulePart for EcmascriptModuleRenameModule"),
}
};
let exports = EsmExports {
exports,
exports: FrozenMap::from_unique_sorted_box(Box::new([export])),
star_exports: Vec::new(),
}
.resolved_cell();
@@ -1,6 +1,5 @@
use std::collections::BTreeMap;
use anyhow::{Result, bail};
use turbo_frozenmap::FrozenMap;
use turbo_tasks::{ResolvedVc, Vc};
use turbopack_core::{
chunk::{
@@ -189,18 +188,16 @@ impl EcmascriptAnalyzable for EcmascriptModuleFacadeModule {
impl EcmascriptChunkPlaceable for EcmascriptModuleFacadeModule {
#[turbo_tasks::function]
async fn get_exports(&self) -> Result<Vc<EcmascriptExports>> {
let mut exports = BTreeMap::new();
let mut star_exports = Vec::new();
let EcmascriptExports::EsmExports(esm_exports) = &*self.module.get_exports().await? else {
bail!("EcmascriptModuleFacadeModule must only be used on modules with EsmExports");
};
let esm_exports = esm_exports.await?;
let mut exports = Vec::with_capacity(esm_exports.exports.len());
for (name, export) in &esm_exports.exports {
let name = name.clone();
match export {
EsmExport::LocalBinding(_, liveness) => {
exports.insert(
exports.push((
name.clone(),
EsmExport::ImportedBinding(
ResolvedVc::upcast(
@@ -215,27 +212,26 @@ impl EcmascriptChunkPlaceable for EcmascriptModuleFacadeModule {
name,
*liveness == Liveness::Mutable,
),
);
));
}
EsmExport::ImportedNamespace(reference) => {
exports.insert(name, EsmExport::ImportedNamespace(*reference));
exports.push((name, EsmExport::ImportedNamespace(*reference)));
}
EsmExport::ImportedBinding(reference, imported_name, mutable) => {
exports.insert(
exports.push((
name,
EsmExport::ImportedBinding(*reference, imported_name.clone(), *mutable),
);
));
}
EsmExport::Error => {
exports.insert(name, EsmExport::Error);
exports.push((name, EsmExport::Error));
}
}
}
star_exports.extend(esm_exports.star_exports.iter().copied());
let exports = EsmExports {
exports,
star_exports,
exports: FrozenMap::from_unique_sorted_box(exports.into_boxed_slice()),
star_exports: esm_exports.star_exports.clone(),
}
.resolved_cell();
Ok(EcmascriptExports::EsmExports(exports).cell())
@@ -1,6 +1,5 @@
use std::collections::BTreeMap;
use anyhow::{Result, bail};
use turbo_frozenmap::FrozenMap;
use turbo_tasks::{ResolvedVc, Vc};
use turbopack_core::{
chunk::{
@@ -142,7 +141,7 @@ impl EcmascriptChunkPlaceable for EcmascriptModuleLocalsModule {
bail!("EcmascriptModuleLocalsModule must only be used on modules with EsmExports");
};
let esm_exports = exports.await?;
let mut exports = BTreeMap::new();
let mut exports = Vec::new();
for (name, export) in &esm_exports.exports {
match export {
@@ -150,19 +149,19 @@ impl EcmascriptChunkPlaceable for EcmascriptModuleLocalsModule {
// not included in locals module
}
EsmExport::LocalBinding(local_name, liveness) => {
exports.insert(
exports.push((
name.clone(),
EsmExport::LocalBinding(local_name.clone(), *liveness),
);
));
}
EsmExport::Error => {
exports.insert(name.clone(), EsmExport::Error);
exports.push((name.clone(), EsmExport::Error));
}
}
}
let exports = EsmExports {
exports,
exports: FrozenMap::from_unique_sorted_box(exports.into_boxed_slice()),
star_exports: vec![],
}
.resolved_cell();