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
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                /*
101                   TODO (@sampoder): ideally, we would have some sort of hint
102                   here that suggests whether we are using `compileModule()` or
103                   `instantiate()` to avoid loading the other unused function.
104                */
105                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        // Both versions of this module have a top level await that instantiates a wasm module
193        // wasm module instantiation can trigger arbitrary side effects from the native start
194        // function
195        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        // Delegate to the loader's chunk item content
242        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}