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    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/// Creates a javascript loader which instantiates the WebAssembly source and
36/// re-exports its exports.
37#[turbo_tasks::value]
38#[derive(Clone)]
39pub struct WebAssemblyModuleAsset {
40    source: ResolvedVc<WebAssemblySource>,
41    asset_context: ResolvedVc<Box<dyn AssetContext>>,
42    /// The path of `source`, precomputed so that `ResolveOrigin::origin_path` is synchronous.
43    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        let is_edge = matches!(*chunk_loading, ChunkLoading::Edge);
76
77        let loader_source = if query == "?module" {
78            compiling_loader_source(*self.source, is_edge)
79        } else {
80            instantiating_loader_source(*self.source, is_edge)
81        };
82
83        let helper_path = match *chunk_loading {
84            ChunkLoading::Edge => rcstr!("edge/loadWasm.ts"),
85            ChunkLoading::NodeJs => rcstr!("node/loadWasm.ts"),
86            ChunkLoading::Dom => rcstr!("browser/loadWasm.ts"),
87        };
88
89        let helper = self
90            .asset_context
91            .process(
92                Vc::upcast(FileSource::new(
93                    embed::embed_fs().root().await?.join(&helper_path)?,
94                )),
95                /*
96                   TODO (@sampoder): ideally, we would have some sort of hint
97                   here that suggests whether we are using `compileModule()` or
98                   `instantiate()` to avoid loading the other unused function.
99                */
100                ReferenceType::Runtime,
101            )
102            .module()
103            .to_resolved()
104            .await?;
105
106        let module = self.asset_context.process(
107            loader_source,
108            ReferenceType::Internal(ResolvedVc::cell(fxindexmap! {
109                rcstr!("WASM_PATH") => ResolvedVc::upcast(RawWebAssemblyModuleAsset::new(*self.source, *self.asset_context).to_resolved().await?),
110                rcstr!("WASM_HELPER") => helper,
111            })),
112        ).module();
113
114        Ok(module)
115    }
116    #[turbo_tasks::function]
117    async fn loader_as_resolve_origin(self: Vc<Self>) -> Result<Vc<Box<dyn ResolveOrigin>>> {
118        let module = self.loader_as_module();
119
120        let Some(esm_asset) =
121            ResolvedVc::try_sidecast::<Box<dyn ResolveOrigin>>(module.to_resolved().await?)
122        else {
123            bail!("WASM loader was not processed into an EcmascriptModuleAsset");
124        };
125
126        Ok(*esm_asset)
127    }
128
129    #[turbo_tasks::function]
130    async fn loader(self: Vc<Self>) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {
131        let module = self.loader_as_module();
132
133        let Some(esm_asset) = ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(
134            module.to_resolved().await?,
135        ) else {
136            bail!("WASM loader was not processed into an EcmascriptModuleAsset");
137        };
138
139        Ok(*esm_asset)
140    }
141
142    #[turbo_tasks::function]
143    async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
144        Ok(Vc::cell(vec![ResolvedVc::upcast(
145            SingleChunkableModuleReference::new(
146                Vc::upcast(self.loader()),
147                rcstr!("wasm loader"),
148                ExportUsage::all(),
149            )
150            .to_resolved()
151            .await?,
152        )]))
153    }
154}
155
156#[turbo_tasks::value_impl]
157impl Module for WebAssemblyModuleAsset {
158    #[turbo_tasks::function]
159    async fn ident(&self) -> Result<Vc<AssetIdent>> {
160        Ok(self
161            .source
162            .ident()
163            .owned()
164            .await?
165            .with_modifier(rcstr!("wasm module"))
166            .with_layer(self.asset_context.into_trait_ref().await?.layer())
167            .into_vc())
168    }
169
170    #[turbo_tasks::function]
171    fn source(&self) -> Vc<OptionSource> {
172        Vc::cell(Some(ResolvedVc::upcast(self.source)))
173    }
174
175    #[turbo_tasks::function]
176    fn references(self: Vc<Self>) -> Vc<ModuleReferences> {
177        self.loader().references()
178    }
179
180    #[turbo_tasks::function]
181    fn is_self_async(self: Vc<Self>) -> Vc<bool> {
182        Vc::cell(true)
183    }
184
185    #[turbo_tasks::function]
186    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
187        // Both versions of this module have a top level await that instantiates a wasm module
188        // wasm module instantiation can trigger arbitrary side effects from the native start
189        // function
190        ModuleSideEffects::SideEffectful.cell()
191    }
192}
193
194#[turbo_tasks::value_impl]
195impl ChunkableModule for WebAssemblyModuleAsset {
196    #[turbo_tasks::function]
197    fn as_chunk_item(
198        self: ResolvedVc<Self>,
199        module_graph: ResolvedVc<ModuleGraph>,
200        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
201    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
202        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
203    }
204}
205
206#[turbo_tasks::value_impl]
207impl EcmascriptChunkPlaceable for WebAssemblyModuleAsset {
208    #[turbo_tasks::function]
209    fn get_exports(self: Vc<Self>) -> Vc<EcmascriptExports> {
210        self.loader().get_exports()
211    }
212
213    #[turbo_tasks::function]
214    fn get_async_module(self: Vc<Self>) -> Vc<OptionAsyncModule> {
215        self.loader().get_async_module()
216    }
217
218    #[turbo_tasks::function]
219    async fn chunk_item_content(
220        self: Vc<Self>,
221        chunking_context: Vc<Box<dyn ChunkingContext>>,
222        module_graph: Vc<ModuleGraph>,
223        async_module_info: Option<Vc<AsyncModuleInfo>>,
224        estimated: bool,
225    ) -> Result<Vc<EcmascriptChunkItemContent>> {
226        // Delegate to the loader's chunk item content
227        Ok(self.loader().chunk_item_content(
228            chunking_context,
229            module_graph,
230            async_module_info,
231            estimated,
232        ))
233    }
234
235    #[turbo_tasks::function]
236    async fn chunk_item_output_assets(
237        self: Vc<Self>,
238        chunking_context: Vc<Box<dyn ChunkingContext>>,
239        _module_graph: Vc<ModuleGraph>,
240    ) -> Result<Vc<OutputAssetsWithReferenced>> {
241        let wasm_asset = self.wasm_asset(chunking_context).to_resolved().await?;
242        Ok(OutputAssetsWithReferenced::from_assets(Vc::cell(vec![
243            ResolvedVc::upcast(wasm_asset),
244        ])))
245    }
246}
247
248#[turbo_tasks::value_impl]
249impl ResolveOrigin for WebAssemblyModuleAsset {
250    fn origin_path(&self) -> FileSystemPath {
251        self.origin_path.clone()
252    }
253
254    fn asset_context(&self) -> ResolvedVc<Box<dyn AssetContext>> {
255        self.asset_context
256    }
257}