turbopack_ecmascript/references/esm/
export.rs

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