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