turbopack_ecmascript/references/
external_module.rs1use std::{fmt::Display, io::Write};
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{NonLocalValue, ResolvedVc, TaskInput, 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 reference::{ModuleReference, ModuleReferences},
15};
16
17use crate::{
18 EcmascriptModuleContent, EcmascriptOptions,
19 chunk::{
20 EcmascriptChunkItem, EcmascriptChunkItemContent, EcmascriptChunkPlaceable,
21 EcmascriptChunkType, EcmascriptExports,
22 },
23 references::async_module::{AsyncModule, OptionAsyncModule},
24 runtime_functions::{
25 TURBOPACK_EXPORT_NAMESPACE, TURBOPACK_EXTERNAL_IMPORT, TURBOPACK_EXTERNAL_REQUIRE,
26 TURBOPACK_LOAD_BY_URL,
27 },
28 utils::StringifyJs,
29};
30
31#[derive(
32 Copy,
33 Clone,
34 Debug,
35 Eq,
36 PartialEq,
37 Serialize,
38 Deserialize,
39 TraceRawVcs,
40 TaskInput,
41 Hash,
42 NonLocalValue,
43)]
44pub enum CachedExternalType {
45 CommonJs,
46 EcmaScriptViaRequire,
47 EcmaScriptViaImport,
48 Global,
49 Script,
50}
51
52impl Display for CachedExternalType {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 CachedExternalType::CommonJs => write!(f, "cjs"),
56 CachedExternalType::EcmaScriptViaRequire => write!(f, "esm_require"),
57 CachedExternalType::EcmaScriptViaImport => write!(f, "esm_import"),
58 CachedExternalType::Global => write!(f, "global"),
59 CachedExternalType::Script => write!(f, "script"),
60 }
61 }
62}
63
64#[turbo_tasks::value]
65pub struct CachedExternalModule {
66 pub request: RcStr,
67 pub external_type: CachedExternalType,
68 pub additional_references: Vec<ResolvedVc<Box<dyn ModuleReference>>>,
69}
70
71#[turbo_tasks::value_impl]
72impl CachedExternalModule {
73 #[turbo_tasks::function]
74 pub fn new(
75 request: RcStr,
76 external_type: CachedExternalType,
77 additional_references: Vec<ResolvedVc<Box<dyn ModuleReference>>>,
78 ) -> Vc<Self> {
79 Self::cell(CachedExternalModule {
80 request,
81 external_type,
82 additional_references,
83 })
84 }
85
86 #[turbo_tasks::function]
87 pub fn content(&self) -> Result<Vc<EcmascriptModuleContent>> {
88 let mut code = RopeBuilder::default();
89
90 match self.external_type {
91 CachedExternalType::EcmaScriptViaImport => {
92 writeln!(
93 code,
94 "const mod = await {TURBOPACK_EXTERNAL_IMPORT}({});",
95 StringifyJs(&self.request)
96 )?;
97 }
98 CachedExternalType::Global => {
99 if self.request.is_empty() {
100 writeln!(code, "const mod = {{}};")?;
101 } else {
102 writeln!(
103 code,
104 "const mod = globalThis[{}];",
105 StringifyJs(&self.request)
106 )?;
107 }
108 }
109 CachedExternalType::Script => {
110 if let Some(at_index) = self.request.find('@') {
113 let variable_name = &self.request[..at_index];
114 let url = &self.request[at_index + 1..];
115
116 writeln!(code, "let mod;")?;
118 writeln!(code, "try {{")?;
119
120 writeln!(
122 code,
123 " await {TURBOPACK_LOAD_BY_URL}({});",
124 StringifyJs(url)
125 )?;
126
127 writeln!(
129 code,
130 " if (typeof global[{}] === 'undefined') {{",
131 StringifyJs(variable_name)
132 )?;
133 writeln!(
134 code,
135 " throw new Error('Variable {} is not available on global object after \
136 loading {}');",
137 StringifyJs(variable_name),
138 StringifyJs(url)
139 )?;
140 writeln!(code, " }}")?;
141 writeln!(code, " mod = global[{}];", StringifyJs(variable_name))?;
142
143 writeln!(code, "}} catch (error) {{")?;
145 writeln!(
146 code,
147 " throw new Error('Failed to load external URL module {}: ' + \
148 (error.message || error));",
149 StringifyJs(&self.request)
150 )?;
151 writeln!(code, "}}")?;
152 } else {
153 writeln!(
155 code,
156 "throw new Error('Invalid URL external format. Expected \"variable@url\", \
157 got: {}');",
158 StringifyJs(&self.request)
159 )?;
160 writeln!(code, "const mod = undefined;")?;
161 }
162 }
163 CachedExternalType::EcmaScriptViaRequire | CachedExternalType::CommonJs => {
164 writeln!(
165 code,
166 "const mod = {TURBOPACK_EXTERNAL_REQUIRE}({}, () => require({}));",
167 StringifyJs(&self.request),
168 StringifyJs(&self.request)
169 )?;
170 }
171 }
172
173 writeln!(code)?;
174
175 if self.external_type == CachedExternalType::CommonJs {
176 writeln!(code, "module.exports = mod;")?;
177 } else {
178 writeln!(code, "{TURBOPACK_EXPORT_NAMESPACE}(mod);")?;
179 }
180
181 Ok(EcmascriptModuleContent {
182 inner_code: code.build(),
183 source_map: None,
184 is_esm: self.external_type != CachedExternalType::CommonJs,
185 strict: false,
186 additional_ids: Default::default(),
187 }
188 .cell())
189 }
190}
191
192#[turbo_tasks::value_impl]
193impl Module for CachedExternalModule {
194 #[turbo_tasks::function]
195 async fn ident(&self) -> Result<Vc<AssetIdent>> {
196 let fs = VirtualFileSystem::new_with_name(rcstr!("externals"));
197
198 Ok(AssetIdent::from_path(fs.root().await?.join(&self.request)?)
199 .with_layer(Layer::new(rcstr!("external")))
200 .with_modifier(self.request.clone())
201 .with_modifier(self.external_type.to_string().into()))
202 }
203
204 #[turbo_tasks::function]
205 fn references(&self) -> Result<Vc<ModuleReferences>> {
206 Ok(Vc::cell(self.additional_references.clone()))
207 }
208
209 #[turbo_tasks::function]
210 fn is_self_async(&self) -> Result<Vc<bool>> {
211 Ok(Vc::cell(
212 self.external_type == CachedExternalType::EcmaScriptViaImport
213 || self.external_type == CachedExternalType::Script,
214 ))
215 }
216}
217
218#[turbo_tasks::value_impl]
219impl Asset for CachedExternalModule {
220 #[turbo_tasks::function]
221 fn content(self: Vc<Self>) -> Vc<AssetContent> {
222 AssetContent::file(FileContent::NotFound.cell())
224 }
225}
226
227#[turbo_tasks::value_impl]
228impl ChunkableModule for CachedExternalModule {
229 #[turbo_tasks::function]
230 fn as_chunk_item(
231 self: ResolvedVc<Self>,
232 _module_graph: Vc<ModuleGraph>,
233 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
234 ) -> Vc<Box<dyn ChunkItem>> {
235 Vc::upcast(
236 CachedExternalModuleChunkItem {
237 module: self,
238 chunking_context,
239 }
240 .cell(),
241 )
242 }
243}
244
245#[turbo_tasks::value_impl]
246impl EcmascriptChunkPlaceable for CachedExternalModule {
247 #[turbo_tasks::function]
248 fn get_exports(&self) -> Vc<EcmascriptExports> {
249 if self.external_type == CachedExternalType::CommonJs {
250 EcmascriptExports::CommonJs.cell()
251 } else {
252 EcmascriptExports::DynamicNamespace.cell()
253 }
254 }
255
256 #[turbo_tasks::function]
257 fn get_async_module(&self) -> Vc<OptionAsyncModule> {
258 Vc::cell(
259 if self.external_type == CachedExternalType::EcmaScriptViaImport
260 || self.external_type == CachedExternalType::Script
261 {
262 Some(
263 AsyncModule {
264 has_top_level_await: true,
265 import_externals: self.external_type
266 == CachedExternalType::EcmaScriptViaImport,
267 }
268 .resolved_cell(),
269 )
270 } else {
271 None
272 },
273 )
274 }
275
276 #[turbo_tasks::function]
277 fn is_marked_as_side_effect_free(
278 self: Vc<Self>,
279 _side_effect_free_packages: Vc<Glob>,
280 ) -> Vc<bool> {
281 Vc::cell(false)
282 }
283}
284
285#[turbo_tasks::value]
286pub struct CachedExternalModuleChunkItem {
287 module: ResolvedVc<CachedExternalModule>,
288 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
289}
290
291#[turbo_tasks::function]
293fn external_fs() -> Vc<VirtualFileSystem> {
294 VirtualFileSystem::new_with_name(rcstr!("externals"))
295}
296
297#[turbo_tasks::value_impl]
298impl ChunkItem for CachedExternalModuleChunkItem {
299 #[turbo_tasks::function]
300 fn asset_ident(&self) -> Vc<AssetIdent> {
301 self.module.ident()
302 }
303
304 #[turbo_tasks::function]
305 fn ty(self: Vc<Self>) -> Vc<Box<dyn ChunkType>> {
306 Vc::upcast(Vc::<EcmascriptChunkType>::default())
307 }
308
309 #[turbo_tasks::function]
310 fn module(&self) -> Vc<Box<dyn Module>> {
311 Vc::upcast(*self.module)
312 }
313
314 #[turbo_tasks::function]
315 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
316 *self.chunking_context
317 }
318}
319
320#[turbo_tasks::value_impl]
321impl EcmascriptChunkItem for CachedExternalModuleChunkItem {
322 #[turbo_tasks::function]
323 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent> {
324 panic!("content() should not be called");
325 }
326
327 #[turbo_tasks::function]
328 fn content_with_async_module_info(
329 &self,
330 async_module_info: Option<Vc<AsyncModuleInfo>>,
331 ) -> Vc<EcmascriptChunkItemContent> {
332 let async_module_options = self
333 .module
334 .get_async_module()
335 .module_options(async_module_info);
336
337 EcmascriptChunkItemContent::new(
338 self.module.content(),
339 *self.chunking_context,
340 EcmascriptOptions::default().cell(),
341 async_module_options,
342 )
343 }
344}