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_code_gen_reference(
122        self,
123        path: AstPath,
124    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
125        let reference = self.resolved_cell();
126        (
127            ResolvedVc::upcast(reference),
128            CodeGen::EsmAsyncAssetReferenceCodeGen(EsmAsyncAssetReferenceCodeGen {
129                reference,
130                path,
131            }),
132        )
133    }
134}
135
136#[derive(
137    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
138)]
139pub struct EsmAsyncAssetReferenceCodeGen {
140    path: AstPath,
141    reference: ResolvedVc<EsmAsyncAssetReference>,
142}
143
144impl EsmAsyncAssetReferenceCodeGen {
145    pub async fn code_generation(
146        &self,
147        chunking_context: Vc<Box<dyn ChunkingContext>>,
148    ) -> Result<CodeGeneration> {
149        let reference = self.reference.await?;
150
151        let pm = PatternMapping::resolve_request(
152            *reference.request,
153            *reference.origin,
154            chunking_context,
155            self.reference.resolve_reference(),
156            if chunking_context.chunk_loading().await?.can_split_async() {
157                ResolveType::AsyncChunkLoader
158            } else {
159                ResolveType::ChunkItem
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}