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