Skip to main content

turbopack_ecmascript/references/esm/
dynamic.rs

1use anyhow::Result;
2use bincode::{Decode, Encode};
3use swc_core::{
4    common::{DUMMY_SP, util::take::Take},
5    ecma::ast::{CallExpr, Callee, Expr, ExprOrSpread, Lit},
6    quote_expr,
7};
8use turbo_tasks::{
9    NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
10};
11use turbopack_core::{
12    chunk::{ChunkingContext, ChunkingType},
13    environment::ChunkLoading,
14    issue::IssueSource,
15    reference::ModuleReference,
16    reference_type::EcmaScriptModulesReferenceSubType,
17    resolve::{
18        BindingUsage, ExportUsage, ModuleResolveResult, ResolveErrorMode,
19        origin::{ResolveOrigin, ResolveOriginExt},
20        parse::Request,
21    },
22};
23use turbopack_resolve::ecmascript::esm_resolve;
24
25use crate::{
26    analyzer::imports::ImportAnnotations,
27    code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
28    create_visitor,
29    references::{
30        AstPath,
31        pattern_mapping::{PatternMapping, ResolveType},
32    },
33};
34
35#[turbo_tasks::value]
36#[derive(Hash, Debug, ValueToString)]
37#[value_to_string("dynamic import {request}")]
38pub struct EsmAsyncAssetReference {
39    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
40    pub request: ResolvedVc<Request>,
41    pub annotations: ImportAnnotations,
42    pub issue_source: IssueSource,
43    pub error_mode: ResolveErrorMode,
44    pub import_externals: bool,
45    /// The export usage extracted from the dynamic import usage pattern.
46    /// Detected from destructured await, member access on await, .then()
47    /// callback destructuring, or webpackExports/turbopackExports comments.
48    pub export_usage: ExportUsage,
49}
50
51impl EsmAsyncAssetReference {
52    fn get_origin(&self) -> Vc<Box<dyn ResolveOrigin>> {
53        if let Some(transition) = self.annotations.transition() {
54            self.origin.with_transition(transition.into())
55        } else {
56            *self.origin
57        }
58    }
59}
60
61impl EsmAsyncAssetReference {
62    pub fn new(
63        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
64        request: ResolvedVc<Request>,
65        issue_source: IssueSource,
66        annotations: ImportAnnotations,
67        error_mode: ResolveErrorMode,
68        import_externals: bool,
69        export_usage: ExportUsage,
70    ) -> Self {
71        EsmAsyncAssetReference {
72            origin,
73            request,
74            issue_source,
75            annotations,
76            error_mode,
77            import_externals,
78            export_usage,
79        }
80    }
81}
82
83#[turbo_tasks::value_impl]
84impl ModuleReference for EsmAsyncAssetReference {
85    #[turbo_tasks::function]
86    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
87        esm_resolve(
88            self.get_origin().resolve().await?,
89            *self.request,
90            EcmaScriptModulesReferenceSubType::DynamicImport,
91            self.error_mode,
92            Some(self.issue_source),
93        )
94        .await
95    }
96
97    fn chunking_type(&self) -> Option<ChunkingType> {
98        Some(ChunkingType::Async)
99    }
100
101    fn binding_usage(&self) -> BindingUsage {
102        BindingUsage {
103            import: Default::default(),
104            export: self.export_usage.clone(),
105        }
106    }
107}
108
109impl IntoCodeGenReference for EsmAsyncAssetReference {
110    fn into_code_gen_reference(
111        self,
112        path: AstPath,
113    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
114        let reference = self.resolved_cell();
115        (
116            ResolvedVc::upcast(reference),
117            CodeGen::EsmAsyncAssetReferenceCodeGen(EsmAsyncAssetReferenceCodeGen {
118                reference,
119                path,
120            }),
121        )
122    }
123}
124
125#[derive(
126    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
127)]
128pub struct EsmAsyncAssetReferenceCodeGen {
129    path: AstPath,
130    reference: ResolvedVc<EsmAsyncAssetReference>,
131}
132
133impl EsmAsyncAssetReferenceCodeGen {
134    pub async fn code_generation(
135        &self,
136        chunking_context: Vc<Box<dyn ChunkingContext>>,
137    ) -> Result<CodeGeneration> {
138        let reference = self.reference.await?;
139
140        let pm = PatternMapping::resolve_request(
141            *reference.request,
142            *reference.origin,
143            chunking_context,
144            self.reference.resolve_reference(),
145            if matches!(
146                *chunking_context.environment().chunk_loading().await?,
147                ChunkLoading::Edge
148            ) {
149                ResolveType::ChunkItem
150            } else {
151                ResolveType::AsyncChunkLoader
152            },
153        )
154        .await?;
155
156        let import_externals = reference.import_externals;
157
158        let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
159            let old_expr = expr.take();
160            let message = if let Expr::Call(CallExpr { args, .. }) = old_expr {
161                match args.into_iter().next() {
162                    Some(ExprOrSpread {
163                        spread: None,
164                        expr: key_expr,
165                    }) => {
166                        *expr = pm.create_import(*key_expr, import_externals);
167                        return;
168                    }
169                    // These are SWC bugs: https://github.com/swc-project/swc/issues/5394
170                    Some(ExprOrSpread {
171                        spread: Some(_),
172                        expr: _,
173                    }) => "spread operator is illegal in import() expressions.",
174                    _ => "import() expressions require at least 1 argument",
175                }
176            } else {
177                "visitor must be executed on a CallExpr"
178            };
179            let error = quote_expr!(
180                "() => { throw new Error($message); }",
181                message: Expr = Expr::Lit(Lit::Str(message.into()))
182            );
183            *expr = Expr::Call(CallExpr {
184                callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
185                args: vec![ExprOrSpread {
186                    spread: None,
187                    expr: error,
188                }],
189                span: DUMMY_SP,
190                ..Default::default()
191            });
192        });
193
194        Ok(CodeGeneration::visitors(vec![visitor]))
195    }
196}