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