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::{NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat};
9use turbopack_core::{
10    chunk::{ChunkingContext, ChunkingType},
11    issue::IssueSource,
12    module::Module,
13    reference::ModuleReference,
14    reference_type::EcmaScriptModulesReferenceSubType,
15    resolve::{
16        BindingUsage, ExportUsage, ModuleResolveResult, ResolveErrorMode,
17        origin::{ResolveOrigin, ResolveOriginExt},
18        parse::Request,
19    },
20};
21use turbopack_resolve::ecmascript::esm_resolve;
22
23use crate::{
24    analyzer::imports::ImportAnnotations,
25    ast_path_trie::{AstPathId, AstPathTrie, AstPathTrieBuilder},
26    async_chunk::proxy::LazyCompilationProxyModule,
27    chunk::EcmascriptChunkPlaceable,
28    code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
29    create_visitor,
30    references::pattern_mapping::{PatternMapping, ResolveType},
31};
32
33#[turbo_tasks::value]
34#[derive(Hash, Debug, ValueToString)]
35#[value_to_string("dynamic import {request}")]
36pub struct EsmAsyncAssetReference {
37    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
38    pub request: ResolvedVc<Request>,
39    pub issue_source: IssueSource,
40    pub error_mode: ResolveErrorMode,
41    pub import_externals: bool,
42    /// The export usage extracted from the dynamic import usage pattern.
43    /// Detected from destructured await, member access on await, .then()
44    /// callback destructuring, or webpackExports/turbopackExports comments.
45    pub export_usage: ExportUsage,
46    pub resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
47    /// Whether the target is compiled only after its runtime proxy is activated.
48    pub lazy_compilation: bool,
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        lazy_compilation: bool,
63    ) -> Result<Self> {
64        // Apply any annotation-driven transition eagerly so the stored origin is final and the
65        // `annotations` don't need to be retained on the reference.
66        let origin = if let Some(transition) = annotations.transition() {
67            origin
68                .with_transition(transition.into())
69                .await?
70                .to_resolved()
71                .await?
72        } else {
73            origin
74        };
75        Ok(EsmAsyncAssetReference {
76            origin,
77            request,
78            issue_source,
79            error_mode,
80            import_externals,
81            export_usage,
82            resolve_override,
83            lazy_compilation,
84        })
85    }
86}
87
88#[turbo_tasks::value_impl]
89impl ModuleReference for EsmAsyncAssetReference {
90    #[turbo_tasks::function]
91    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
92        if let Some(resolved) = self.resolve_override {
93            if self.lazy_compilation
94                && let Some(module) =
95                    ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(resolved)
96            {
97                let proxy = LazyCompilationProxyModule::new_direct(*module)
98                    .to_resolved()
99                    .await?;
100                return Ok(*ModuleResolveResult::module(ResolvedVc::upcast(proxy)));
101            }
102            return Ok(*ModuleResolveResult::module(resolved));
103        }
104
105        esm_resolve(
106            *self.origin,
107            *self.request,
108            if self.lazy_compilation {
109                EcmaScriptModulesReferenceSubType::LazyDynamicImport
110            } else {
111                EcmaScriptModulesReferenceSubType::DynamicImport
112            },
113            self.error_mode,
114            Some(self.issue_source),
115        )
116        .await
117    }
118
119    fn chunking_type(&self) -> Option<ChunkingType> {
120        Some(ChunkingType::Async)
121    }
122
123    fn binding_usage(&self) -> BindingUsage {
124        BindingUsage {
125            import: Default::default(),
126            export: self.export_usage.clone(),
127        }
128    }
129
130    fn source(&self) -> Option<IssueSource> {
131        Some(self.issue_source)
132    }
133}
134
135impl IntoCodeGenReference for EsmAsyncAssetReference {
136    fn into_reference(self) -> ResolvedVc<Box<dyn ModuleReference>> {
137        ResolvedVc::upcast(self.resolved_cell())
138    }
139
140    fn into_code_gen_reference(
141        self,
142        _trie: &AstPathTrieBuilder,
143        path: AstPathId,
144    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
145        let reference = self.resolved_cell();
146        (
147            ResolvedVc::upcast(reference),
148            CodeGen::EsmAsyncAssetReferenceCodeGen(EsmAsyncAssetReferenceCodeGen {
149                reference,
150                path,
151            }),
152        )
153    }
154}
155
156#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode)]
157pub struct EsmAsyncAssetReferenceCodeGen {
158    path: AstPathId,
159    reference: ResolvedVc<EsmAsyncAssetReference>,
160}
161
162impl EsmAsyncAssetReferenceCodeGen {
163    pub async fn code_generation(
164        &self,
165        trie: &AstPathTrie,
166        chunking_context: Vc<Box<dyn ChunkingContext>>,
167    ) -> Result<CodeGeneration> {
168        let reference = self.reference.await?;
169
170        let pm = PatternMapping::resolve_request(
171            *reference.request,
172            *reference.origin,
173            chunking_context,
174            self.reference.resolve_reference(),
175            if chunking_context.chunk_loading().await?.can_split_async() {
176                ResolveType::AsyncChunkLoader
177            } else {
178                ResolveType::ChunkItem
179            },
180            Some(Vc::upcast(*self.reference)),
181        )
182        .await?;
183
184        let import_externals = reference.import_externals;
185
186        let visitor = create_visitor!(trie, self.path, visit_mut_expr, |expr: &mut Expr| {
187            let old_expr = expr.take();
188            let message = if let Expr::Call(CallExpr { args, .. }) = old_expr {
189                match args.into_iter().next() {
190                    Some(ExprOrSpread {
191                        spread: None,
192                        expr: key_expr,
193                    }) => {
194                        *expr = pm.create_import(*key_expr, import_externals);
195                        return;
196                    }
197                    // These are SWC bugs: https://github.com/swc-project/swc/issues/5394
198                    Some(ExprOrSpread {
199                        spread: Some(_),
200                        expr: _,
201                    }) => "spread operator is illegal in import() expressions.",
202                    _ => "import() expressions require at least 1 argument",
203                }
204            } else {
205                "visitor must be executed on a CallExpr"
206            };
207            let error = quote_expr!(
208                "() => { throw new Error($message); }",
209                message: Expr = Expr::Lit(Lit::Str(message.into()))
210            );
211            *expr = Expr::Call(CallExpr {
212                callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
213                args: vec![ExprOrSpread {
214                    spread: None,
215                    expr: error,
216                }],
217                span: DUMMY_SP,
218                ..Default::default()
219            });
220        });
221
222        Ok(CodeGeneration::visitors(vec![visitor]))
223    }
224}