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::{
8 FileContent, FileSystem, FileSystemPath, VirtualFileSystem, glob::Glob, rope::RopeBuilder,
9};
10use turbopack_core::{
11 asset::{Asset, AssetContent},
12 chunk::{AsyncModuleInfo, ChunkItem, ChunkType, ChunkableModule, ChunkingContext},
13 context::AssetContext,
14 ident::{AssetIdent, Layer},
15 module::Module,
16 module_graph::ModuleGraph,
17 raw_module::RawModule,
18 reference::{ModuleReference, ModuleReferences, TracedModuleReference},
19 reference_type::ReferenceType,
20 resolve::parse::Request,
21};
22
23use crate::{
24 EcmascriptModuleContent,
25 chunk::{
26 EcmascriptChunkItem, EcmascriptChunkItemContent, EcmascriptChunkPlaceable,
27 EcmascriptChunkType, EcmascriptExports,
28 },
29 references::async_module::{AsyncModule, OptionAsyncModule},
30 runtime_functions::{
31 TURBOPACK_EXPORT_NAMESPACE, TURBOPACK_EXPORT_VALUE, TURBOPACK_EXTERNAL_IMPORT,
32 TURBOPACK_EXTERNAL_REQUIRE, TURBOPACK_LOAD_BY_URL,
33 },
34 utils::StringifyJs,
35};
36
37#[derive(
38 Copy,
39 Clone,
40 Debug,
41 Eq,
42 PartialEq,
43 Serialize,
44 Deserialize,
45 TraceRawVcs,
46 TaskInput,
47 Hash,
48 NonLocalValue,
49)]
50pub enum CachedExternalType {
51 CommonJs,
52 EcmaScriptViaRequire,
53 EcmaScriptViaImport,
54 Global,
55 Script,
56}
57
58#[derive(
59 Clone, Debug, Eq, PartialEq, Serialize, Deserialize, TraceRawVcs, TaskInput, Hash, NonLocalValue,
60)]
61pub enum CachedExternalTracingMode {
64 Untraced,
65 Traced {
66 externals_context: ResolvedVc<Box<dyn AssetContext>>,
67 root_origin: FileSystemPath,
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 tracing_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 tracing_mode: CachedExternalTracingMode,
97 ) -> Vc<Self> {
98 Self::cell(CachedExternalModule {
99 request,
100 external_type,
101 tracing_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.tracing_mode {
230 CachedExternalTracingMode::Untraced => ModuleReferences::empty(),
231 CachedExternalTracingMode::Traced {
232 externals_context,
233 root_origin,
234 } => {
235 let reference_type = match self.external_type {
236 CachedExternalType::EcmaScriptViaImport => {
237 ReferenceType::EcmaScriptModules(Default::default())
238 }
239 CachedExternalType::CommonJs | CachedExternalType::EcmaScriptViaRequire => {
240 ReferenceType::CommonJs(Default::default())
241 }
242 _ => ReferenceType::Undefined,
243 };
244
245 let external_result = externals_context
246 .resolve_asset(
247 root_origin.clone(),
248 Request::parse_string(self.request.clone()),
249 externals_context
250 .resolve_options(root_origin.clone(), reference_type.clone()),
251 reference_type,
252 )
253 .await?;
254 let references = external_result
255 .affecting_sources
256 .iter()
257 .map(|s| Vc::upcast::<Box<dyn Module>>(RawModule::new(**s)))
258 .chain(
259 external_result
260 .primary_modules_raw_iter()
261 .map(|m| Vc::upcast(ModuleWithoutSelfAsync::new(*m))),
269 )
270 .map(|s| {
271 Vc::upcast::<Box<dyn ModuleReference>>(TracedModuleReference::new(s))
272 .to_resolved()
273 })
274 .try_join()
275 .await?;
276 Vc::cell(references)
277 }
278 })
279 }
280
281 #[turbo_tasks::function]
282 fn is_self_async(&self) -> Result<Vc<bool>> {
283 Ok(Vc::cell(
284 self.external_type == CachedExternalType::EcmaScriptViaImport
285 || self.external_type == CachedExternalType::Script,
286 ))
287 }
288}
289
290#[turbo_tasks::value_impl]
291impl Asset for CachedExternalModule {
292 #[turbo_tasks::function]
293 fn content(self: Vc<Self>) -> Vc<AssetContent> {
294 AssetContent::file(FileContent::NotFound.cell())
296 }
297}
298
299#[turbo_tasks::value_impl]
300impl ChunkableModule for CachedExternalModule {
301 #[turbo_tasks::function]
302 fn as_chunk_item(
303 self: ResolvedVc<Self>,
304 _module_graph: Vc<ModuleGraph>,
305 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
306 ) -> Vc<Box<dyn ChunkItem>> {
307 Vc::upcast(
308 CachedExternalModuleChunkItem {
309 module: self,
310 chunking_context,
311 }
312 .cell(),
313 )
314 }
315}
316
317#[turbo_tasks::value_impl]
318impl EcmascriptChunkPlaceable for CachedExternalModule {
319 #[turbo_tasks::function]
320 fn get_exports(&self) -> Vc<EcmascriptExports> {
321 if self.external_type == CachedExternalType::CommonJs {
322 EcmascriptExports::CommonJs.cell()
323 } else {
324 EcmascriptExports::DynamicNamespace.cell()
325 }
326 }
327
328 #[turbo_tasks::function]
329 fn get_async_module(&self) -> Vc<OptionAsyncModule> {
330 Vc::cell(
331 if self.external_type == CachedExternalType::EcmaScriptViaImport
332 || self.external_type == CachedExternalType::Script
333 {
334 Some(
335 AsyncModule {
336 has_top_level_await: true,
337 import_externals: self.external_type
338 == CachedExternalType::EcmaScriptViaImport,
339 }
340 .resolved_cell(),
341 )
342 } else {
343 None
344 },
345 )
346 }
347
348 #[turbo_tasks::function]
349 fn is_marked_as_side_effect_free(
350 self: Vc<Self>,
351 _side_effect_free_packages: Vc<Glob>,
352 ) -> Vc<bool> {
353 Vc::cell(false)
354 }
355}
356
357#[turbo_tasks::value]
358pub struct CachedExternalModuleChunkItem {
359 module: ResolvedVc<CachedExternalModule>,
360 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
361}
362
363#[turbo_tasks::value_impl]
364impl ChunkItem for CachedExternalModuleChunkItem {
365 #[turbo_tasks::function]
366 fn asset_ident(&self) -> Vc<AssetIdent> {
367 self.module.ident()
368 }
369
370 #[turbo_tasks::function]
371 fn ty(self: Vc<Self>) -> Vc<Box<dyn ChunkType>> {
372 Vc::upcast(Vc::<EcmascriptChunkType>::default())
373 }
374
375 #[turbo_tasks::function]
376 fn module(&self) -> Vc<Box<dyn Module>> {
377 Vc::upcast(*self.module)
378 }
379
380 #[turbo_tasks::function]
381 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
382 *self.chunking_context
383 }
384}
385
386#[turbo_tasks::value_impl]
387impl EcmascriptChunkItem for CachedExternalModuleChunkItem {
388 #[turbo_tasks::function]
389 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent> {
390 panic!("content() should not be called");
391 }
392
393 #[turbo_tasks::function]
394 fn content_with_async_module_info(
395 &self,
396 async_module_info: Option<Vc<AsyncModuleInfo>>,
397 ) -> Vc<EcmascriptChunkItemContent> {
398 let async_module_options = self
399 .module
400 .get_async_module()
401 .module_options(async_module_info);
402
403 EcmascriptChunkItemContent::new(
404 self.module.content(),
405 *self.chunking_context,
406 async_module_options,
407 )
408 }
409}
410
411#[turbo_tasks::value]
414pub struct ModuleWithoutSelfAsync {
415 module: ResolvedVc<Box<dyn Module>>,
416}
417
418#[turbo_tasks::value_impl]
419impl ModuleWithoutSelfAsync {
420 #[turbo_tasks::function]
421 pub fn new(module: ResolvedVc<Box<dyn Module>>) -> Vc<Self> {
422 Self::cell(ModuleWithoutSelfAsync { module })
423 }
424}
425
426#[turbo_tasks::value_impl]
427impl Asset for ModuleWithoutSelfAsync {
428 #[turbo_tasks::function]
429 fn content(&self) -> Vc<AssetContent> {
430 self.module.content()
431 }
432}
433
434#[turbo_tasks::value_impl]
435impl Module for ModuleWithoutSelfAsync {
436 #[turbo_tasks::function]
437 fn ident(&self) -> Vc<AssetIdent> {
438 self.module.ident()
439 }
440
441 #[turbo_tasks::function]
442 fn references(&self) -> Vc<ModuleReferences> {
443 self.module.references()
444 }
445
446 }