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_rcstr::RcStr;
9use turbo_tasks::{
10    NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
11};
12use turbopack_core::{
13    chunk::{ChunkableModuleReference, ChunkingContext, ChunkingType, ChunkingTypeOption},
14    environment::ChunkLoading,
15    issue::IssueSource,
16    reference::ModuleReference,
17    reference_type::EcmaScriptModulesReferenceSubType,
18    resolve::{
19        ModuleResolveResult,
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)]
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 in_try: bool,
44    pub import_externals: bool,
45}
46
47impl EsmAsyncAssetReference {
48    fn get_origin(&self) -> Vc<Box<dyn ResolveOrigin>> {
49        if let Some(transition) = self.annotations.transition() {
50            self.origin.with_transition(transition.into())
51        } else {
52            *self.origin
53        }
54    }
55}
56
57impl EsmAsyncAssetReference {
58    pub fn new(
59        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
60        request: ResolvedVc<Request>,
61        issue_source: IssueSource,
62        annotations: ImportAnnotations,
63        in_try: bool,
64        import_externals: bool,
65    ) -> Self {
66        EsmAsyncAssetReference {
67            origin,
68            request,
69            issue_source,
70            annotations,
71            in_try,
72            import_externals,
73        }
74    }
75}
76
77#[turbo_tasks::value_impl]
78impl ModuleReference for EsmAsyncAssetReference {
79    #[turbo_tasks::function]
80    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
81        esm_resolve(
82            self.get_origin().resolve().await?,
83            *self.request,
84            EcmaScriptModulesReferenceSubType::DynamicImport,
85            self.in_try,
86            Some(self.issue_source),
87        )
88        .await
89    }
90}
91
92#[turbo_tasks::value_impl]
93impl ValueToString for EsmAsyncAssetReference {
94    #[turbo_tasks::function]
95    async fn to_string(&self) -> Result<Vc<RcStr>> {
96        Ok(Vc::cell(
97            format!("dynamic import {}", self.request.to_string().await?,).into(),
98        ))
99    }
100}
101
102#[turbo_tasks::value_impl]
103impl ChunkableModuleReference for EsmAsyncAssetReference {
104    #[turbo_tasks::function]
105    fn chunking_type(&self) -> Vc<ChunkingTypeOption> {
106        Vc::cell(Some(ChunkingType::Async))
107    }
108}
109
110impl IntoCodeGenReference for EsmAsyncAssetReference {
111    fn into_code_gen_reference(
112        self,
113        path: AstPath,
114    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
115        let reference = self.resolved_cell();
116        (
117            ResolvedVc::upcast(reference),
118            CodeGen::EsmAsyncAssetReferenceCodeGen(EsmAsyncAssetReferenceCodeGen {
119                reference,
120                path,
121            }),
122        )
123    }
124}
125
126#[derive(
127    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
128)]
129pub struct EsmAsyncAssetReferenceCodeGen {
130    path: AstPath,
131    reference: ResolvedVc<EsmAsyncAssetReference>,
132}
133
134impl EsmAsyncAssetReferenceCodeGen {
135    pub async fn code_generation(
136        &self,
137        chunking_context: Vc<Box<dyn ChunkingContext>>,
138    ) -> Result<CodeGeneration> {
139        let reference = self.reference.await?;
140
141        let pm = PatternMapping::resolve_request(
142            *reference.request,
143            *reference.origin,
144            chunking_context,
145            self.reference.resolve_reference(),
146            if matches!(
147                *chunking_context.environment().chunk_loading().await?,
148                ChunkLoading::Edge
149            ) {
150                ResolveType::ChunkItem
151            } else {
152                ResolveType::AsyncChunkLoader
153            },
154        )
155        .await?;
156
157        let import_externals = reference.import_externals;
158
159        let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
160            let old_expr = expr.take();
161            let message = if let Expr::Call(CallExpr { args, .. }) = old_expr {
162                match args.into_iter().next() {
163                    Some(ExprOrSpread {
164                        spread: None,
165                        expr: key_expr,
166                    }) => {
167                        *expr = pm.create_import(*key_expr, import_externals);
168                        return;
169                    }
170                    // These are SWC bugs: https://github.com/swc-project/swc/issues/5394
171                    Some(ExprOrSpread {
172                        spread: Some(_),
173                        expr: _,
174                    }) => "spread operator is illegal in import() expressions.",
175                    _ => "import() expressions require at least 1 argument",
176                }
177            } else {
178                "visitor must be executed on a CallExpr"
179            };
180            let error = quote_expr!(
181                "() => { throw new Error($message); }",
182                message: Expr = Expr::Lit(Lit::Str(message.into()))
183            );
184            *expr = Expr::Call(CallExpr {
185                callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
186                args: vec![ExprOrSpread {
187                    spread: None,
188                    expr: error,
189                }],
190                span: DUMMY_SP,
191                ..Default::default()
192            });
193        });
194
195        Ok(CodeGeneration::visitors(vec![visitor]))
196    }
197}