turbopack_wasm/
module_asset.rs1use anyhow::{Result, bail};
2use turbo_rcstr::rcstr;
3use turbo_tasks::{ResolvedVc, Vc, fxindexmap};
4use turbo_tasks_fs::{FileSystem, FileSystemPath};
5use turbopack_core::{
6 chunk::{AsyncModuleInfo, ChunkableModule, ChunkingContext},
7 context::AssetContext,
8 environment::ChunkLoading,
9 file_source::FileSource,
10 ident::AssetIdent,
11 module::{Module, ModuleSideEffects},
12 module_graph::ModuleGraph,
13 output::OutputAssetsWithReferenced,
14 reference::{ModuleReferences, SingleChunkableModuleReference},
15 reference_type::ReferenceType,
16 resolve::{ExportUsage, origin::ResolveOrigin},
17 source::{OptionSource, Source},
18};
19use turbopack_ecmascript::{
20 chunk::{
21 EcmascriptChunkItemContent, EcmascriptChunkPlaceable, EcmascriptExports,
22 ecmascript_chunk_item,
23 },
24 chunk_item_content_with_code_from,
25 references::async_module::OptionAsyncModule,
26};
27
28use crate::{
29 embed,
30 loader::{compiling_loader_source, instantiating_loader_source},
31 output_asset::WebAssemblyAsset,
32 raw::RawWebAssemblyModuleAsset,
33 source::WebAssemblySource,
34};
35
36#[turbo_tasks::value]
39#[derive(Clone)]
40pub struct WebAssemblyModuleAsset {
41 source: ResolvedVc<WebAssemblySource>,
42 asset_context: ResolvedVc<Box<dyn AssetContext>>,
43 origin_path: FileSystemPath,
45}
46
47#[turbo_tasks::value_impl]
48impl WebAssemblyModuleAsset {
49 #[turbo_tasks::function]
50 pub async fn new(
51 source: ResolvedVc<WebAssemblySource>,
52 asset_context: ResolvedVc<Box<dyn AssetContext>>,
53 ) -> Result<Vc<Self>> {
54 Ok(Self::cell(WebAssemblyModuleAsset {
55 origin_path: source.ident().await?.path.clone(),
56 source,
57 asset_context,
58 }))
59 }
60
61 #[turbo_tasks::function]
62 fn wasm_asset(&self, chunking_context: Vc<Box<dyn ChunkingContext>>) -> Vc<WebAssemblyAsset> {
63 WebAssemblyAsset::new(*self.source, chunking_context)
64 }
65
66 #[turbo_tasks::function]
67 async fn loader_as_module(&self) -> Result<Vc<Box<dyn Module>>> {
68 let query = &self.source.ident().await?.query;
69
70 let chunk_loading = self
71 .asset_context
72 .compile_time_info()
73 .environment()
74 .chunk_loading()
75 .await?;
76
77 let is_edge = matches!(*chunk_loading, ChunkLoading::Edge);
78
79 let loader_source = if query == "?module" {
80 compiling_loader_source(*self.source, is_edge)
81 } else {
82 instantiating_loader_source(*self.source, is_edge)
83 };
84
85 let helper_path = match *chunk_loading {
86 ChunkLoading::Edge => rcstr!("edge/loadWasm.ts"),
87 ChunkLoading::NodeJs => rcstr!("node/loadWasm.ts"),
88 ChunkLoading::Dom => rcstr!("browser/loadWasm.ts"),
89 ChunkLoading::SingleChunk => unreachable!(
90 "Environment::chunk_loading never returns SingleChunk; single-chunk WASM is \
91 rejected in chunk_item_content"
92 ),
93 };
94
95 let helper = self
96 .asset_context
97 .process(
98 Vc::upcast(FileSource::new(
99 embed::embed_fs().root().await?.join(&helper_path)?,
100 )),
101 ReferenceType::Runtime,
107 )
108 .module()
109 .to_resolved()
110 .await?;
111
112 let module = self.asset_context.process(
113 loader_source,
114 ReferenceType::Internal(ResolvedVc::cell(fxindexmap! {
115 rcstr!("WASM_PATH") => ResolvedVc::upcast(RawWebAssemblyModuleAsset::new(*self.source, *self.asset_context).to_resolved().await?),
116 rcstr!("WASM_HELPER") => helper,
117 })),
118 ).module();
119
120 Ok(module)
121 }
122 #[turbo_tasks::function]
123 async fn loader_as_resolve_origin(self: Vc<Self>) -> Result<Vc<Box<dyn ResolveOrigin>>> {
124 let module = self.loader_as_module();
125
126 let Some(esm_asset) =
127 ResolvedVc::try_sidecast::<Box<dyn ResolveOrigin>>(module.to_resolved().await?)
128 else {
129 bail!("WASM loader was not processed into an EcmascriptModuleAsset");
130 };
131
132 Ok(*esm_asset)
133 }
134
135 #[turbo_tasks::function]
136 async fn loader(self: Vc<Self>) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {
137 let module = self.loader_as_module();
138
139 let Some(esm_asset) = ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(
140 module.to_resolved().await?,
141 ) else {
142 bail!("WASM loader was not processed into an EcmascriptModuleAsset");
143 };
144
145 Ok(*esm_asset)
146 }
147
148 #[turbo_tasks::function]
149 async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
150 Ok(Vc::cell(vec![ResolvedVc::upcast(
151 SingleChunkableModuleReference::new(
152 Vc::upcast(self.loader()),
153 rcstr!("wasm loader"),
154 ExportUsage::all(),
155 )
156 .to_resolved()
157 .await?,
158 )]))
159 }
160}
161
162#[turbo_tasks::value_impl]
163impl Module for WebAssemblyModuleAsset {
164 #[turbo_tasks::function]
165 async fn ident(&self) -> Result<Vc<AssetIdent>> {
166 Ok(self
167 .source
168 .ident()
169 .owned()
170 .await?
171 .with_modifier(rcstr!("wasm module"))
172 .with_layer(self.asset_context.into_trait_ref().await?.layer())
173 .into_vc())
174 }
175
176 #[turbo_tasks::function]
177 fn source(&self) -> Vc<OptionSource> {
178 Vc::cell(Some(ResolvedVc::upcast(self.source)))
179 }
180
181 #[turbo_tasks::function]
182 fn references(self: Vc<Self>) -> Vc<ModuleReferences> {
183 self.loader().references()
184 }
185
186 #[turbo_tasks::function]
187 fn is_self_async(self: Vc<Self>) -> Vc<bool> {
188 Vc::cell(true)
189 }
190
191 #[turbo_tasks::function]
192 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
193 ModuleSideEffects::SideEffectful.cell()
197 }
198}
199
200#[turbo_tasks::value_impl]
201impl ChunkableModule for WebAssemblyModuleAsset {
202 #[turbo_tasks::function]
203 fn as_chunk_item(
204 self: ResolvedVc<Self>,
205 module_graph: ResolvedVc<ModuleGraph>,
206 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
207 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
208 ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
209 }
210}
211
212#[turbo_tasks::value_impl]
213impl EcmascriptChunkPlaceable for WebAssemblyModuleAsset {
214 #[turbo_tasks::function]
215 fn get_exports(self: Vc<Self>) -> Vc<EcmascriptExports> {
216 self.loader().get_exports().borrowed()
219 }
220
221 #[turbo_tasks::function]
222 fn get_async_module(self: Vc<Self>) -> Vc<OptionAsyncModule> {
223 self.loader().get_async_module()
224 }
225
226 #[turbo_tasks::function]
227 async fn chunk_item_content(
228 self: ResolvedVc<Self>,
229 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
230 _module_graph: Vc<ModuleGraph>,
231 async_module_info: Option<Vc<AsyncModuleInfo>>,
232 _estimated: bool,
233 ) -> Result<Vc<EcmascriptChunkItemContent>> {
234 if matches!(
235 *chunking_context.chunk_loading().await?,
236 ChunkLoading::SingleChunk
237 ) {
238 bail!(
239 "WebAssembly imports are not supported in single-chunk (service-worker) \
240 entrypoints"
241 );
242 }
243
244 Ok(chunk_item_content_with_code_from(
247 *ResolvedVc::upcast(self),
248 self.loader(),
249 *chunking_context,
250 async_module_info,
251 ))
252 }
253
254 #[turbo_tasks::function]
255 async fn chunk_item_output_assets(
256 self: Vc<Self>,
257 chunking_context: Vc<Box<dyn ChunkingContext>>,
258 _module_graph: Vc<ModuleGraph>,
259 ) -> Result<Vc<OutputAssetsWithReferenced>> {
260 let wasm_asset = self.wasm_asset(chunking_context).to_resolved().await?;
261 Ok(OutputAssetsWithReferenced::from_assets(Vc::cell(vec![
262 ResolvedVc::upcast(wasm_asset),
263 ])))
264 }
265}
266
267#[turbo_tasks::value_impl]
268impl ResolveOrigin for WebAssemblyModuleAsset {
269 fn origin_path(&self) -> FileSystemPath {
270 self.origin_path.clone()
271 }
272
273 fn asset_context(&self) -> ResolvedVc<Box<dyn AssetContext>> {
274 self.asset_context
275 }
276}