turbopack_ecmascript/references/
external_module.rs1use std::{fmt::Display, io::Write};
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use turbo_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,
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 },
27 utils::StringifyJs,
28};
29
30#[turbo_tasks::function]
31fn layer() -> Vc<RcStr> {
32 Vc::cell("external".into())
33}
34
35#[derive(
36 Copy,
37 Clone,
38 Debug,
39 Eq,
40 PartialEq,
41 Serialize,
42 Deserialize,
43 TraceRawVcs,
44 TaskInput,
45 Hash,
46 NonLocalValue,
47)]
48pub enum CachedExternalType {
49 CommonJs,
50 EcmaScriptViaRequire,
51 EcmaScriptViaImport,
52}
53
54impl Display for CachedExternalType {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 match self {
57 CachedExternalType::CommonJs => write!(f, "cjs"),
58 CachedExternalType::EcmaScriptViaRequire => write!(f, "esm_require"),
59 CachedExternalType::EcmaScriptViaImport => write!(f, "esm_import"),
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 if self.external_type == CachedExternalType::EcmaScriptViaImport {
91 writeln!(
92 code,
93 "const mod = await {TURBOPACK_EXTERNAL_IMPORT}({});",
94 StringifyJs(&self.request)
95 )?;
96 } else {
97 writeln!(
98 code,
99 "const mod = {TURBOPACK_EXTERNAL_REQUIRE}({}, () => require({}));",
100 StringifyJs(&self.request),
101 StringifyJs(&self.request)
102 )?;
103 }
104
105 writeln!(code)?;
106
107 if self.external_type == CachedExternalType::CommonJs {
108 writeln!(code, "module.exports = mod;")?;
109 } else {
110 writeln!(code, "{TURBOPACK_EXPORT_NAMESPACE}(mod);")?;
111 }
112
113 Ok(EcmascriptModuleContent {
114 inner_code: code.build(),
115 source_map: None,
116 is_esm: self.external_type != CachedExternalType::CommonJs,
117 }
118 .cell())
119 }
120}
121
122#[turbo_tasks::value_impl]
123impl Module for CachedExternalModule {
124 #[turbo_tasks::function]
125 fn ident(&self) -> Vc<AssetIdent> {
126 let fs = VirtualFileSystem::new_with_name("externals".into());
127
128 AssetIdent::from_path(fs.root().join(self.request.clone()))
129 .with_layer(layer())
130 .with_modifier(Vc::cell(self.request.clone()))
131 .with_modifier(Vc::cell(self.external_type.to_string().into()))
132 }
133
134 #[turbo_tasks::function]
135 async fn references(&self) -> Result<Vc<ModuleReferences>> {
136 Ok(Vc::cell(self.additional_references.clone()))
137 }
138
139 #[turbo_tasks::function]
140 async fn is_self_async(&self) -> Result<Vc<bool>> {
141 Ok(Vc::cell(
142 self.external_type == CachedExternalType::EcmaScriptViaImport,
143 ))
144 }
145}
146
147#[turbo_tasks::value_impl]
148impl Asset for CachedExternalModule {
149 #[turbo_tasks::function]
150 fn content(self: Vc<Self>) -> Vc<AssetContent> {
151 AssetContent::file(FileContent::NotFound.cell())
153 }
154}
155
156#[turbo_tasks::value_impl]
157impl ChunkableModule for CachedExternalModule {
158 #[turbo_tasks::function]
159 fn as_chunk_item(
160 self: ResolvedVc<Self>,
161 _module_graph: Vc<ModuleGraph>,
162 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
163 ) -> Vc<Box<dyn ChunkItem>> {
164 Vc::upcast(
165 CachedExternalModuleChunkItem {
166 module: self,
167 chunking_context,
168 }
169 .cell(),
170 )
171 }
172}
173
174#[turbo_tasks::value_impl]
175impl EcmascriptChunkPlaceable for CachedExternalModule {
176 #[turbo_tasks::function]
177 fn get_exports(&self) -> Vc<EcmascriptExports> {
178 if self.external_type == CachedExternalType::CommonJs {
179 EcmascriptExports::CommonJs.cell()
180 } else {
181 EcmascriptExports::DynamicNamespace.cell()
182 }
183 }
184
185 #[turbo_tasks::function]
186 fn get_async_module(&self) -> Vc<OptionAsyncModule> {
187 Vc::cell(
188 if self.external_type == CachedExternalType::EcmaScriptViaImport {
189 Some(
190 AsyncModule {
191 has_top_level_await: true,
192 import_externals: true,
193 }
194 .resolved_cell(),
195 )
196 } else {
197 None
198 },
199 )
200 }
201
202 #[turbo_tasks::function]
203 fn is_marked_as_side_effect_free(
204 self: Vc<Self>,
205 _side_effect_free_packages: Vc<Glob>,
206 ) -> Vc<bool> {
207 Vc::cell(false)
208 }
209}
210
211#[turbo_tasks::value]
212pub struct CachedExternalModuleChunkItem {
213 module: ResolvedVc<CachedExternalModule>,
214 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
215}
216
217#[turbo_tasks::function]
219fn external_fs() -> Vc<VirtualFileSystem> {
220 VirtualFileSystem::new_with_name("externals".into())
221}
222
223#[turbo_tasks::value_impl]
224impl ChunkItem for CachedExternalModuleChunkItem {
225 #[turbo_tasks::function]
226 fn asset_ident(&self) -> Vc<AssetIdent> {
227 self.module.ident()
228 }
229
230 #[turbo_tasks::function]
231 fn ty(self: Vc<Self>) -> Vc<Box<dyn ChunkType>> {
232 Vc::upcast(Vc::<EcmascriptChunkType>::default())
233 }
234
235 #[turbo_tasks::function]
236 fn module(&self) -> Vc<Box<dyn Module>> {
237 Vc::upcast(*self.module)
238 }
239
240 #[turbo_tasks::function]
241 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
242 *self.chunking_context
243 }
244}
245
246#[turbo_tasks::value_impl]
247impl EcmascriptChunkItem for CachedExternalModuleChunkItem {
248 #[turbo_tasks::function]
249 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent> {
250 panic!("content() should not be called");
251 }
252
253 #[turbo_tasks::function]
254 fn content_with_async_module_info(
255 &self,
256 async_module_info: Option<Vc<AsyncModuleInfo>>,
257 ) -> Vc<EcmascriptChunkItemContent> {
258 let async_module_options = self
259 .module
260 .get_async_module()
261 .module_options(async_module_info);
262
263 EcmascriptChunkItemContent::new(
264 self.module.content(),
265 *self.chunking_context,
266 EcmascriptOptions::default().cell(),
267 async_module_options,
268 )
269 }
270}