Skip to main content

turbopack_ecmascript/references/esm/
export.rs

1use std::{borrow::Cow, collections::BTreeMap, ops::ControlFlow};
2
3use anyhow::{Result, bail};
4use bincode::{Decode, Encode};
5use rustc_hash::FxHashSet;
6use swc_core::{
7    common::{DUMMY_SP, SyntaxContext},
8    ecma::ast::{
9        ArrayLit, AssignTarget, Expr, ExprStmt, Ident, Lit, Number, SimpleAssignTarget, Stmt, Str,
10    },
11    quote, quote_expr,
12};
13use turbo_rcstr::{RcStr, rcstr};
14use turbo_tasks::{
15    FxIndexMap, NonLocalValue, ResolvedVc, TryFlatJoinIterExt, ValueToString, Vc,
16    trace::TraceRawVcs,
17};
18use turbopack_core::{
19    chunk::{ChunkingContext, ModuleChunkItemIdExt},
20    ident::AssetIdent,
21    issue::{IssueExt, IssueSeverity, StyledString, analyze::AnalyzeIssue},
22    module::{Module, ModuleSideEffects},
23    module_graph::binding_usage_info::ModuleExportUsageInfo,
24    reference::ModuleReference,
25    resolve::ModulePart,
26};
27
28use crate::{
29    EcmascriptModuleAsset, ScopeHoistingContext,
30    analyzer::graph::EvalContext,
31    chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
32    code_gen::{CodeGeneration, CodeGenerationHoistedStmt},
33    magic_identifier,
34    references::esm::base::ReferencedAsset,
35    runtime_functions::{TURBOPACK_DYNAMIC, TURBOPACK_ESM},
36    tree_shake::asset::EcmascriptModulePartAsset,
37    utils::module_id_to_lit,
38};
39
40/// Models the 'liveness' of an esm export
41/// All ESM exports are technically live but many never change and we can optimize representation to
42/// support that, this enum tracks the actual behavior of the export binding.
43#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
44pub enum Liveness {
45    // The binding never changes after module evaluation
46    Constant,
47    // The binding may change after module evaluation
48    Live,
49    // The binding needs to be exposed as mutable to callers.  This isn't part of the spec but is
50    // part of our module-fragments optimization where we split modules into parts and preserve
51    // mutability of variables via mutable exports.
52    Mutable,
53}
54
55#[derive(Clone, Hash, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
56pub enum EsmExport {
57    /// A local binding that is exported (export { a } or export const a = 1)
58    ///
59    /// Fields: (local_name, liveness)
60    LocalBinding(RcStr, Liveness),
61    /// An imported binding that is exported (export { a as b } from "...")
62    ///
63    /// Fields: (module_reference, name, is_mutable)
64    ImportedBinding(ResolvedVc<Box<dyn ModuleReference>>, RcStr, bool),
65    /// An imported namespace that is exported (export * from "...")
66    ImportedNamespace(ResolvedVc<Box<dyn ModuleReference>>),
67    /// An error occurred while resolving the export
68    Error,
69}
70
71#[turbo_tasks::function]
72pub async fn is_export_missing(
73    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
74    export_name: RcStr,
75) -> Result<Vc<bool>> {
76    if export_name == "__turbopack_module_id__" {
77        return Ok(Vc::cell(false));
78    }
79
80    let exports = module.get_exports().await?;
81    let exports = match &*exports {
82        EcmascriptExports::None => return Ok(Vc::cell(true)),
83        EcmascriptExports::Unknown => return Ok(Vc::cell(false)),
84        EcmascriptExports::Value => return Ok(Vc::cell(false)),
85        EcmascriptExports::CommonJs => return Ok(Vc::cell(false)),
86        EcmascriptExports::EmptyCommonJs => return Ok(Vc::cell(export_name != "default")),
87        EcmascriptExports::DynamicNamespace => return Ok(Vc::cell(false)),
88        EcmascriptExports::EsmExports(exports) => *exports,
89    };
90
91    let exports = exports.await?;
92    if exports.exports.contains_key(&export_name) {
93        return Ok(Vc::cell(false));
94    }
95    if export_name == "default" {
96        return Ok(Vc::cell(true));
97    }
98
99    if exports.star_exports.is_empty() {
100        return Ok(Vc::cell(true));
101    }
102
103    let all_export_names = get_all_export_names(*module).await?;
104    if all_export_names.esm_exports.contains_key(&export_name) {
105        return Ok(Vc::cell(false));
106    }
107
108    for &dynamic_module in &all_export_names.dynamic_exporting_modules {
109        let exports = dynamic_module.get_exports().await?;
110        match &*exports {
111            EcmascriptExports::Value
112            | EcmascriptExports::CommonJs
113            | EcmascriptExports::DynamicNamespace
114            | EcmascriptExports::Unknown => {
115                return Ok(Vc::cell(false));
116            }
117            EcmascriptExports::None
118            | EcmascriptExports::EmptyCommonJs
119            | EcmascriptExports::EsmExports(_) => {}
120        }
121    }
122
123    Ok(Vc::cell(true))
124}
125
126#[turbo_tasks::function]
127pub async fn all_known_export_names(
128    module: Vc<Box<dyn EcmascriptChunkPlaceable>>,
129) -> Result<Vc<Vec<RcStr>>> {
130    let export_names = get_all_export_names(module).await?;
131    Ok(Vc::cell(export_names.esm_exports.keys().cloned().collect()))
132}
133
134#[derive(Copy, Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
135pub enum FoundExportType {
136    Found,
137    Dynamic,
138    NotFound,
139    SideEffects,
140    Unknown,
141}
142
143#[turbo_tasks::value]
144pub struct FollowExportsResult {
145    pub module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
146    pub export_name: Option<RcStr>,
147    pub ty: FoundExportType,
148}
149
150#[turbo_tasks::function]
151pub async fn follow_reexports(
152    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
153    export_name: RcStr,
154    ignore_side_effect_of_entry: bool,
155) -> Result<Vc<FollowExportsResult>> {
156    let mut ignore_side_effects = ignore_side_effect_of_entry;
157
158    let mut module = module;
159    let mut export_name = export_name;
160    loop {
161        let exports = module.get_exports().await?;
162        let EcmascriptExports::EsmExports(exports) = &*exports else {
163            return Ok(FollowExportsResult::cell(FollowExportsResult {
164                module,
165                export_name: Some(export_name),
166                ty: FoundExportType::Dynamic,
167            }));
168        };
169
170        if !ignore_side_effects
171            && *module.side_effects().await? != ModuleSideEffects::SideEffectFree
172        {
173            // TODO It's unfortunate that we have to use the whole module here.
174            // This is often the Facade module, which includes all reexports.
175            // Often we could use Locals + the followed reexports instead.
176            return Ok(FollowExportsResult::cell(FollowExportsResult {
177                module,
178                export_name: Some(export_name),
179                ty: FoundExportType::SideEffects,
180            }));
181        }
182        ignore_side_effects = false;
183
184        // Try to find the export in the local exports
185        let exports_ref = exports.await?;
186        if let Some(export) = exports_ref.exports.get(&export_name) {
187            match handle_declared_export(module, export_name, export).await? {
188                ControlFlow::Continue((m, n)) => {
189                    module = m.to_resolved().await?;
190                    export_name = n;
191                    continue;
192                }
193                ControlFlow::Break(result) => {
194                    return Ok(result.cell());
195                }
196            }
197        }
198
199        // Try to find the export in the star exports
200        if !exports_ref.star_exports.is_empty() && &*export_name != "default" {
201            let result = find_export_from_reexports(*module, export_name.clone()).await?;
202            if let Some(m) = result.esm_export {
203                module = m;
204                continue;
205            }
206            return match &result.dynamic_exporting_modules[..] {
207                [] => Ok(FollowExportsResult {
208                    module,
209                    export_name: Some(export_name),
210                    ty: FoundExportType::NotFound,
211                }
212                .cell()),
213                [module] => Ok(FollowExportsResult {
214                    module: *module,
215                    export_name: Some(export_name),
216                    ty: FoundExportType::Dynamic,
217                }
218                .cell()),
219                _ => Ok(FollowExportsResult {
220                    module,
221                    export_name: Some(export_name),
222                    ty: FoundExportType::Dynamic,
223                }
224                .cell()),
225            };
226        }
227
228        return Ok(FollowExportsResult::cell(FollowExportsResult {
229            module,
230            export_name: Some(export_name),
231            ty: FoundExportType::NotFound,
232        }));
233    }
234}
235
236async fn handle_declared_export(
237    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
238    export_name: RcStr,
239    export: &EsmExport,
240) -> Result<ControlFlow<FollowExportsResult, (Vc<Box<dyn EcmascriptChunkPlaceable>>, RcStr)>> {
241    match export {
242        EsmExport::ImportedBinding(reference, name, _) => {
243            if let ReferencedAsset::Some(module) =
244                *ReferencedAsset::from_resolve_result(reference.resolve_reference()).await?
245            {
246                return Ok(ControlFlow::Continue((*module, name.clone())));
247            }
248        }
249        EsmExport::ImportedNamespace(reference) => {
250            if let ReferencedAsset::Some(module) =
251                *ReferencedAsset::from_resolve_result(reference.resolve_reference()).await?
252            {
253                return Ok(ControlFlow::Break(FollowExportsResult {
254                    module,
255                    export_name: None,
256                    ty: FoundExportType::Found,
257                }));
258            }
259        }
260        EsmExport::LocalBinding(..) => {
261            return Ok(ControlFlow::Break(FollowExportsResult {
262                module,
263                export_name: Some(export_name),
264                ty: FoundExportType::Found,
265            }));
266        }
267        EsmExport::Error => {
268            return Ok(ControlFlow::Break(FollowExportsResult {
269                module,
270                export_name: Some(export_name),
271                ty: FoundExportType::Unknown,
272            }));
273        }
274    }
275    Ok(ControlFlow::Break(FollowExportsResult {
276        module,
277        export_name: Some(export_name),
278        ty: FoundExportType::Unknown,
279    }))
280}
281
282#[turbo_tasks::value]
283struct FindExportFromReexportsResult {
284    esm_export: Option<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
285    dynamic_exporting_modules: Vec<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
286}
287
288#[turbo_tasks::function]
289async fn find_export_from_reexports(
290    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
291    export_name: RcStr,
292) -> Result<Vc<FindExportFromReexportsResult>> {
293    // TODO why do we need a special case for this?
294    if let Some(module) = ResolvedVc::try_downcast_type::<EcmascriptModulePartAsset>(module)
295        && matches!(module.await?.part, ModulePart::Exports)
296    {
297        let module_part = EcmascriptModulePartAsset::select_part(
298            *module.await?.full_module,
299            ModulePart::export(export_name.clone()),
300        );
301
302        // If we apply this logic to EcmascriptModuleAsset, we will resolve everything in the
303        // target module.
304        if (ResolvedVc::try_downcast_type::<EcmascriptModuleAsset>(
305            module_part.to_resolved().await?,
306        ))
307        .is_none()
308        {
309            return Ok(find_export_from_reexports(module_part, export_name));
310        }
311    }
312
313    let all_export_names = get_all_export_names(*module).await?;
314    let esm_export = all_export_names.esm_exports.get(&export_name).copied();
315    Ok(FindExportFromReexportsResult {
316        esm_export,
317        dynamic_exporting_modules: all_export_names.dynamic_exporting_modules.clone(),
318    }
319    .cell())
320}
321
322#[turbo_tasks::value]
323struct AllExportNamesResult {
324    #[bincode(with = "turbo_bincode::indexmap")]
325    esm_exports: FxIndexMap<RcStr, ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
326    dynamic_exporting_modules: Vec<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
327}
328
329#[turbo_tasks::function]
330async fn get_all_export_names(
331    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
332) -> Result<Vc<AllExportNamesResult>> {
333    let exports = module.get_exports().await?;
334    let EcmascriptExports::EsmExports(exports) = &*exports else {
335        return Ok(AllExportNamesResult {
336            esm_exports: FxIndexMap::default(),
337            dynamic_exporting_modules: vec![module],
338        }
339        .cell());
340    };
341
342    let exports = exports.await?;
343    let mut esm_exports = FxIndexMap::default();
344    let mut dynamic_exporting_modules = Vec::new();
345    esm_exports.extend(exports.exports.keys().cloned().map(|n| (n, module)));
346    let star_export_names = exports
347        .star_exports
348        .iter()
349        .map(|esm_ref| async {
350            Ok(
351                if let ReferencedAsset::Some(m) =
352                    *ReferencedAsset::from_resolve_result(esm_ref.resolve_reference()).await?
353                {
354                    Some(expand_star_exports(*m))
355                } else {
356                    None
357                },
358            )
359        })
360        .try_flat_join()
361        .await?;
362    for star_export_names in star_export_names {
363        let star_export_names = star_export_names.await?;
364        esm_exports.extend(
365            star_export_names
366                .esm_exports
367                .iter()
368                .map(|(k, &v)| (k.clone(), v)),
369        );
370        dynamic_exporting_modules
371            .extend(star_export_names.dynamic_exporting_modules.iter().copied());
372    }
373
374    Ok(AllExportNamesResult {
375        esm_exports,
376        dynamic_exporting_modules,
377    }
378    .cell())
379}
380
381#[turbo_tasks::value]
382pub struct ExpandStarResult {
383    #[bincode(with = "turbo_bincode::indexmap")]
384    pub esm_exports: FxIndexMap<RcStr, ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
385    pub dynamic_exporting_modules: Vec<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
386}
387
388#[turbo_tasks::function]
389pub async fn expand_star_exports(
390    root_module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
391) -> Result<Vc<ExpandStarResult>> {
392    let mut esm_exports = FxIndexMap::default();
393    let mut dynamic_exporting_modules = Vec::new();
394    let mut checked_modules = FxHashSet::default();
395    checked_modules.insert(root_module);
396    let mut queue = vec![(root_module, root_module.get_exports())];
397    while let Some((asset, exports)) = queue.pop() {
398        match &*exports.await? {
399            EcmascriptExports::EsmExports(exports) => {
400                let exports = exports.await?;
401                for key in exports.exports.keys() {
402                    if key == "default" {
403                        continue;
404                    }
405                    esm_exports.entry(key.clone()).or_insert(asset);
406                }
407                for esm_ref in exports.star_exports.iter() {
408                    if let ReferencedAsset::Some(asset) =
409                        &*ReferencedAsset::from_resolve_result(esm_ref.resolve_reference()).await?
410                        && checked_modules.insert(*asset)
411                    {
412                        queue.push((*asset, asset.get_exports()));
413                    }
414                }
415            }
416            EcmascriptExports::None | EcmascriptExports::EmptyCommonJs => {
417                emit_star_exports_issue(
418                    asset.ident(),
419                    format!(
420                        "export * used with module {} which has no exports\nTypescript only: Did \
421                         you want to export only types with `export type * from \"...\"`?\nNote: \
422                         Using `export type` is more efficient than `export *` as it won't emit \
423                         any runtime code.",
424                        asset.ident().to_string().await?
425                    )
426                    .into(),
427                )
428                .await?
429            }
430            EcmascriptExports::Value => {
431                emit_star_exports_issue(
432                    asset.ident(),
433                    format!(
434                        "export * used with module {} which only has a default export (default \
435                         export is not exported with export *)\nDid you want to use `export {{ \
436                         default }} from \"...\";` instead?",
437                        asset.ident().to_string().await?
438                    )
439                    .into(),
440                )
441                .await?
442            }
443            EcmascriptExports::CommonJs => {
444                dynamic_exporting_modules.push(asset);
445                emit_star_exports_issue(
446                    asset.ident(),
447                    format!(
448                        "export * used with module {} which is a CommonJS module with exports \
449                         only available at runtime\nList all export names manually (`export {{ a, \
450                         b, c }} from \"...\") or rewrite the module to ESM, to avoid the \
451                         additional runtime code.`",
452                        asset.ident().to_string().await?
453                    )
454                    .into(),
455                )
456                .await?;
457            }
458            EcmascriptExports::DynamicNamespace => {
459                dynamic_exporting_modules.push(asset);
460            }
461            EcmascriptExports::Unknown => {
462                // Propagate the Unknown export type to a certain extent.
463                dynamic_exporting_modules.push(asset);
464            }
465        }
466    }
467
468    Ok(ExpandStarResult {
469        esm_exports,
470        dynamic_exporting_modules,
471    }
472    .cell())
473}
474
475async fn emit_star_exports_issue(source_ident: Vc<AssetIdent>, message: RcStr) -> Result<()> {
476    AnalyzeIssue::new(
477        IssueSeverity::Warning,
478        source_ident,
479        Vc::cell(rcstr!("unexpected export *")),
480        StyledString::Text(message).cell(),
481        None,
482        None,
483    )
484    .to_resolved()
485    .await?
486    .emit();
487    Ok(())
488}
489
490#[turbo_tasks::value(shared)]
491#[derive(Hash, Debug)]
492pub struct EsmExports {
493    /// Explicit exports
494    pub exports: BTreeMap<RcStr, EsmExport>,
495    /// Unexpanded `export * from ...` statements (expanded in `expand_star_exports`)
496    pub star_exports: Vec<ResolvedVc<Box<dyn ModuleReference>>>,
497}
498
499/// The expanded version of [`EsmExports`], the `exports` field here includes all exports that could
500/// be expanded from `star_exports`.
501///
502/// [`EsmExports::star_exports`] that could not be (fully) expanded end up in `dynamic_exports`.
503#[turbo_tasks::value(shared)]
504#[derive(Hash, Debug)]
505pub struct ExpandedExports {
506    pub exports: BTreeMap<RcStr, EsmExport>,
507    /// Modules we couldn't analyze all exports of.
508    pub dynamic_exports: Vec<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
509}
510
511#[turbo_tasks::value_impl]
512impl EsmExports {
513    /// Creates an EsmExports that re-exports all exports from another module.
514    /// This is useful for wrapper modules that simply forward all exports.
515    ///
516    /// The resulting exports will have:
517    /// - A default export binding to the module's default
518    /// - A star export that re-exports all named exports
519    #[turbo_tasks::function]
520    pub async fn reexport_including_default(
521        module_reference: Vc<Box<dyn ModuleReference>>,
522    ) -> Result<Vc<EcmascriptExports>> {
523        let module_reference = module_reference.to_resolved().await?;
524        let mut exports = BTreeMap::new();
525        let default = rcstr!("default");
526        exports.insert(
527            default.clone(),
528            EsmExport::ImportedBinding(module_reference, default, false),
529        );
530
531        Ok(EcmascriptExports::EsmExports(
532            EsmExports {
533                exports,
534                star_exports: vec![module_reference],
535            }
536            .resolved_cell(),
537        )
538        .cell())
539    }
540
541    #[turbo_tasks::function]
542    pub async fn expand_exports(
543        &self,
544        export_usage_info: Vc<ModuleExportUsageInfo>,
545    ) -> Result<Vc<ExpandedExports>> {
546        let mut exports: BTreeMap<RcStr, EsmExport> = self.exports.clone();
547        let mut dynamic_exports = vec![];
548        let export_usage_info = export_usage_info.await?;
549
550        if !matches!(*export_usage_info, ModuleExportUsageInfo::All) {
551            exports.retain(|export, _| export_usage_info.is_export_used(export));
552        }
553
554        for &esm_ref in self.star_exports.iter() {
555            // TODO(PACK-2176): we probably need to handle re-exporting from external
556            // modules.
557            let ReferencedAsset::Some(asset) =
558                &*ReferencedAsset::from_resolve_result(esm_ref.resolve_reference()).await?
559            else {
560                continue;
561            };
562
563            let export_info = expand_star_exports(**asset).await?;
564
565            for export in export_info.esm_exports.keys() {
566                if export == "default" {
567                    continue;
568                }
569                if !export_usage_info.is_export_used(export) {
570                    continue;
571                }
572
573                if !exports.contains_key(export) {
574                    exports.insert(
575                        export.clone(),
576                        EsmExport::ImportedBinding(esm_ref, export.clone(), false),
577                    );
578                }
579            }
580
581            if !export_info.dynamic_exporting_modules.is_empty() {
582                dynamic_exports.push(*asset);
583            }
584        }
585
586        Ok(ExpandedExports {
587            exports,
588            dynamic_exports,
589        }
590        .cell())
591    }
592}
593
594impl EsmExports {
595    pub async fn code_generation(
596        self: Vc<Self>,
597        chunking_context: Vc<Box<dyn ChunkingContext>>,
598        scope_hoisting_context: ScopeHoistingContext<'_>,
599        eval_context: &EvalContext,
600        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
601    ) -> Result<CodeGeneration> {
602        let export_usage_info = chunking_context
603            .module_export_usage(*ResolvedVc::upcast(module))
604            .await?;
605        let expanded = self.expand_exports(*export_usage_info.export_usage).await?;
606
607        if scope_hoisting_context.skip_module_exports() && expanded.dynamic_exports.is_empty() {
608            // If the current module is not exposed, no need to generate exports.
609            //
610            // If there are dynamic_exports, we still need to export everything because it wasn't
611            // possible to determine statically where a reexport is coming from which will instead
612            // be handled at runtime via property access, e.g. `export * from "./some-dynamic-cjs"`
613            return Ok(CodeGeneration::empty());
614        }
615
616        let mut dynamic_exports = Vec::<Box<Expr>>::new();
617        {
618            let id = if let Some(module) = scope_hoisting_context.module()
619                && !expanded.dynamic_exports.is_empty()
620            {
621                Some(module.chunk_item_id(chunking_context).await?)
622            } else {
623                None
624            };
625
626            for dynamic_export_asset in &expanded.dynamic_exports {
627                let ident = ReferencedAsset::get_ident_from_placeable(
628                    dynamic_export_asset,
629                    chunking_context,
630                )
631                .await?;
632
633                if let Some(id) = &id {
634                    dynamic_exports.push(quote_expr!(
635                        "$turbopack_dynamic($arg, $id)",
636                        turbopack_dynamic: Expr = TURBOPACK_DYNAMIC.into(),
637                        arg: Expr = Ident::new(ident.into(), DUMMY_SP, Default::default()).into(),
638                        id: Expr = module_id_to_lit(id)
639                    ));
640                } else {
641                    dynamic_exports.push(quote_expr!(
642                        "$turbopack_dynamic($arg)",
643                        turbopack_dynamic: Expr = TURBOPACK_DYNAMIC.into(),
644                        arg: Expr = Ident::new(ident.into(), DUMMY_SP, Default::default()).into()
645                    ));
646                }
647            }
648        }
649
650        #[derive(Eq, PartialEq)]
651        enum ExportBinding {
652            Getter(Expr),
653            GetterSetter(Expr, Expr),
654            Value(Expr),
655            None,
656        }
657
658        let mut getters = Vec::new();
659        for (exported, local) in &expanded.exports {
660            let exprs: ExportBinding = match local {
661                EsmExport::Error => ExportBinding::Getter(quote!(
662                    "(() => { throw new Error(\"Failed binding. See build errors!\"); })" as Expr,
663                )),
664                EsmExport::LocalBinding(name, liveness) => {
665                    // TODO ideally, this information would just be stored in
666                    // EsmExport::LocalBinding and we wouldn't have to re-correlated this
667                    // information with eval_context.imports.exports to get the syntax context.
668                    let binding =
669                        if let Some((local, ctxt)) = eval_context.imports.exports.get(exported) {
670                            Some((Cow::Borrowed(local.as_str()), *ctxt))
671                        } else {
672                            bail!(
673                                "Expected export to be in eval context {:?} {:?}",
674                                exported,
675                                eval_context.imports,
676                            )
677                        };
678                    let (local, ctxt) = binding.unwrap_or_else(|| {
679                        // Fallback, shouldn't happen in practice
680                        (
681                            if name == "default" {
682                                Cow::Owned(magic_identifier::mangle("default export"))
683                            } else {
684                                Cow::Borrowed(name.as_str())
685                            },
686                            SyntaxContext::empty(),
687                        )
688                    });
689
690                    let local = Ident::new(local.into(), DUMMY_SP, ctxt);
691                    match (liveness, export_usage_info.is_circuit_breaker) {
692                        (Liveness::Constant, false) => ExportBinding::Value(Expr::Ident(local)),
693                        // If the value might change or we are a circuit breaker we must bind a
694                        // getter to avoid capturing the value at the wrong time.
695                        (Liveness::Live, _) | (Liveness::Constant, true) => {
696                            ExportBinding::Getter(quote!("() => $local" as Expr, local = local))
697                        }
698                        (Liveness::Mutable, _) => ExportBinding::GetterSetter(
699                            quote!("() => $local" as Expr, local = local.clone()),
700                            quote!(
701                                "($new) => $local = $new" as Expr,
702                                local: AssignTarget = AssignTarget::Simple(local.into()),
703                                new = Ident::new(format!("new_{name}").into(), DUMMY_SP, ctxt),
704                            ),
705                        ),
706                    }
707                }
708                EsmExport::ImportedBinding(esm_ref, name, mutable) => {
709                    let referenced_asset =
710                        ReferencedAsset::from_resolve_result(esm_ref.resolve_reference()).await?;
711                    referenced_asset
712                        .get_ident(chunking_context, Some(name.clone()), scope_hoisting_context)
713                        .await?
714                        .map(|ident| {
715                            let expr = ident.as_expr_individual(DUMMY_SP);
716                            let read_expr = expr.map_either(Expr::from, Expr::from).into_inner();
717                            use crate::references::esm::base::ReferencedAssetIdent;
718                            match &ident {
719                                ReferencedAssetIdent::LocalBinding {ctxt, liveness,.. } => {
720                                    debug_assert!(*mutable == (*liveness == Liveness::Mutable), "If the re-export is mutable, the merged local must be too");
721                                    // If we are re-exporting something but got merged with it we can treat it like a local export
722                                     match (liveness, export_usage_info.is_circuit_breaker) {
723                                        (Liveness::Constant, false) => {
724                                            ExportBinding::Value(read_expr)
725                                        }
726                                        // If the value might change or we are a circuit breaker we must bind a
727                                        // getter to avoid capturing the value at the wrong time.
728                                        (Liveness::Live, _) | (Liveness::Constant, true) => {
729                                            // In the constant case, we could still export as a value if we knew that the module
730                                            // came _before_ us, but we don't at this point.
731                                            ExportBinding::Getter(quote!("() => $local" as Expr, local: Expr = read_expr))
732                                        }
733                                        (Liveness::Mutable, _) => {
734                                            let assign_target = AssignTarget::Simple(
735                                                        ident.as_expr_individual(DUMMY_SP).map_either(|i| SimpleAssignTarget::Ident(i.into()), SimpleAssignTarget::Member).into_inner());
736                                            ExportBinding::GetterSetter(
737                                                quote!("() => $local" as Expr, local: Expr= read_expr.clone()),
738                                                quote!(
739                                                    "($new) => $lhs = $new" as Expr,
740                                                    lhs: AssignTarget = assign_target,
741                                                    new = Ident::new(format!("new_{name}").into(), DUMMY_SP, *ctxt),
742                                                )
743                                            )
744                                        }
745                                    }
746                                },
747                                ReferencedAssetIdent::Module { namespace_ident:_, ctxt:_, export:_ } => {
748                                    // Otherwise we need to bind as a getter to preserve the 'liveness' of the other modules bindings.
749                                    // TODO: If this becomes important it might be faster to use the runtime to copy PropertyDescriptors across modules
750                                    // since that would reduce allocations and optimize access. We could do this by passing the module-id up.
751                                    let getter = quote!("() => $expr" as Expr, expr: Expr = read_expr);
752                                    let assign_target = AssignTarget::Simple(
753                                                    ident.as_expr_individual(DUMMY_SP).map_either(|i| SimpleAssignTarget::Ident(i.into()), SimpleAssignTarget::Member).into_inner());
754                                    if *mutable {
755                                        ExportBinding::GetterSetter(
756                                            getter,
757                                            quote!(
758                                                "($new) => $lhs = $new" as Expr,
759                                                lhs: AssignTarget = assign_target,
760                                                new = Ident::new(
761                                                    format!("new_{name}").into(),
762                                                    DUMMY_SP,
763                                                    Default::default()
764                                                ),
765                                            ))
766                                    } else {
767                                        ExportBinding::Getter(getter)
768                                    }
769                                }
770                            }
771                        }).unwrap_or(ExportBinding::None)
772                }
773                EsmExport::ImportedNamespace(esm_ref) => {
774                    let referenced_asset =
775                        ReferencedAsset::from_resolve_result(esm_ref.resolve_reference()).await?;
776                    referenced_asset
777                        .get_ident(chunking_context, None, scope_hoisting_context)
778                        .await?
779                        .map(|ident| {
780                            let imported = ident.as_expr(DUMMY_SP, false);
781                            if export_usage_info.is_circuit_breaker {
782                                ExportBinding::Getter(quote!(
783                                    "(() => $imported)" as Expr,
784                                    imported: Expr = imported
785                                ))
786                            } else {
787                                ExportBinding::Value(imported)
788                            }
789                        })
790                        .unwrap_or(ExportBinding::None)
791                }
792            };
793            if exprs != ExportBinding::None {
794                getters.push(Some(
795                    Expr::Lit(Lit::Str(Str {
796                        span: DUMMY_SP,
797                        value: exported.as_str().into(),
798                        raw: None,
799                    }))
800                    .into(),
801                ));
802                match exprs {
803                    ExportBinding::Getter(getter) => {
804                        getters.push(Some(getter.into()));
805                    }
806                    ExportBinding::GetterSetter(getter, setter) => {
807                        getters.push(Some(getter.into()));
808                        getters.push(Some(setter.into()));
809                    }
810                    ExportBinding::Value(value) => {
811                        // We need to push a discriminator in this case to make the fact that we are
812                        // binding a value unambiguous to the runtime.
813                        getters.push(Some(Expr::Lit(Lit::Num(Number::from(0))).into()));
814                        getters.push(Some(value.into()));
815                    }
816                    ExportBinding::None => {}
817                };
818            }
819        }
820        let getters = Expr::Array(ArrayLit {
821            span: DUMMY_SP,
822            elems: getters,
823        });
824        let dynamic_stmt = if !dynamic_exports.is_empty() {
825            vec![CodeGenerationHoistedStmt::new(
826                rcstr!("__turbopack_dynamic__"),
827                Stmt::Expr(ExprStmt {
828                    span: DUMMY_SP,
829                    expr: Expr::from_exprs(dynamic_exports),
830                }),
831            )]
832        } else {
833            vec![]
834        };
835
836        let esm_exports = vec![CodeGenerationHoistedStmt::new(
837            rcstr!("__turbopack_esm__"),
838            if let Some(module) = scope_hoisting_context.module() {
839                let id = module.chunk_item_id(chunking_context).await?;
840                quote!("$turbopack_esm($getters, $id);" as Stmt,
841                    turbopack_esm: Expr = TURBOPACK_ESM.into(),
842                    getters: Expr = getters,
843                    id: Expr = module_id_to_lit(&id)
844                )
845            } else {
846                quote!("$turbopack_esm($getters);" as Stmt,
847                    turbopack_esm: Expr = TURBOPACK_ESM.into(),
848                    getters: Expr = getters
849                )
850            },
851        )];
852        // If we are a circuit breaker module we need to expose exports first so they are available
853        // to a cyclic importer otherwise we put them at the bottom of the module factory.
854        Ok(if export_usage_info.is_circuit_breaker {
855            CodeGeneration::new(vec![], dynamic_stmt, esm_exports, vec![], vec![])
856        } else {
857            CodeGeneration::new(vec![], vec![], vec![], dynamic_stmt, esm_exports)
858        })
859    }
860}