turbopack_wasm/
module_asset.rs

1use anyhow::{Context, Result, bail};
2use turbo_rcstr::rcstr;
3use turbo_tasks::{IntoTraitRef, ResolvedVc, Vc, fxindexmap};
4use turbo_tasks_fs::FileSystemPath;
5use turbopack_core::{
6    asset::{Asset, AssetContent},
7    chunk::{
8        AsyncModuleInfo, ChunkItem, ChunkType, ChunkableModule, ChunkingContext,
9        chunk_group::references_to_output_assets,
10    },
11    context::AssetContext,
12    ident::AssetIdent,
13    module::{Module, ModuleSideEffects, OptionModule},
14    module_graph::ModuleGraph,
15    output::{OutputAssetsReference, OutputAssetsWithReferenced},
16    reference::{ModuleReferences, SingleChunkableModuleReference},
17    reference_type::ReferenceType,
18    resolve::{ExportUsage, origin::ResolveOrigin, parse::Request},
19    source::{OptionSource, Source},
20};
21use turbopack_ecmascript::{
22    chunk::{
23        EcmascriptChunkItem, EcmascriptChunkItemContent, EcmascriptChunkPlaceable,
24        EcmascriptChunkType, EcmascriptExports,
25    },
26    references::async_module::OptionAsyncModule,
27};
28
29use crate::{
30    loader::{compiling_loader_source, instantiating_loader_source},
31    output_asset::WebAssemblyAsset,
32    raw::RawWebAssemblyModuleAsset,
33    source::WebAssemblySource,
34};
35
36/// Creates a javascript loader which instantiates the WebAssembly source and
37/// re-exports its exports.
38#[turbo_tasks::value]
39#[derive(Clone)]
40pub struct WebAssemblyModuleAsset {
41    source: ResolvedVc<WebAssemblySource>,
42    asset_context: ResolvedVc<Box<dyn AssetContext>>,
43}
44
45#[turbo_tasks::value_impl]
46impl WebAssemblyModuleAsset {
47    #[turbo_tasks::function]
48    pub fn new(
49        source: ResolvedVc<WebAssemblySource>,
50        asset_context: ResolvedVc<Box<dyn AssetContext>>,
51    ) -> Vc<Self> {
52        Self::cell(WebAssemblyModuleAsset {
53            source,
54            asset_context,
55        })
56    }
57
58    #[turbo_tasks::function]
59    fn wasm_asset(&self, chunking_context: Vc<Box<dyn ChunkingContext>>) -> Vc<WebAssemblyAsset> {
60        WebAssemblyAsset::new(*self.source, chunking_context)
61    }
62
63    #[turbo_tasks::function]
64    async fn loader_as_module(self: Vc<Self>) -> Result<Vc<Box<dyn Module>>> {
65        let this = self.await?;
66        let query = &this.source.ident().await?.query;
67
68        let loader_source = if query == "?module" {
69            compiling_loader_source(*this.source)
70        } else {
71            instantiating_loader_source(*this.source)
72        };
73
74        let module = this.asset_context.process(
75            loader_source,
76            ReferenceType::Internal(ResolvedVc::cell(fxindexmap! {
77                rcstr!("WASM_PATH") => ResolvedVc::upcast(RawWebAssemblyModuleAsset::new(*this.source, *this.asset_context).to_resolved().await?),
78            })),
79        ).module();
80
81        Ok(module)
82    }
83    #[turbo_tasks::function]
84    async fn loader_as_resolve_origin(self: Vc<Self>) -> Result<Vc<Box<dyn ResolveOrigin>>> {
85        let module = self.loader_as_module();
86
87        let Some(esm_asset) = Vc::try_resolve_sidecast::<Box<dyn ResolveOrigin>>(module).await?
88        else {
89            bail!("WASM loader was not processed into an EcmascriptModuleAsset");
90        };
91
92        Ok(esm_asset)
93    }
94
95    #[turbo_tasks::function]
96    async fn loader(self: Vc<Self>) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {
97        let module = self.loader_as_module();
98
99        let Some(esm_asset) =
100            Vc::try_resolve_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(module).await?
101        else {
102            bail!("WASM loader was not processed into an EcmascriptModuleAsset");
103        };
104
105        Ok(esm_asset)
106    }
107
108    #[turbo_tasks::function]
109    async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
110        Ok(Vc::cell(vec![ResolvedVc::upcast(
111            SingleChunkableModuleReference::new(
112                Vc::upcast(self.loader()),
113                rcstr!("wasm loader"),
114                ExportUsage::all(),
115            )
116            .to_resolved()
117            .await?,
118        )]))
119    }
120}
121
122#[turbo_tasks::value_impl]
123impl Module for WebAssemblyModuleAsset {
124    #[turbo_tasks::function]
125    async fn ident(&self) -> Result<Vc<AssetIdent>> {
126        Ok(self
127            .source
128            .ident()
129            .with_modifier(rcstr!("wasm module"))
130            .with_layer(self.asset_context.into_trait_ref().await?.layer()))
131    }
132
133    #[turbo_tasks::function]
134    fn source(&self) -> Vc<OptionSource> {
135        Vc::cell(Some(ResolvedVc::upcast(self.source)))
136    }
137
138    #[turbo_tasks::function]
139    fn references(self: Vc<Self>) -> Vc<ModuleReferences> {
140        self.loader().references()
141    }
142
143    #[turbo_tasks::function]
144    fn is_self_async(self: Vc<Self>) -> Vc<bool> {
145        Vc::cell(true)
146    }
147
148    #[turbo_tasks::function]
149    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
150        // Both versions of this module have a top level await that instantiates a wasm module
151        // wasm module instantiation can trigger arbitrary side effects from the native start
152        // function
153        ModuleSideEffects::SideEffectful.cell()
154    }
155}
156
157#[turbo_tasks::value_impl]
158impl Asset for WebAssemblyModuleAsset {
159    #[turbo_tasks::function]
160    fn content(&self) -> Vc<AssetContent> {
161        self.source.content()
162    }
163}
164
165#[turbo_tasks::value_impl]
166impl ChunkableModule for WebAssemblyModuleAsset {
167    #[turbo_tasks::function]
168    fn as_chunk_item(
169        self: ResolvedVc<Self>,
170        module_graph: ResolvedVc<ModuleGraph>,
171        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
172    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
173        Vc::upcast(
174            ModuleChunkItem {
175                module: self,
176                module_graph,
177                chunking_context,
178            }
179            .cell(),
180        )
181    }
182}
183
184#[turbo_tasks::value_impl]
185impl EcmascriptChunkPlaceable for WebAssemblyModuleAsset {
186    #[turbo_tasks::function]
187    fn get_exports(self: Vc<Self>) -> Vc<EcmascriptExports> {
188        self.loader().get_exports()
189    }
190
191    #[turbo_tasks::function]
192    fn get_async_module(self: Vc<Self>) -> Vc<OptionAsyncModule> {
193        self.loader().get_async_module()
194    }
195}
196
197#[turbo_tasks::value_impl]
198impl ResolveOrigin for WebAssemblyModuleAsset {
199    #[turbo_tasks::function]
200    fn origin_path(&self) -> Vc<FileSystemPath> {
201        self.source.ident().path()
202    }
203
204    #[turbo_tasks::function]
205    fn asset_context(&self) -> Vc<Box<dyn AssetContext>> {
206        *self.asset_context
207    }
208
209    #[turbo_tasks::function]
210    fn get_inner_asset(self: Vc<Self>, request: Vc<Request>) -> Vc<OptionModule> {
211        self.loader_as_resolve_origin().get_inner_asset(request)
212    }
213}
214
215#[turbo_tasks::value]
216struct ModuleChunkItem {
217    module: ResolvedVc<WebAssemblyModuleAsset>,
218    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
219    module_graph: ResolvedVc<ModuleGraph>,
220}
221
222#[turbo_tasks::value_impl]
223impl OutputAssetsReference for ModuleChunkItem {
224    #[turbo_tasks::function]
225    async fn references(&self) -> Result<Vc<OutputAssetsWithReferenced>> {
226        let loader_references = self.module.loader().references().await?;
227        references_to_output_assets(&*loader_references).await
228    }
229}
230
231#[turbo_tasks::value_impl]
232impl ChunkItem for ModuleChunkItem {
233    #[turbo_tasks::function]
234    fn asset_ident(&self) -> Vc<AssetIdent> {
235        self.module.ident()
236    }
237
238    #[turbo_tasks::function]
239    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
240        *self.chunking_context
241    }
242
243    #[turbo_tasks::function]
244    async fn ty(&self) -> Result<Vc<Box<dyn ChunkType>>> {
245        Ok(Vc::upcast(
246            Vc::<EcmascriptChunkType>::default().resolve().await?,
247        ))
248    }
249
250    #[turbo_tasks::function]
251    fn module(&self) -> Vc<Box<dyn Module>> {
252        Vc::upcast(*self.module)
253    }
254}
255
256#[turbo_tasks::value_impl]
257impl EcmascriptChunkItem for ModuleChunkItem {
258    #[turbo_tasks::function]
259    fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent> {
260        panic!("content() should not be called");
261    }
262
263    #[turbo_tasks::function]
264    async fn content_with_async_module_info(
265        &self,
266        async_module_info: Option<Vc<AsyncModuleInfo>>,
267        _estimated: bool,
268    ) -> Result<Vc<EcmascriptChunkItemContent>> {
269        let loader_asset = self.module.loader();
270        let item = loader_asset.as_chunk_item(*self.module_graph, *self.chunking_context);
271
272        let ecmascript_item = Vc::try_resolve_downcast::<Box<dyn EcmascriptChunkItem>>(item)
273            .await?
274            .context("EcmascriptModuleAsset must implement EcmascriptChunkItem")?;
275
276        let chunk_item_content = ecmascript_item
277            .content_with_async_module_info(async_module_info, false)
278            .owned()
279            .await?;
280
281        Ok(chunk_item_content.cell())
282    }
283}