Skip to main content

turbopack_wasm/
module_asset.rs

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