1use std::{fmt::Display, io::Write};
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{NonLocalValue, ResolvedVc, TaskInput, TryJoinIterExt, Vc, trace::TraceRawVcs};
7use turbo_tasks_fs::{FileContent, FileSystem, VirtualFileSystem, glob::Glob, rope::RopeBuilder};
8use turbopack_core::{
9 asset::{Asset, AssetContent},
10 chunk::{AsyncModuleInfo, ChunkItem, ChunkType, ChunkableModule, ChunkingContext},
11 ident::{AssetIdent, Layer},
12 module::Module,
13 module_graph::ModuleGraph,
14 raw_module::RawModule,
15 reference::{ModuleReference, ModuleReferences, TracedModuleReference},
16 reference_type::ReferenceType,
17 resolve::{
18 origin::{ResolveOrigin, ResolveOriginExt},
19 parse::Request,
20 },
21};
22use turbopack_resolve::ecmascript::{cjs_resolve, esm_resolve};
23
24use crate::{
25 EcmascriptModuleContent,
26 chunk::{
27 EcmascriptChunkItem, EcmascriptChunkItemContent, EcmascriptChunkPlaceable,
28 EcmascriptChunkType, EcmascriptExports,
29 },
30 references::async_module::{AsyncModule, OptionAsyncModule},
31 runtime_functions::{
32 TURBOPACK_EXPORT_NAMESPACE, TURBOPACK_EXPORT_VALUE, TURBOPACK_EXTERNAL_IMPORT,
33 TURBOPACK_EXTERNAL_REQUIRE, TURBOPACK_LOAD_BY_URL,
34 },
35 utils::StringifyJs,
36};
37
38#[derive(
39 Copy,
40 Clone,
41 Debug,
42 Eq,
43 PartialEq,
44 Serialize,
45 Deserialize,
46 TraceRawVcs,
47 TaskInput,
48 Hash,
49 NonLocalValue,
50)]
51pub enum CachedExternalType {
52 CommonJs,
53 EcmaScriptViaRequire,
54 EcmaScriptViaImport,
55 Global,
56 Script,
57}
58
59#[derive(
60 Clone, Debug, Eq, PartialEq, Serialize, Deserialize, TraceRawVcs, TaskInput, Hash, NonLocalValue,
61)]
62pub enum CachedExternalTracingMode {
65 Untraced,
66 Traced {
67 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
68 },
69}
70
71impl Display for CachedExternalType {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self {
74 CachedExternalType::CommonJs => write!(f, "cjs"),
75 CachedExternalType::EcmaScriptViaRequire => write!(f, "esm_require"),
76 CachedExternalType::EcmaScriptViaImport => write!(f, "esm_import"),
77 CachedExternalType::Global => write!(f, "global"),
78 CachedExternalType::Script => write!(f, "script"),
79 }
80 }
81}
82
83#[turbo_tasks::value]
84pub struct CachedExternalModule {
85 request: RcStr,
86 external_type: CachedExternalType,
87 analyze_mode: CachedExternalTracingMode,
88}
89
90#[turbo_tasks::value_impl]
91impl CachedExternalModule {
92 #[turbo_tasks::function]
93 pub fn new(
94 request: RcStr,
95 external_type: CachedExternalType,
96 analyze_mode: CachedExternalTracingMode,
97 ) -> Vc<Self> {
98 Self::cell(CachedExternalModule {
99 request,
100 external_type,
101 analyze_mode,
102 })
103 }
104
105 #[turbo_tasks::function]
106 pub fn content(&self) -> Result<Vc<EcmascriptModuleContent>> {
107 let mut code = RopeBuilder::default();
108
109 match self.external_type {
110 CachedExternalType::EcmaScriptViaImport => {
111 writeln!(
112 code,
113 "const mod = await {TURBOPACK_EXTERNAL_IMPORT}({});",
114 StringifyJs(&self.request)
115 )?;
116 }
117 CachedExternalType::Global => {
118 if self.request.is_empty() {
119 writeln!(code, "const mod = {{}};")?;
120 } else {
121 writeln!(
122 code,
123 "const mod = globalThis[{}];",
124 StringifyJs(&self.request)
125 )?;
126 }
127 }
128 CachedExternalType::Script => {
129 if let Some(at_index) = self.request.find('@') {
132 let variable_name = &self.request[..at_index];
133 let url = &self.request[at_index + 1..];
134
135 writeln!(code, "let mod;")?;
137 writeln!(code, "try {{")?;
138
139 writeln!(
141 code,
142 " await {TURBOPACK_LOAD_BY_URL}({});",
143 StringifyJs(url)
144 )?;
145
146 writeln!(
148 code,
149 " if (typeof global[{}] === 'undefined') {{",
150 StringifyJs(variable_name)
151 )?;
152 writeln!(
153 code,
154 " throw new Error('Variable {} is not available on global object after \
155 loading {}');",
156 StringifyJs(variable_name),
157 StringifyJs(url)
158 )?;
159 writeln!(code, " }}")?;
160 writeln!(code, " mod = global[{}];", StringifyJs(variable_name))?;
161
162 writeln!(code, "}} catch (error) {{")?;
164 writeln!(
165 code,
166 " throw new Error('Failed to load external URL module {}: ' + \
167 (error.message || error));",
168 StringifyJs(&self.request)
169 )?;
170 writeln!(code, "}}")?;
171 } else {
172 writeln!(
174 code,
175 "throw new Error('Invalid URL external format. Expected \"variable@url\", \
176 got: {}');",
177 StringifyJs(&self.request)
178 )?;
179 writeln!(code, "const mod = undefined;")?;
180 }
181 }
182 CachedExternalType::EcmaScriptViaRequire | CachedExternalType::CommonJs => {
183 writeln!(
184 code,
185 "const mod = {TURBOPACK_EXTERNAL_REQUIRE}({}, () => require({}));",
186 StringifyJs(&self.request),
187 StringifyJs(&self.request)
188 )?;
189 }
190 }
191
192 writeln!(code)?;
193
194 if self.external_type == CachedExternalType::CommonJs {
195 writeln!(code, "module.exports = mod;")?;
196 } else if self.external_type == CachedExternalType::EcmaScriptViaImport
197 || self.external_type == CachedExternalType::EcmaScriptViaRequire
198 {
199 writeln!(code, "{TURBOPACK_EXPORT_NAMESPACE}(mod);")?;
200 } else {
201 writeln!(code, "{TURBOPACK_EXPORT_VALUE}(mod);")?;
202 }
203
204 Ok(EcmascriptModuleContent {
205 inner_code: code.build(),
206 source_map: None,
207 is_esm: self.external_type != CachedExternalType::CommonJs,
208 strict: false,
209 additional_ids: Default::default(),
210 }
211 .cell())
212 }
213}
214
215#[turbo_tasks::value_impl]
216impl Module for CachedExternalModule {
217 #[turbo_tasks::function]
218 async fn ident(&self) -> Result<Vc<AssetIdent>> {
219 let fs = VirtualFileSystem::new_with_name(rcstr!("externals"));
220
221 Ok(AssetIdent::from_path(fs.root().await?.join(&self.request)?)
222 .with_layer(Layer::new(rcstr!("external")))
223 .with_modifier(self.request.clone())
224 .with_modifier(self.external_type.to_string().into()))
225 }
226
227 #[turbo_tasks::function]
228 async fn references(&self) -> Result<Vc<ModuleReferences>> {
229 Ok(match &self.analyze_mode {
230 CachedExternalTracingMode::Untraced => ModuleReferences::empty(),
231 CachedExternalTracingMode::Traced { origin } => {
232 let external_result = match self.external_type {
233 CachedExternalType::EcmaScriptViaImport => {
234 esm_resolve(
235 **origin,
236 Request::parse_string(self.request.clone()),
237 Default::default(),
238 false,
239 None,
240 )
241 .await?
242 .await?
243 }
244 CachedExternalType::CommonJs | CachedExternalType::EcmaScriptViaRequire => {
245 cjs_resolve(
246 **origin,
247 Request::parse_string(self.request.clone()),
248 Default::default(),
249 None,
250 false,
251 )
252 .await?
253 }
254 CachedExternalType::Global | CachedExternalType::Script => {
255 origin
256 .resolve_asset(
257 Request::parse_string(self.request.clone()),
258 origin.resolve_options(ReferenceType::Undefined).await?,
259 ReferenceType::Undefined,
260 )
261 .await?
262 .await?
263 }
264 };
265
266 let references = external_result
267 .affecting_sources
268 .iter()
269 .map(|s| Vc::upcast::<Box<dyn Module>>(RawModule::new(**s)))
270 .chain(
271 external_result
272 .primary_modules_raw_iter()
273 .map(|m| Vc::upcast(ModuleWithoutSelfAsync::new(*m))),
281 )
282 .map(|s| {
283 Vc::upcast::<Box<dyn ModuleReference>>(TracedModuleReference::new(s))
284 .to_resolved()
285 })
286 .try_join()
287 .await?;
288 Vc::cell(references)
289 }
290 })
291 }
292
293 #[turbo_tasks::function]
294 fn is_self_async(&self) -> Result<Vc<bool>> {
295 Ok(Vc::cell(
296 self.external_type == CachedExternalType::EcmaScriptViaImport
297 || self.external_type == CachedExternalType::Script,
298 ))
299 }
300}
301
302#[turbo_tasks::value_impl]
303impl Asset for CachedExternalModule {
304 #[turbo_tasks::function]
305 fn content(self: Vc<Self>) -> Vc<AssetContent> {
306 AssetContent::file(FileContent::NotFound.cell())
308 }
309}
310
311#[turbo_tasks::value_impl]
312impl ChunkableModule for CachedExternalModule {
313 #[turbo_tasks::function]
314 fn as_chunk_item(
315 self: ResolvedVc<Self>,
316 _module_graph: Vc<ModuleGraph>,
317 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
318 ) -> Vc<Box<dyn ChunkItem>> {
319 Vc::upcast(
320 CachedExternalModuleChunkItem {
321 module: self,
322 chunking_context,
323 }
324 .cell(),
325 )
326 }
327}
328
329#[turbo_tasks::value_impl]
330impl EcmascriptChunkPlaceable for CachedExternalModule {
331 #[turbo_tasks::function]
332 fn get_exports(&self) -> Vc<EcmascriptExports> {
333 if self.external_type == CachedExternalType::CommonJs {
334 EcmascriptExports::CommonJs.cell()
335 } else {
336 EcmascriptExports::DynamicNamespace.cell()
337 }
338 }
339
340 #[turbo_tasks::function]
341 fn get_async_module(&self) -> Vc<OptionAsyncModule> {
342 Vc::cell(
343 if self.external_type == CachedExternalType::EcmaScriptViaImport
344 || self.external_type == CachedExternalType::Script
345 {
346 Some(
347 AsyncModule {
348 has_top_level_await: true,
349 import_externals: self.external_type
350 == CachedExternalType::EcmaScriptViaImport,
351 }
352 .resolved_cell(),
353 )
354 } else {
355 None
356 },
357 )
358 }
359
360 #[turbo_tasks::function]
361 fn is_marked_as_side_effect_free(
362 self: Vc<Self>,
363 _side_effect_free_packages: Vc<Glob>,
364 ) -> Vc<bool> {
365 Vc::cell(false)
366 }
367}
368
369#[turbo_tasks::value]
370pub struct CachedExternalModuleChunkItem {
371 module: ResolvedVc<CachedExternalModule>,
372 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
373}
374
375#[turbo_tasks::value_impl]
376impl ChunkItem for CachedExternalModuleChunkItem {
377 #[turbo_tasks::function]
378 fn asset_ident(&self) -> Vc<AssetIdent> {
379 self.module.ident()
380 }
381
382 #[turbo_tasks::function]
383 fn ty(self: Vc<Self>) -> Vc<Box<dyn ChunkType>> {
384 Vc::upcast(Vc::<EcmascriptChunkType>::default())
385 }
386
387 #[turbo_tasks::function]
388 fn module(&self) -> Vc<Box<dyn Module>> {
389 Vc::upcast(*self.module)
390 }
391
392 #[turbo_tasks::function]
393 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
394 *self.chunking_context
395 }
396}
397
398#[turbo_tasks::value_impl]
399impl EcmascriptChunkItem for CachedExternalModuleChunkItem {
400 #[turbo_tasks::function]
401 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent> {
402 panic!("content() should not be called");
403 }
404
405 #[turbo_tasks::function]
406 fn content_with_async_module_info(
407 &self,
408 async_module_info: Option<Vc<AsyncModuleInfo>>,
409 ) -> Vc<EcmascriptChunkItemContent> {
410 let async_module_options = self
411 .module
412 .get_async_module()
413 .module_options(async_module_info);
414
415 EcmascriptChunkItemContent::new(
416 self.module.content(),
417 *self.chunking_context,
418 async_module_options,
419 )
420 }
421}
422
423#[turbo_tasks::value]
426pub struct ModuleWithoutSelfAsync {
427 module: ResolvedVc<Box<dyn Module>>,
428}
429
430#[turbo_tasks::value_impl]
431impl ModuleWithoutSelfAsync {
432 #[turbo_tasks::function]
433 pub fn new(module: ResolvedVc<Box<dyn Module>>) -> Vc<Self> {
434 Self::cell(ModuleWithoutSelfAsync { module })
435 }
436}
437
438#[turbo_tasks::value_impl]
439impl Asset for ModuleWithoutSelfAsync {
440 #[turbo_tasks::function]
441 fn content(&self) -> Vc<AssetContent> {
442 self.module.content()
443 }
444}
445
446#[turbo_tasks::value_impl]
447impl Module for ModuleWithoutSelfAsync {
448 #[turbo_tasks::function]
449 fn ident(&self) -> Vc<AssetIdent> {
450 self.module.ident()
451 }
452
453 #[turbo_tasks::function]
454 fn references(&self) -> Vc<ModuleReferences> {
455 self.module.references()
456 }
457
458 }