diff --git a/Cargo.lock b/Cargo.lock index 0b3a7b6b8..0a21219dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2817,6 +2817,7 @@ dependencies = [ "tungstenite", "twox-hash", "url", + "wasmparser", ] [[package]] @@ -6878,6 +6879,19 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "wasmparser" +version = "0.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19bb9f8ab07616da582ef8adb24c54f1424c7ec876720b7da9db8ec0626c92c" +dependencies = [ + "ahash 0.8.11", + "bitflags 2.6.0", + "hashbrown 0.14.5", + "indexmap 2.5.0", + "semver 1.0.23", +] + [[package]] name = "wayland-client" version = "0.29.5" diff --git a/crates/mako/Cargo.toml b/crates/mako/Cargo.toml index 4c0cc4cc2..b0835cf3f 100644 --- a/crates/mako/Cargo.toml +++ b/crates/mako/Cargo.toml @@ -27,6 +27,7 @@ percent-encoding = { version = "2.3.1" } serde = { workspace = true } serde_json = { workspace = true } url = { version = "2.5.0" } +wasmparser = "0.207.0" swc_core = { workspace = true, features = [ "base", diff --git a/crates/mako/src/plugins/wasm_runtime.rs b/crates/mako/src/plugins/wasm_runtime.rs index 9feb48973..4dd842dbf 100644 --- a/crates/mako/src/plugins/wasm_runtime.rs +++ b/crates/mako/src/plugins/wasm_runtime.rs @@ -1,9 +1,14 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; use std::sync::Arc; use anyhow; +use wasmparser::{Import, Parser, Payload}; +use crate::ast::file::{Content, JsContent}; use crate::compiler::Context; -use crate::plugin::Plugin; +use crate::plugin::{Plugin, PluginLoadParam}; pub struct WasmRuntimePlugin {} @@ -27,4 +32,138 @@ impl Plugin for WasmRuntimePlugin { Ok(vec![]) } } + + fn load( + &self, + param: &PluginLoadParam, + _context: &Arc, + ) -> anyhow::Result> { + let file = param.file; + if file.path.to_string_lossy().ends_with(".wasm") { + let final_file_name = format!( + "{}.{}.{}", + file.get_file_stem(), + file.get_content_hash()?, + file.extname + ); + _context.emit_assets( + file.pathname.to_string_lossy().to_string(), + final_file_name.clone(), + ); + + let mut buffer = Vec::new(); + File::open(&file.path)?.read_to_end(&mut buffer)?; + // Parse wasm file to get imports + let mut wasm_import_object_map: HashMap<&str, Vec> = HashMap::new(); + Parser::new(0).parse_all(&buffer).for_each(|payload| { + if let Ok(Payload::ImportSection(imports)) = payload { + imports.into_iter_with_offsets().for_each(|import| { + if let Ok(( + _, + Import { + module, + name, + ty: _, + }, + )) = import + { + if let Some(import_object) = wasm_import_object_map.get_mut(module) { + import_object.push(name.to_string()); + } else { + wasm_import_object_map.insert(module, vec![name.to_string()]); + } + } + }); + } + }); + + let mut module_import_code = String::new(); + let mut wasm_import_object_code = String::new(); + + for (index, (key, value)) in wasm_import_object_map.iter().enumerate() { + module_import_code.push_str(&format!( + "import * as module{module_idx} from \"{module}\";\n", + module_idx = index, + module = key + )); + + wasm_import_object_code.push_str(&format!( + "\"{module}\": {{ {names} }}", + module = key, + names = value + .iter() + .map(|name| format!("\"{}\": module{}[\"{}\"]", name, index, name)) + .collect::>() + .join(", ") + )); + } + + let mut content = String::new(); + content.push_str(&module_import_code); + + if wasm_import_object_code.is_empty() { + content.push_str(&format!( + "module.exports = require._interopreRequireWasm(exports, \"{}\")", + final_file_name + )); + } else { + content.push_str(&format!( + "module.exports = require._interopreRequireWasm(exports, \"{}\", {{{}}})", + final_file_name, wasm_import_object_code + )); + } + + return Ok(Some(Content::Js(JsContent { + content, + ..Default::default() + }))); + } + + Ok(None) + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use super::*; + use crate::ast::file::File; + use crate::compiler::Context; + + #[test] + fn test_wasm_runtime_load_with_import_object() { + let plugin = WasmRuntimePlugin {}; + let context = Arc::new(Context { + ..Default::default() + }); + let wasm_relative_path = + std::path::Path::new("../../examples/import-resources/minus-wasm-pack/index_bg.wasm"); + let wasm_path = std::fs::canonicalize(wasm_relative_path).unwrap(); + let file = File::new(wasm_path.to_string_lossy().to_string(), context.clone()); + let param = PluginLoadParam { file: &file }; + let result: Option = plugin.load(¶m, &context).unwrap(); + + assert!(result.is_some()); + if let Some(Content::Js(js_content)) = result { + assert!(js_content.content.contains("import * as module0 from")); + } + } + + #[test] + fn test_wasm_runtime_load_without_import_object() { + let plugin = WasmRuntimePlugin {}; + let context = Arc::new(Context { + ..Default::default() + }); + let wasm_relative_path = std::path::Path::new("../../examples/import-resources/add.wasm"); + let wasm_path = std::fs::canonicalize(wasm_relative_path).unwrap(); + let file = File::new(wasm_path.to_string_lossy().to_string(), context.clone()); + let param = PluginLoadParam { file: &file }; + let result = plugin.load(¶m, &context).unwrap(); + assert!(result.is_some()); + if let Some(Content::Js(js_content)) = result { + assert!(!js_content.content.contains("import * as module0 from")) + } + } } diff --git a/examples/import-resources/index.tsx b/examples/import-resources/index.tsx index f5313c77e..afdc166e2 100644 --- a/examples/import-resources/index.tsx +++ b/examples/import-resources/index.tsx @@ -7,6 +7,7 @@ import toml, { title } from './index.toml'; import xml from './index.xml'; import yaml, { pi } from './index.yaml'; import MailchimpUnsplash from './mailchimp-unsplash.jpg'; +import * as wasm from './minus-wasm-pack'; const num1 = 10; const num2 = 20; @@ -32,6 +33,10 @@ function App() {

Test import .wasm file async: {num1} + {num2} = {sum}

+

+ Test import .wasm file(generated by wasm-pack) async: {num1} - {num2} ={' '} + {wasm.minus(num1, num2)} +

Test import .toml file

{JSON.stringify(toml, null, 2)}
diff --git a/examples/import-resources/minus-wasm-pack/index.d.ts b/examples/import-resources/minus-wasm-pack/index.d.ts new file mode 100644 index 000000000..1a32e9455 --- /dev/null +++ b/examples/import-resources/minus-wasm-pack/index.d.ts @@ -0,0 +1,15 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + */ +export function greet(): void; +/** + * @param {string} name + */ +export function greet2(name: string): void; +/** + * @param {number} a + * @param {number} b + * @returns {number} + */ +export function minus(a: number, b: number): number; diff --git a/examples/import-resources/minus-wasm-pack/index.js b/examples/import-resources/minus-wasm-pack/index.js new file mode 100644 index 000000000..870892045 --- /dev/null +++ b/examples/import-resources/minus-wasm-pack/index.js @@ -0,0 +1,4 @@ +import { __wbg_set_wasm } from './index_bg.js'; +import * as wasm from './index_bg.wasm'; +__wbg_set_wasm(wasm); +export * from './index_bg.js'; diff --git a/examples/import-resources/minus-wasm-pack/index_bg.js b/examples/import-resources/minus-wasm-pack/index_bg.js new file mode 100644 index 000000000..e0cd54229 --- /dev/null +++ b/examples/import-resources/minus-wasm-pack/index_bg.js @@ -0,0 +1,133 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + +const lTextDecoder = + typeof TextDecoder === 'undefined' + ? (0, module.require)('util').TextDecoder + : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { + ignoreBOM: true, + fatal: true, +}); + +cachedTextDecoder.decode(); + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if ( + cachedUint8ArrayMemory0 === null || + cachedUint8ArrayMemory0.byteLength === 0 + ) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode( + getUint8ArrayMemory0().subarray(ptr, ptr + len), + ); +} +/** + */ +export function greet() { + wasm.greet(); +} + +let WASM_VECTOR_LEN = 0; + +const lTextEncoder = + typeof TextEncoder === 'undefined' + ? (0, module.require)('util').TextEncoder + : TextEncoder; + +let cachedTextEncoder = new lTextEncoder('utf-8'); + +const encodeString = + typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); + } + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length, + }; + }; + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0() + .subarray(ptr, ptr + buf.length) + .set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7f) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} +/** + * @param {string} name + */ +export function greet2(name) { + const ptr0 = passStringToWasm0( + name, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + wasm.greet2(ptr0, len0); +} + +/** + * @param {number} a + * @param {number} b + * @returns {number} + */ +export function minus(a, b) { + const ret = wasm.minus(a, b); + return ret; +} + +export function __wbg_alert_f837f172b2a24942(arg0, arg1) { + alert(getStringFromWasm0(arg0, arg1)); +} + +export function __wbg_prompt_ec584a06a1c7c28b(arg0, arg1) { + prompt(getStringFromWasm0(arg0, arg1)); +} diff --git a/examples/import-resources/minus-wasm-pack/index_bg.wasm b/examples/import-resources/minus-wasm-pack/index_bg.wasm new file mode 100644 index 000000000..a50256cbd Binary files /dev/null and b/examples/import-resources/minus-wasm-pack/index_bg.wasm differ diff --git a/examples/import-resources/minus-wasm-pack/index_bg.wasm.d.ts b/examples/import-resources/minus-wasm-pack/index_bg.wasm.d.ts new file mode 100644 index 000000000..183cf3457 --- /dev/null +++ b/examples/import-resources/minus-wasm-pack/index_bg.wasm.d.ts @@ -0,0 +1,13 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export function greet2(a: number, b: number): void; +export function minus(a: number, b: number): number; +export function greet(): void; +export function __wbindgen_malloc(a: number, b: number): number; +export function __wbindgen_realloc( + a: number, + b: number, + c: number, + d: number, +): number;