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