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 .resolved_cell();
146
147 import_references.push(reference);
148 if should_add_evaluation {
149 esm_evaluation_reference_idxs.push(i);
150 }
151 }
152 anyhow::Ok(import_references)
153 }
154 .instrument(span)
155 .await?;
156
157 let span = tracing::trace_span!("exports");
158 let exports = async {
159 let esm_star_exports: Vec<ResolvedVc<Box<dyn ModuleReference>>> = eval_context
160 .imports
161 .reexport_namespaces()
162 .map(|i| ResolvedVc::upcast(import_references[i]))
163 .collect();
164 let esm_exports = eval_context
165 .imports
166 .as_esm_exports(&import_references, eval_context)?;
167
168 for idx in eval_context.imports.reexports_reference_idxs() {
169 esm_reexport_reference_idxs.push(idx);
170 }
171
172 anyhow::Ok(
173 if !esm_exports.is_empty() || !esm_star_exports.is_empty() {
174 if specified_type == SpecifiedModuleType::CommonJs {
175 SpecifiedModuleTypeIssue {
176 source: IssueSource::from_source_only(source),
178 specified_type,
179 }
180 .resolved_cell()
181 .emit();
182 }
183
184 let esm_exports = EsmExports {
185 exports: esm_exports,
186 star_exports: esm_star_exports,
187 }
188 .cell();
189
190 EcmascriptExports::EsmExports(esm_exports.to_resolved().await?)
191 } else if specified_type == SpecifiedModuleType::EcmaScript {
192 match detect_dynamic_export(program) {
193 DetectedDynamicExportType::CommonJs => {
194 SpecifiedModuleTypeIssue {
195 source: IssueSource::from_source_only(source),
198 specified_type,
199 }
200 .resolved_cell()
201 .emit();
202
203 EcmascriptExports::EsmExports(
204 EsmExports {
205 exports: Default::default(),
206 star_exports: Default::default(),
207 }
208 .resolved_cell(),
209 )
210 }
211 DetectedDynamicExportType::Namespace => EcmascriptExports::DynamicNamespace,
212 DetectedDynamicExportType::Value => EcmascriptExports::Value,
213 DetectedDynamicExportType::UsingModuleDeclarations
214 | DetectedDynamicExportType::None => EcmascriptExports::EsmExports(
215 EsmExports {
216 exports: Default::default(),
217 star_exports: Default::default(),
218 }
219 .resolved_cell(),
220 ),
221 }
222 } else {
223 match detect_dynamic_export(program) {
224 DetectedDynamicExportType::CommonJs => EcmascriptExports::CommonJs,
225 DetectedDynamicExportType::Namespace => EcmascriptExports::DynamicNamespace,
226 DetectedDynamicExportType::Value => EcmascriptExports::Value,
227 DetectedDynamicExportType::UsingModuleDeclarations => {
228 EcmascriptExports::EsmExports(
229 EsmExports {
230 exports: Default::default(),
231 star_exports: Default::default(),
232 }
233 .resolved_cell(),
234 )
235 }
236 DetectedDynamicExportType::None => EcmascriptExports::EmptyCommonJs,
237 }
238 }
239 .resolved_cell(),
240 )
241 }
242 .instrument(span)
243 .await?;
244
245 Ok(EcmascriptExportsAnalysis {
246 exports,
247 import_references: import_references.into_boxed_slice(),
248 esm_reexport_reference_idxs: esm_reexport_reference_idxs.into_boxed_slice(),
249 esm_evaluation_reference_idxs: esm_evaluation_reference_idxs.into_boxed_slice(),
250 }
251 .cell())
252}
253
254#[derive(Debug)]
255enum DetectedDynamicExportType {
256 CommonJs,
257 Namespace,
258 Value,
259 None,
260 UsingModuleDeclarations,
261}
262
263fn detect_dynamic_export(p: &Program) -> DetectedDynamicExportType {
265 use swc_core::ecma::visit::{Visit, VisitWith, visit_obj_and_computed};
266
267 if let Program::Module(m) = p {
268 if m.body.iter().any(|item| {
270 item.as_module_decl().is_some_and(|module_decl| {
271 module_decl.as_import().is_none_or(|import| {
272 !is_turbopack_helper_import(import) && !is_swc_helper_import(import)
273 })
274 })
275 }) {
276 return DetectedDynamicExportType::UsingModuleDeclarations;
277 }
278 }
279
280 struct Visitor {
281 cjs: bool,
282 value: bool,
283 namespace: bool,
284 found: bool,
285 }
286
287 impl Visit for Visitor {
288 visit_obj_and_computed!();
289
290 fn visit_ident(&mut self, i: &Ident) {
291 if &*i.sym == "module" || &*i.sym == "exports" {
296 self.cjs = true;
297 self.found = true;
298 }
299 if &*i.sym == "__turbopack_export_value__" {
300 self.value = true;
301 self.found = true;
302 }
303 if &*i.sym == "__turbopack_export_namespace__" {
304 self.namespace = true;
305 self.found = true;
306 }
307 }
308
309 fn visit_expr(&mut self, n: &Expr) {
310 if self.found {
311 return;
312 }
313
314 if let Expr::Member(member) = n
315 && member.obj.is_ident_ref_to("__turbopack_context__")
316 && let MemberProp::Ident(prop) = &member.prop
317 {
318 const TURBOPACK_EXPORT_VALUE_SHORTCUT: &str = TURBOPACK_EXPORT_VALUE.shortcut;
319 const TURBOPACK_EXPORT_NAMESPACE_SHORTCUT: &str =
320 TURBOPACK_EXPORT_NAMESPACE.shortcut;
321 match &*prop.sym {
322 TURBOPACK_EXPORT_VALUE_SHORTCUT => {
323 self.value = true;
324 self.found = true;
325 }
326 TURBOPACK_EXPORT_NAMESPACE_SHORTCUT => {
327 self.namespace = true;
328 self.found = true;
329 }
330 _ => {}
331 }
332 }
333
334 n.visit_children_with(self);
335 }
336
337 fn visit_stmt(&mut self, n: &Stmt) {
338 if self.found {
339 return;
340 }
341 n.visit_children_with(self);
342 }
343 }
344
345 let mut v = Visitor {
346 cjs: false,
347 value: false,
348 namespace: false,
349 found: false,
350 };
351 p.visit_with(&mut v);
352 if v.cjs {
353 DetectedDynamicExportType::CommonJs
354 } else if v.value {
355 DetectedDynamicExportType::Value
356 } else if v.namespace {
357 DetectedDynamicExportType::Namespace
358 } else {
359 DetectedDynamicExportType::None
360 }
361}
362
363pub fn is_turbopack_helper_import(import: &ImportDecl) -> bool {
364 let annotations = ImportAnnotations::parse(import.with.as_deref());
365
366 annotations.is_some_and(|a| a.get(&TURBOPACK_HELPER_WTF8).is_some())
367}
368
369pub fn is_swc_helper_import(import: &ImportDecl) -> bool {
370 import.src.value.starts_with("@swc/helpers/")
371}