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