Skip to main content

turbopack_wasm/
module_asset.rs

1use 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/// 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    /// The path of `source`, precomputed so that `ResolveOrigin::origin_path` is synchronous.
44    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                /*
102                   TODO (@sampoder): ideally, we would have some sort of hint
103                   here that suggests whether we are using `compileModule()` or
104                   `instantiate()` to avoid loading the other unused function.
105                */
106                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        // Both versions of this module have a top level await that instantiates a wasm module
194        // wasm module instantiation can trigger arbitrary side effects from the native start
195        // function
196        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        // This module hands out the *loader* module's exports as its own, so they must not carry
217        // a mangling decision — see `EcmascriptExports::borrowed`.
218        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        // Generate the loader's code under this module's graph identity. The loader is a
245        // separately processed virtual module and has no export-usage entry of its own.
246        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}