use rspack_core::{
BoxPlugin, ChunkUkey, Compilation, CompilationRuntimeRequirementInTree, Plugin, PluginExt,
RuntimeGlobals, RuntimeModule, RuntimeModuleExt, WasmLoading, WasmLoadingType,
};
use rspack_error::Result;
use rspack_hook::{plugin, plugin_hook};
use crate::runtime::AsyncWasmLoadingRuntimeModule;
pub fn enable_wasm_loading_plugin(wasm_loading_type: WasmLoadingType) -> BoxPlugin {
match wasm_loading_type {
WasmLoadingType::Fetch => FetchCompileAsyncWasmPlugin::default().boxed(),
WasmLoadingType::AsyncNode => ReadFileCompileAsyncWasmPlugin::new().boxed(),
WasmLoadingType::Universal => UniversalCompileAsyncWasmPlugin::default().boxed(),
}
}
#[plugin]
#[derive(Debug, Default)]
pub struct FetchCompileAsyncWasmPlugin;
#[plugin_hook(CompilationRuntimeRequirementInTree for FetchCompileAsyncWasmPlugin)]
async fn fetch_compile_async_wasm_plugin_runtime_requirements_in_tree(
&self,
compilation: &Compilation,
chunk_ukey: &ChunkUkey,
_all_runtime_requirements: &RuntimeGlobals,
runtime_requirements: &RuntimeGlobals,
runtime_requirements_mut: &mut RuntimeGlobals,
runtime_modules_to_add: &mut Vec<(ChunkUkey, Box<dyn RuntimeModule>)>,
) -> Result<Option<()>> {
if !runtime_requirements.contains(RuntimeGlobals::INSTANTIATE_WASM) {
return Ok(None);
}
let runtime_template = compilation.runtime_template.create_runtime_code_template();
runtime_requirements_mut.insert(RuntimeGlobals::PUBLIC_PATH);
runtime_modules_to_add.push((
*chunk_ukey,
AsyncWasmLoadingRuntimeModule::new(
&compilation.runtime_template,
format!(
"fetch({} + $PATH)",
runtime_template.render_runtime_globals(&RuntimeGlobals::PUBLIC_PATH)
),
true,
)
.boxed(),
));
Ok(None)
}
impl Plugin for FetchCompileAsyncWasmPlugin {
fn name(&self) -> &'static str {
"FetchCompileAsyncWasmPlugin"
}
fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
ctx
.compilation_hooks
.runtime_requirement_in_tree
.tap(fetch_compile_async_wasm_plugin_runtime_requirements_in_tree::new(self));
Ok(())
}
}
#[plugin]
#[derive(Debug)]
pub struct ReadFileCompileAsyncWasmPlugin {}
impl ReadFileCompileAsyncWasmPlugin {
fn new() -> Self {
Self::new_inner()
}
}
#[plugin_hook(CompilationRuntimeRequirementInTree for ReadFileCompileAsyncWasmPlugin)]
async fn read_file_compile_async_wasm_plugin_runtime_requirements_in_tree(
&self,
compilation: &Compilation,
chunk_ukey: &ChunkUkey,
_all_runtime_requirements: &RuntimeGlobals,
runtime_requirements: &RuntimeGlobals,
_runtime_requirements_mut: &mut RuntimeGlobals,
runtime_modules_to_add: &mut Vec<(ChunkUkey, Box<dyn RuntimeModule>)>,
) -> Result<Option<()>> {
if !runtime_requirements.contains(RuntimeGlobals::INSTANTIATE_WASM) {
return Ok(None);
}
let import_enabled = compilation.options.output.module
&& compilation
.options
.output
.environment
.supports_dynamic_import();
runtime_modules_to_add.push((
*chunk_ukey,
AsyncWasmLoadingRuntimeModule::new(
&compilation.runtime_template,
if import_enabled {
include_str!("runtime/read_file_compile_async_wasm_with_import.js").to_string()
} else {
include_str!("runtime/read_file_compile_async_wasm.js").to_string()
},
false,
)
.boxed(),
));
Ok(None)
}
impl Plugin for ReadFileCompileAsyncWasmPlugin {
fn name(&self) -> &'static str {
"ReadFileCompileAsyncWasmPlugin"
}
fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
ctx
.compilation_hooks
.runtime_requirement_in_tree
.tap(read_file_compile_async_wasm_plugin_runtime_requirements_in_tree::new(self));
Ok(())
}
}
#[plugin]
#[derive(Debug, Default)]
pub struct UniversalCompileAsyncWasmPlugin;
#[plugin_hook(CompilationRuntimeRequirementInTree for UniversalCompileAsyncWasmPlugin)]
async fn universal_compile_async_wasm_plugin_runtime_requirements_in_tree(
&self,
compilation: &Compilation,
chunk_ukey: &ChunkUkey,
_all_runtime_requirements: &RuntimeGlobals,
runtime_requirements: &RuntimeGlobals,
_runtime_requirements_mut: &mut RuntimeGlobals,
runtime_modules_to_add: &mut Vec<(ChunkUkey, Box<dyn RuntimeModule>)>,
) -> Result<Option<()>> {
if !runtime_requirements.contains(RuntimeGlobals::INSTANTIATE_WASM) {
return Ok(None);
}
let chunk = compilation
.build_chunk_graph_artifact
.chunk_by_ukey
.expect_get(chunk_ukey);
let wasm_loading = chunk
.get_entry_options(&compilation.build_chunk_graph_artifact.chunk_group_by_ukey)
.and_then(|options| options.wasm_loading.clone())
.unwrap_or_else(|| compilation.options.output.wasm_loading.clone());
let is_enabled_for_chunk = matches!(
wasm_loading,
WasmLoading::Enable(WasmLoadingType::Universal)
);
if !is_enabled_for_chunk {
return Ok(None);
}
let import_meta_name = &compilation.options.output.import_meta_name;
let generate_before_load_binary_code =
r#"var useFetch = typeof document !== 'undefined' || typeof self !== 'undefined';
var wasmUrl = $PATH;"#
.to_string();
let generate_load_binary_code = format!(
r#"(useFetch
? fetch(new URL(wasmUrl, {import_meta_name}.url))
: Promise.all([import('fs'), import('url')]).then(([{{ readFile }}, {{ URL }}]) => new Promise((resolve, reject) => {{
readFile(new URL(wasmUrl, {import_meta_name}.url), (err, buffer) => {{
if (err) return reject(err);
// Fake fetch response
resolve({{
arrayBuffer() {{ return buffer; }}
}});
}});
}})))"#
);
let generate_before_instantiate_streaming = r#"if (!useFetch) {
return fallback();
}"#
.to_string();
runtime_modules_to_add.push((
*chunk_ukey,
AsyncWasmLoadingRuntimeModule::new_with_before_streaming(
&compilation.runtime_template,
generate_load_binary_code,
generate_before_load_binary_code,
generate_before_instantiate_streaming,
true, )
.boxed(),
));
Ok(None)
}
impl Plugin for UniversalCompileAsyncWasmPlugin {
fn name(&self) -> &'static str {
"UniversalCompileAsyncWasmPlugin"
}
fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
ctx
.compilation_hooks
.runtime_requirement_in_tree
.tap(universal_compile_async_wasm_plugin_runtime_requirements_in_tree::new(self));
Ok(())
}
}