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, Value, ValueToString, Vc, debug::ValueDebugFormat,
11    trace::TraceRawVcs,
12};
13use turbopack_core::{
14    chunk::{ChunkableModuleReference, ChunkingContext, ChunkingType, ChunkingTypeOption},
15    environment::ChunkLoading,
16    issue::IssueSource,
17    module_graph::ModuleGraph,
18    reference::ModuleReference,
19    reference_type::EcmaScriptModulesReferenceSubType,
20    resolve::{
21        ModuleResolveResult,
22        origin::{ResolveOrigin, ResolveOriginExt},
23        parse::Request,
24    },
25};
26use turbopack_resolve::ecmascript::esm_resolve;
27
28use super::super::pattern_mapping::{PatternMapping, ResolveType};
29use crate::{
30    analyzer::imports::ImportAnnotations,
31    code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
32    create_visitor,
33    references::AstPath,
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: Value<ImportAnnotations>,
63        in_try: bool,
64        import_externals: bool,
65    ) -> Self {
66        EsmAsyncAssetReference {
67            origin,
68            request,
69            issue_source,
70            annotations: annotations.into_value(),
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            Value::new(EcmaScriptModulesReferenceSubType::DynamicImport),
85            self.in_try,
86            Some(self.issue_source.clone()),
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(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
127pub struct EsmAsyncAssetReferenceCodeGen {
128    path: AstPath,
129    reference: ResolvedVc<EsmAsyncAssetReference>,
130}
131
132impl EsmAsyncAssetReferenceCodeGen {
133    pub async fn code_generation(
134        &self,
135        _module_graph: Vc<ModuleGraph>,
136        chunking_context: Vc<Box<dyn ChunkingContext>>,
137    ) -> Result<CodeGeneration> {
138        let reference = self.reference.await?;
139
140        let pm = PatternMapping::resolve_request(
141            *reference.request,
142            *reference.origin,
143            Vc::upcast(chunking_context),
144            self.reference.resolve_reference(),
145            if matches!(
146                *chunking_context.environment().chunk_loading().await?,
147                ChunkLoading::Edge
148            ) {
149                ResolveType::ChunkItem
150            } else {
151                ResolveType::AsyncChunkLoader
152            },
153        )
154        .await?;
155
156        let import_externals = reference.import_externals;
157
158        let visitor = create_visitor!(self.path, visit_mut_expr(expr: &mut Expr) {
159            let old_expr = expr.take();
160            let message = if let Expr::Call(CallExpr { args, ..}) = old_expr {
161                match args.into_iter().next() {
162                    Some(ExprOrSpread { spread: None, expr: key_expr }) => {
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 { spread: Some(_), expr: _ }) => {
168                        "spread operator is illegal in import() expressions."
169                    }
170                    _ => {
171                        "import() expressions require at least 1 argument"
172                    }
173                }
174            } else {
175                "visitor must be executed on a CallExpr"
176            };
177            let error = quote_expr!(
178                "() => { throw new Error($message); }",
179                message: Expr = Expr::Lit(Lit::Str(message.into()))
180            );
181            *expr = Expr::Call(CallExpr {
182                callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
183                args: vec![ExprOrSpread {
184                    spread: None,
185                    expr: error,
186                }],
187                span: DUMMY_SP,
188               ..Default::default()
189            });
190        });
191
192        Ok(CodeGeneration::visitors(vec![visitor]))
193    }
194}