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