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, OptionModule},
14    module_graph::ModuleGraph,
15    output::OutputAssets,
16    reference::{ModuleReferences, SingleChunkableModuleReference},
17    reference_type::ReferenceType,
18    resolve::{ExportUsage, origin::ResolveOrigin, parse::Request},
19    source::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 references(self: Vc<Self>) -> Vc<ModuleReferences> {
135        self.loader().references()
136    }
137
138    #[turbo_tasks::function]
139    fn is_self_async(self: Vc<Self>) -> Vc<bool> {
140        Vc::cell(true)
141    }
142}
143
144#[turbo_tasks::value_impl]
145impl Asset for WebAssemblyModuleAsset {
146    #[turbo_tasks::function]
147    fn content(&self) -> Vc<AssetContent> {
148        self.source.content()
149    }
150}
151
152#[turbo_tasks::value_impl]
153impl ChunkableModule for WebAssemblyModuleAsset {
154    #[turbo_tasks::function]
155    fn as_chunk_item(
156        self: ResolvedVc<Self>,
157        module_graph: ResolvedVc<ModuleGraph>,
158        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
159    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
160        Vc::upcast(
161            ModuleChunkItem {
162                module: self,
163                module_graph,
164                chunking_context,
165            }
166            .cell(),
167        )
168    }
169}
170
171#[turbo_tasks::value_impl]
172impl EcmascriptChunkPlaceable for WebAssemblyModuleAsset {
173    #[turbo_tasks::function]
174    fn get_exports(self: Vc<Self>) -> Vc<EcmascriptExports> {
175        self.loader().get_exports()
176    }
177
178    #[turbo_tasks::function]
179    fn get_async_module(self: Vc<Self>) -> Vc<OptionAsyncModule> {
180        self.loader().get_async_module()
181    }
182}
183
184#[turbo_tasks::value_impl]
185impl ResolveOrigin for WebAssemblyModuleAsset {
186    #[turbo_tasks::function]
187    fn origin_path(&self) -> Vc<FileSystemPath> {
188        self.source.ident().path()
189    }
190
191    #[turbo_tasks::function]
192    fn asset_context(&self) -> Vc<Box<dyn AssetContext>> {
193        *self.asset_context
194    }
195
196    #[turbo_tasks::function]
197    fn get_inner_asset(self: Vc<Self>, request: Vc<Request>) -> Vc<OptionModule> {
198        self.loader_as_resolve_origin().get_inner_asset(request)
199    }
200}
201
202#[turbo_tasks::value]
203struct ModuleChunkItem {
204    module: ResolvedVc<WebAssemblyModuleAsset>,
205    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
206    module_graph: ResolvedVc<ModuleGraph>,
207}
208
209#[turbo_tasks::value_impl]
210impl ChunkItem for ModuleChunkItem {
211    #[turbo_tasks::function]
212    fn asset_ident(&self) -> Vc<AssetIdent> {
213        self.module.ident()
214    }
215
216    #[turbo_tasks::function]
217    async fn references(&self) -> Result<Vc<OutputAssets>> {
218        let loader_references = self.module.loader().references().await?;
219        references_to_output_assets(&*loader_references).await
220    }
221
222    #[turbo_tasks::function]
223    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
224        Vc::upcast(*self.chunking_context)
225    }
226
227    #[turbo_tasks::function]
228    async fn ty(&self) -> Result<Vc<Box<dyn ChunkType>>> {
229        Ok(Vc::upcast(
230            Vc::<EcmascriptChunkType>::default().resolve().await?,
231        ))
232    }
233
234    #[turbo_tasks::function]
235    fn module(&self) -> Vc<Box<dyn Module>> {
236        Vc::upcast(*self.module)
237    }
238}
239
240#[turbo_tasks::value_impl]
241impl EcmascriptChunkItem for ModuleChunkItem {
242    #[turbo_tasks::function]
243    fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent> {
244        panic!("content() should not be called");
245    }
246
247    #[turbo_tasks::function]
248    async fn content_with_async_module_info(
249        &self,
250        async_module_info: Option<Vc<AsyncModuleInfo>>,
251    ) -> Result<Vc<EcmascriptChunkItemContent>> {
252        let loader_asset = self.module.loader();
253        let item =
254            loader_asset.as_chunk_item(*self.module_graph, Vc::upcast(*self.chunking_context));
255
256        let ecmascript_item = Vc::try_resolve_downcast::<Box<dyn EcmascriptChunkItem>>(item)
257            .await?
258            .context("EcmascriptModuleAsset must implement EcmascriptChunkItem")?;
259
260        let mut chunk_item_content = ecmascript_item
261            .content_with_async_module_info(async_module_info)
262            .owned()
263            .await?;
264
265        chunk_item_content.options.wasm = true;
266
267        Ok(chunk_item_content.cell())
268    }
269}