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, 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 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}