turbopack_ecmascript/references/
exports.rs1use anyhow::{Result, bail};
2use swc_core::{
3 common::source_map::SmallPos,
4 ecma::ast::{Expr, Ident, ImportDecl, MemberProp, Program, Stmt},
5};
6use tracing::Instrument;
7use turbo_rcstr::RcStr;
8use turbo_tasks::{ResolvedVc, Vc};
9use turbopack_core::{
10 issue::{IssueExt, IssueSource},
11 reference::ModuleReference,
12 resolve::ModulePart,
13};
14
15use crate::{
16 EcmascriptModuleAsset, EcmascriptParsable, ModuleTypeResult, SpecifiedModuleType,
17 TreeShakingMode,
18 analyzer::imports::{ImportAnnotations, ImportedSymbol},
19 chunk::EcmascriptExports,
20 parse::ParseResult,
21 references::{
22 TURBOPACK_HELPER_WTF8,
23 esm::{EsmAssetReference, EsmExports},
24 type_issue::SpecifiedModuleTypeIssue,
25 },
26 runtime_functions::{TURBOPACK_EXPORT_NAMESPACE, TURBOPACK_EXPORT_VALUE},
27 tree_shake::{part_of_module, split_module},
28};
29
30#[turbo_tasks::value]
31pub struct EcmascriptExportsAnalysis {
32 pub exports: ResolvedVc<EcmascriptExports>,
33 pub import_references: Box<[ResolvedVc<EsmAssetReference>]>,
34 pub esm_reexport_reference_idxs: Box<[usize]>,
35 pub esm_evaluation_reference_idxs: Box<[usize]>,
36}
37
38#[turbo_tasks::function]
39pub async fn compute_ecmascript_module_exports(
40 module: ResolvedVc<EcmascriptModuleAsset>,
41 part: Option<ModulePart>,
42) -> Result<Vc<EcmascriptExportsAnalysis>> {
43 let raw_module = module.await?;
44 let source = raw_module.source;
45 let options = raw_module.options.await?;
46 let import_externals = options.import_externals;
47
48 let parsed = if let Some(part) = part {
49 let split_data = split_module(*module);
50 part_of_module(split_data, part)
51 } else {
52 module.failsafe_parse()
53 };
54
55 let parsed = parsed.await?;
56 let ParseResult::Ok {
57 program,
58 eval_context,
59 ..
60 } = &*parsed
61 else {
62 return Ok(EcmascriptExportsAnalysis {
63 exports: EcmascriptExports::Unknown.resolved_cell(),
64 import_references: Box::new([]),
65 esm_reexport_reference_idxs: Box::new([]),
66 esm_evaluation_reference_idxs: Box::new([]),
67 }
68 .cell());
69 };
70
71 let ModuleTypeResult {
72 module_type: specified_type,
73 ..
74 } = *module.determine_module_type().await?;
75
76 let inner_assets = if let Some(assets) = raw_module.inner_assets {
77 Some(assets.await?)
78 } else {
79 None
80 };
81
82 let mut esm_reexport_reference_idxs: Vec<usize> = vec![];
83 let mut esm_evaluation_reference_idxs: Vec<usize> = vec![];
84
85 let span = tracing::trace_span!("esm import references");
86 let import_references = async {
87 let mut import_references = Vec::with_capacity(eval_context.imports.references().len());
88 for (i, r) in eval_context.imports.references().enumerate() {
89 let mut should_add_evaluation = false;
90
91 let resolve_override = if let Some(inner_assets) = &inner_assets
92 && let Some(req) = r.module_path.as_str()
93 && let Some(a) = inner_assets.get(req)
94 {
95 Some(*a)
96 } else {
97 None
98 };
99
100 let reference = EsmAssetReference::new(
101 module,
102 ResolvedVc::upcast(module),
103 RcStr::from(&*r.module_path.to_string_lossy()),
104 IssueSource::from_swc_offsets(source, r.span.lo.to_u32(), r.span.hi.to_u32()),
105 r.annotations.as_ref().map(|a| (**a).clone()),
106 match &r.imported_symbol {
107 &ImportedSymbol::ModuleEvaluation => {
108 should_add_evaluation = true;
109 Some(ModulePart::evaluation())
110 }
111 ImportedSymbol::Symbol(name) => Some(ModulePart::export((&**name).into())),
112 ImportedSymbol::PartEvaluation(part_id) | ImportedSymbol::Part(part_id) => {
113 if !matches!(
114 options.tree_shaking_mode,
115 Some(TreeShakingMode::ModuleFragments)
116 ) {
117 bail!(
118 "Internal imports only exist in reexports only mode when \
119 importing {:?} from {}",
120 r.imported_symbol,
121 r.module_path.to_string_lossy()
122 );
123 }
124 if matches!(&r.imported_symbol, ImportedSymbol::PartEvaluation(_)) {
125 should_add_evaluation = true;
126 }
127 Some(ModulePart::internal(*part_id))
128 }
129 ImportedSymbol::Exports => matches!(
130 options.tree_shaking_mode,
131 Some(TreeShakingMode::ModuleFragments)
132 )
133 .then(ModulePart::exports),
134 },
135 eval_context
136 .imports
137 .import_usage
138 .get(&i)
139 .cloned()
140 .unwrap_or_default(),
141 import_externals,
142 options.tree_shaking_mode,
143 resolve_override,
144 )
145 .await?
146 .resolved_cell();
147
148 import_references.push(reference);
149 if should_add_evaluation {
150 esm_evaluation_reference_idxs.push(i);
151 }
152 }
153 anyhow::Ok(import_references)
154 }
155 .instrument(span)
156 .await?;
157
158 let span = tracing::trace_span!("exports");
159 let exports = async {
160 let esm_star_exports: Vec<ResolvedVc<Box<dyn ModuleReference>>> = eval_context
161 .imports
162 .reexport_namespaces()
163 .map(|i| ResolvedVc::upcast(import_references[i]))
164 .collect();
165 let esm_exports = eval_context
166 .imports
167 .as_esm_exports(&import_references, eval_context)?;
168
169 for idx in eval_context.imports.reexports_reference_idxs() {
170 esm_reexport_reference_idxs.push(idx);
171 }
172
173 anyhow::Ok(
174 if !esm_exports.is_empty() || !esm_star_exports.is_empty() {
175 if specified_type == SpecifiedModuleType::CommonJs {
176 SpecifiedModuleTypeIssue {
177 source: IssueSource::from_source_only(source),
179 specified_type,
180 }
181 .resolved_cell()
182 .emit();
183 }
184
185 let esm_exports = EsmExports {
186 exports: esm_exports,
187 star_exports: esm_star_exports,
188 }
189 .cell();
190
191 EcmascriptExports::EsmExports(esm_exports.to_resolved().await?)
192 } else if specified_type == SpecifiedModuleType::EcmaScript {
193 match detect_dynamic_export(program) {
194 DetectedDynamicExportType::CommonJs => {
195 SpecifiedModuleTypeIssue {
196 source: IssueSource::from_source_only(source),
199 specified_type,
200 }
201 .resolved_cell()
202 .emit();
203
204 EcmascriptExports::EsmExports(
205 EsmExports {
206 exports: Default::default(),
207 star_exports: Default::default(),
208 }
209 .resolved_cell(),
210 )
211 }
212 DetectedDynamicExportType::Namespace => EcmascriptExports::DynamicNamespace,
213 DetectedDynamicExportType::Value => EcmascriptExports::Value,
214 DetectedDynamicExportType::UsingModuleDeclarations
215 | DetectedDynamicExportType::None => EcmascriptExports::EsmExports(
216 EsmExports {
217 exports: Default::default(),
218 star_exports: Default::default(),
219 }
220 .resolved_cell(),
221 ),
222 }
223 } else {
224 match detect_dynamic_export(program) {
225 DetectedDynamicExportType::CommonJs => EcmascriptExports::CommonJs,
226 DetectedDynamicExportType::Namespace => EcmascriptExports::DynamicNamespace,
227 DetectedDynamicExportType::Value => EcmascriptExports::Value,
228 DetectedDynamicExportType::UsingModuleDeclarations => {
229 EcmascriptExports::EsmExports(
230 EsmExports {
231 exports: Default::default(),
232 star_exports: Default::default(),
233 }
234 .resolved_cell(),
235 )
236 }
237 DetectedDynamicExportType::None => EcmascriptExports::EmptyCommonJs,
238 }
239 }
240 .resolved_cell(),
241 )
242 }
243 .instrument(span)
244 .await?;
245
246 Ok(EcmascriptExportsAnalysis {
247 exports,
248 import_references: import_references.into_boxed_slice(),
249 esm_reexport_reference_idxs: esm_reexport_reference_idxs.into_boxed_slice(),
250 esm_evaluation_reference_idxs: esm_evaluation_reference_idxs.into_boxed_slice(),
251 }
252 .cell())
253}
254
255#[derive(Debug)]
256enum DetectedDynamicExportType {
257 CommonJs,
258 Namespace,
259 Value,
260 None,
261 UsingModuleDeclarations,
262}
263
264fn detect_dynamic_export(p: &Program) -> DetectedDynamicExportType {
266 use swc_core::ecma::visit::{Visit, VisitWith, visit_obj_and_computed};
267
268 if let Program::Module(m) = p {
269 if m.body.iter().any(|item| {
271 item.as_module_decl().is_some_and(|module_decl| {
272 module_decl.as_import().is_none_or(|import| {
273 !is_turbopack_helper_import(import) && !is_swc_helper_import(import)
274 })
275 })
276 }) {
277 return DetectedDynamicExportType::UsingModuleDeclarations;
278 }
279 }
280
281 struct Visitor {
282 cjs: bool,
283 value: bool,
284 namespace: bool,
285 found: bool,
286 }
287
288 impl Visit for Visitor {
289 visit_obj_and_computed!();
290
291 fn visit_ident(&mut self, i: &Ident) {
292 if &*i.sym == "module" || &*i.sym == "exports" {
297 self.cjs = true;
298 self.found = true;
299 }
300 if &*i.sym == "__turbopack_export_value__" {
301 self.value = true;
302 self.found = true;
303 }
304 if &*i.sym == "__turbopack_export_namespace__" {
305 self.namespace = true;
306 self.found = true;
307 }
308 }
309
310 fn visit_expr(&mut self, n: &Expr) {
311 if self.found {
312 return;
313 }
314
315 if let Expr::Member(member) = n
316 && member.obj.is_ident_ref_to("__turbopack_context__")
317 && let MemberProp::Ident(prop) = &member.prop
318 {
319 const TURBOPACK_EXPORT_VALUE_SHORTCUT: &str = TURBOPACK_EXPORT_VALUE.shortcut;
320 const TURBOPACK_EXPORT_NAMESPACE_SHORTCUT: &str =
321 TURBOPACK_EXPORT_NAMESPACE.shortcut;
322 match &*prop.sym {
323 TURBOPACK_EXPORT_VALUE_SHORTCUT => {
324 self.value = true;
325 self.found = true;
326 }
327 TURBOPACK_EXPORT_NAMESPACE_SHORTCUT => {
328 self.namespace = true;
329 self.found = true;
330 }
331 _ => {}
332 }
333 }
334
335 n.visit_children_with(self);
336 }
337
338 fn visit_stmt(&mut self, n: &Stmt) {
339 if self.found {
340 return;
341 }
342 n.visit_children_with(self);
343 }
344 }
345
346 let mut v = Visitor {
347 cjs: false,
348 value: false,
349 namespace: false,
350 found: false,
351 };
352 p.visit_with(&mut v);
353 if v.cjs {
354 DetectedDynamicExportType::CommonJs
355 } else if v.value {
356 DetectedDynamicExportType::Value
357 } else if v.namespace {
358 DetectedDynamicExportType::Namespace
359 } else {
360 DetectedDynamicExportType::None
361 }
362}
363
364pub fn is_turbopack_helper_import(import: &ImportDecl) -> bool {
365 let annotations = ImportAnnotations::parse(import.with.as_deref());
366
367 annotations.is_some_and(|a| a.get(&TURBOPACK_HELPER_WTF8).is_some())
368}
369
370pub fn is_swc_helper_import(import: &ImportDecl) -> bool {
371 import.src.value.starts_with("@swc/helpers/")
372}