Skip to content
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

perf(turbopack): Do not create module part per a star re-export #70383

Draft
wants to merge 10 commits into
base: kdy1/ts-action-with-client-proxy
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/next-api/src/server_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ async fn parse_actions(module: Vc<Box<dyn Module>>) -> Result<Vc<OptionActionMap
&*module.await?.part.await?,
ModulePart::Evaluation
| ModulePart::Exports
| ModulePart::StarReexports
| ModulePart::Facade
| ModulePart::Internal(..)
) {
Expand Down
5 changes: 4 additions & 1 deletion turbopack/crates/turbopack-core/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,12 @@ impl AssetIdent {
ModulePart::Exports => {
7_u8.deterministic_hash(&mut hasher);
}
ModulePart::Facade => {
ModulePart::StarReexports => {
8_u8.deterministic_hash(&mut hasher);
}
ModulePart::Facade => {
9_u8.deterministic_hash(&mut hasher);
}
}

has_hash = true;
Expand Down
7 changes: 7 additions & 0 deletions turbopack/crates/turbopack-core/src/resolve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2876,6 +2876,8 @@ pub enum ModulePart {
Locals,
/// The whole exports of a module.
Exports,
/// The whole reexports of a module.
StarReexports,
/// A facade of the module behaving like the original, but referencing
/// internal parts.
Facade,
Expand Down Expand Up @@ -2919,6 +2921,10 @@ impl ModulePart {
ModulePart::Exports.cell()
}
#[turbo_tasks::function]
pub fn star_reexports() -> Vc<Self> {
ModulePart::StarReexports.cell()
}
#[turbo_tasks::function]
pub fn facade() -> Vc<Self> {
ModulePart::Facade.cell()
}
Expand All @@ -2945,6 +2951,7 @@ impl ValueToString for ModulePart {
ModulePart::Internal(id) => format!("internal part {}", id,).into(),
ModulePart::Locals => "locals".into(),
ModulePart::Exports => "exports".into(),
ModulePart::StarReexports => "reexports".into(),
ModulePart::Facade => "facade".into(),
}))
}
Expand Down
42 changes: 40 additions & 2 deletions turbopack/crates/turbopack-ecmascript/src/analyzer/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,43 @@ pub(crate) enum ImportedSymbol {
ModuleEvaluation,
Symbol(JsWord),
Exports,
/// We need a separate variant for `export * from 'package-star'`.
///
/// `package-star/index.js`:
///
/// ```
/// export { notCompiled } from "./not-compiled.js";
/// export { notExisting } from "./not-existing.js";
/// export { notExecuted } from "./not-executed.js";
/// export * from "./not-executed.js";
/// export * from "./a.js";
/// export * from "./b.js";
/// export const local = "local";
/// ```
///
///
/// `package-reexport/index.js`:
///
/// ```
/// export * from "package-star";
/// export const outer = "outer";
/// ```
///
/// `import { a } from 'package-reexport'` currently creates `ModulePart::Export("a")` for
/// `package-reexport` and `ModulePart::Exports` for `package-star`.
///
/// To make side effect optimization work, we need to create `ModulePart::Export("a")`for
/// `export * from "package-star"`.
///
/// But we cannot make `ImportAnalyzer` or `EvalContext` take the name of the target symbol,
/// because it will make them parameterized by the name of the target symbol and it's too bad
/// for caching.
///
/// So we need to have a separate variant, and reuse the requested `ModulePart` for reexports.
ReexportAll,
Comment on lines +203 to +236
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need any special fragments or that kind of thing. You need to have a logic to follow reexports in turbopack/lib.rs as it is used for the side effects optimization

Part(u32),
PartEvaluation(u32),
StarReexports,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -402,7 +437,7 @@ impl Visit for Analyzer<'_> {
let i = self.ensure_reference(
export.span,
export.src.value.clone(),
symbol.unwrap_or(ImportedSymbol::Exports),
symbol.unwrap_or(ImportedSymbol::ReexportAll),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks wrong to me. When I export * from "module" I want to reference the Exports part of that because it includes all exports. Why do we need ReexportAll at all?

annotations,
);
if let Some(i) = i {
Expand Down Expand Up @@ -586,12 +621,15 @@ pub(crate) fn orig_name(n: &ModuleExportName) -> JsWord {
}

fn parse_with(with: Option<&ObjectLit>) -> Option<ImportedSymbol> {
find_turbopack_part_id_in_asserts(with?).map(|v| match v {
let id = find_turbopack_part_id_in_asserts(with?)?;

Some(match id {
PartId::Internal(index, true) => ImportedSymbol::PartEvaluation(index),
PartId::Internal(index, false) => ImportedSymbol::Part(index),
PartId::ModuleEvaluation => ImportedSymbol::ModuleEvaluation,
PartId::Export(e) => ImportedSymbol::Symbol(e.as_str().into()),
PartId::Exports => ImportedSymbol::Exports,
PartId::StarReexports => ImportedSymbol::StarReexports,
})
}

Expand Down
14 changes: 8 additions & 6 deletions turbopack/crates/turbopack-ecmascript/src/references/esm/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ impl ModuleReference for EsmAssetReference {
async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
let ty = if matches!(self.annotations.module_type(), Some("json")) {
EcmaScriptModulesReferenceSubType::ImportWithType(ImportWithType::Json)
} else if let Some(part) = &self.export_name {
EcmaScriptModulesReferenceSubType::ImportPart(*part)
} else if let Some(part) = self.export_name {
EcmaScriptModulesReferenceSubType::ImportPart(part)
} else {
EcmaScriptModulesReferenceSubType::Import
};
Expand All @@ -166,10 +166,12 @@ impl ModuleReference for EsmAssetReference {
.await?
.expect("EsmAssetReference origin should be a EcmascriptModuleAsset");

return Ok(ModuleResolveResult::module(
EcmascriptModulePartAsset::select_part(module, part),
)
.cell());
let part_module = *EcmascriptModulePartAsset::select_part(module, part).await?;

return match part_module {
Some(part_module) => Ok(ModuleResolveResult::module(part_module).cell()),
None => Ok(ModuleResolveResult::ignored().cell()),
};
}

bail!("export_name is required for part import")
Expand Down
6 changes: 5 additions & 1 deletion turbopack/crates/turbopack-ecmascript/src/references/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,8 @@ pub(crate) async fn analyse_ecmascript_module_internal(
}
ImportedSymbol::Part(part_id) => Some(ModulePart::internal(*part_id)),
ImportedSymbol::Exports => Some(ModulePart::exports()),
ImportedSymbol::StarReexports => Some(ModulePart::star_reexports()),
ImportedSymbol::ReexportAll => Some(ModulePart::exports()),
},
Some(TreeShakingMode::ReexportsOnly) => match &r.imported_symbol {
ImportedSymbol::ModuleEvaluation => {
Expand All @@ -615,7 +617,9 @@ pub(crate) async fn analyse_ecmascript_module_internal(
ImportedSymbol::PartEvaluation(_) | ImportedSymbol::Part(_) => {
bail!("Internal imports doesn't exist in reexports only mode")
}
ImportedSymbol::Exports => None,
ImportedSymbol::Exports
| ImportedSymbol::ReexportAll
| ImportedSymbol::StarReexports => None,
},
None => {
evaluation_references.push(i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,24 @@ impl Module for EcmascriptModuleFacadeModule {
self.module,
ModulePart::locals(),
)));
references.push(Vc::upcast(EcmascriptModulePartReference::new_part(
self.module,
ModulePart::star_reexports(),
)));
references
}
ModulePart::StarReexports { .. } => {
let Some(module) =
Vc::try_resolve_sidecast::<Box<dyn EcmascriptAnalyzable>>(self.module).await?
else {
bail!(
"Expected EcmascriptModuleAsset for a EcmascriptModuleFacadeModule with \
ModulePart::Evaluation"
);
};
let result = module.analyze().await?;
result.reexport_references.await?.clone_value()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that only star reexports or all reexports?

}
ModulePart::Facade => {
vec![
Vc::upcast(EcmascriptModulePartReference::new_part(
Expand Down Expand Up @@ -194,6 +210,16 @@ impl EcmascriptChunkPlaceable for EcmascriptModuleFacadeModule {
}
star_exports.extend(esm_exports.star_exports.iter().copied());
}
ModulePart::StarReexports => {
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?;
star_exports.extend(esm_exports.star_exports.iter().copied());
}
ModulePart::Facade => {
// Reexport everything from the reexports module
// (including default export if any)
Expand Down Expand Up @@ -268,6 +294,7 @@ impl EcmascriptChunkPlaceable for EcmascriptModuleFacadeModule {
.module
.is_marked_as_side_effect_free(side_effect_free_packages),
ModulePart::Exports
| ModulePart::StarReexports
| ModulePart::RenamedExport { .. }
| ModulePart::RenamedNamespace { .. } => Vc::cell(true),
_ => bail!("Unexpected ModulePart for EcmascriptModuleFacadeModule"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ impl ModuleReference for EcmascriptModulePartReference {
| ModulePart::Evaluation
| ModulePart::Facade
| ModulePart::RenamedExport { .. }
| ModulePart::RenamedNamespace { .. } => {
| ModulePart::RenamedNamespace { .. }
| ModulePart::StarReexports => {
Vc::upcast(EcmascriptModuleFacadeModule::new(self.module, part))
}
ModulePart::Export(..) | ModulePart::Internal(..) => {
Expand Down
33 changes: 23 additions & 10 deletions turbopack/crates/turbopack-ecmascript/src/tree_shake/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use turbopack_core::{
asset::{Asset, AssetContent},
chunk::{AsyncModuleInfo, ChunkableModule, ChunkingContext, EvaluatableAsset},
ident::AssetIdent,
module::Module,
module::{Module, OptionModule},
reference::{ModuleReference, ModuleReferences, SingleModuleReference},
resolve::ModulePart,
};
Expand Down Expand Up @@ -92,24 +92,34 @@ impl EcmascriptModulePartAsset {
.cell()
}

/// Returns `None` only if the part is a proxied export. (Which is allowed to not exist)
#[turbo_tasks::function]
pub async fn select_part(
module: Vc<EcmascriptModuleAsset>,
part: Vc<ModulePart>,
) -> Result<Vc<Box<dyn Module>>> {
) -> Result<Vc<OptionModule>> {
let split_result = split_module(module).await?;

Ok(if matches!(&*split_result, SplitResult::Failed { .. }) {
Vc::upcast(module)
} else {
Vc::upcast(EcmascriptModulePartAsset::new(module, part))
})
Ok(Vc::cell(
if matches!(&*split_result, SplitResult::Failed { .. }) {
Some(Vc::upcast(module))
} else if matches!(&*part.await?, ModulePart::Export(..)) {
let part_id = get_part_id(&split_result, part).await?;
if part_id.is_some() {
Some(Vc::upcast(EcmascriptModulePartAsset::new(module, part)))
} else {
None
}
} else {
Some(Vc::upcast(EcmascriptModulePartAsset::new(module, part)))
},
))
}

#[turbo_tasks::function]
pub async fn is_async_module(self: Vc<Self>) -> Result<Vc<bool>> {
let this = self.await?;
let result = this.full_module.analyze();
let result = analyse_ecmascript_module(this.full_module, Some(this.part));

if let Some(async_module) = *result.await?.async_module.await? {
Ok(async_module.is_self_async(self.references()))
Expand Down Expand Up @@ -160,9 +170,12 @@ impl Module for EcmascriptModulePartAsset {
}

let deps = {
let part_id = get_part_id(&split_data, self.part)
let Some(part_id) = get_part_id(&split_data, self.part)
.await
.with_context(|| format!("part {:?} is not found in the module", self.part))?;
.with_context(|| format!("part {:?} is not found in the module", self.part))?
else {
return Ok(analyze.references);
};

match deps.get(&part_id) {
Some(v) => &**v,
Expand Down
Loading
Loading