turbopack_ecmascript/references/
external_module.rs

1use 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        // should be `NotFound` as this function gets called to detect source changes
152        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// Without this wrapper, VirtualFileSystem::new_with_name always returns a new filesystem
218#[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}