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 ))
214 }
215}
216
217#[turbo_tasks::value_impl]
218impl Asset for CachedExternalModule {
219 #[turbo_tasks::function]
220 fn content(self: Vc<Self>) -> Vc<AssetContent> {
221 AssetContent::file(FileContent::NotFound.cell())
223 }
224}
225
226#[turbo_tasks::value_impl]
227impl ChunkableModule for CachedExternalModule {
228 #[turbo_tasks::function]
229 fn as_chunk_item(
230 self: ResolvedVc<Self>,
231 _module_graph: Vc<ModuleGraph>,
232 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
233 ) -> Vc<Box<dyn ChunkItem>> {
234 Vc::upcast(
235 CachedExternalModuleChunkItem {
236 module: self,
237 chunking_context,
238 }
239 .cell(),
240 )
241 }
242}
243
244#[turbo_tasks::value_impl]
245impl EcmascriptChunkPlaceable for CachedExternalModule {
246 #[turbo_tasks::function]
247 fn get_exports(&self) -> Vc<EcmascriptExports> {
248 if self.external_type == CachedExternalType::CommonJs {
249 EcmascriptExports::CommonJs.cell()
250 } else {
251 EcmascriptExports::DynamicNamespace.cell()
252 }
253 }
254
255 #[turbo_tasks::function]
256 fn get_async_module(&self) -> Vc<OptionAsyncModule> {
257 Vc::cell(
258 if self.external_type == CachedExternalType::EcmaScriptViaImport {
259 Some(
260 AsyncModule {
261 has_top_level_await: true,
262 import_externals: true,
263 }
264 .resolved_cell(),
265 )
266 } else {
267 None
268 },
269 )
270 }
271
272 #[turbo_tasks::function]
273 fn is_marked_as_side_effect_free(
274 self: Vc<Self>,
275 _side_effect_free_packages: Vc<Glob>,
276 ) -> Vc<bool> {
277 Vc::cell(false)
278 }
279}
280
281#[turbo_tasks::value]
282pub struct CachedExternalModuleChunkItem {
283 module: ResolvedVc<CachedExternalModule>,
284 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
285}
286
287#[turbo_tasks::function]
289fn external_fs() -> Vc<VirtualFileSystem> {
290 VirtualFileSystem::new_with_name(rcstr!("externals"))
291}
292
293#[turbo_tasks::value_impl]
294impl ChunkItem for CachedExternalModuleChunkItem {
295 #[turbo_tasks::function]
296 fn asset_ident(&self) -> Vc<AssetIdent> {
297 self.module.ident()
298 }
299
300 #[turbo_tasks::function]
301 fn ty(self: Vc<Self>) -> Vc<Box<dyn ChunkType>> {
302 Vc::upcast(Vc::<EcmascriptChunkType>::default())
303 }
304
305 #[turbo_tasks::function]
306 fn module(&self) -> Vc<Box<dyn Module>> {
307 Vc::upcast(*self.module)
308 }
309
310 #[turbo_tasks::function]
311 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
312 *self.chunking_context
313 }
314}
315
316#[turbo_tasks::value_impl]
317impl EcmascriptChunkItem for CachedExternalModuleChunkItem {
318 #[turbo_tasks::function]
319 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent> {
320 panic!("content() should not be called");
321 }
322
323 #[turbo_tasks::function]
324 fn content_with_async_module_info(
325 &self,
326 async_module_info: Option<Vc<AsyncModuleInfo>>,
327 ) -> Vc<EcmascriptChunkItemContent> {
328 let async_module_options = self
329 .module
330 .get_async_module()
331 .module_options(async_module_info);
332
333 EcmascriptChunkItemContent::new(
334 self.module.content(),
335 *self.chunking_context,
336 EcmascriptOptions::default().cell(),
337 async_module_options,
338 )
339 }
340}