turbopack_ecmascript/references/esm/
dynamic.rs

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