turbopack_ecmascript/references/esm/
dynamic.rs1use 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 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}