WebAssembly 最小可行版本(Minimum Viable Product, MVP)就只有四個對象可以匯入、匯出,也就是到目前為止,看過的函式、全域變數、表格與記憶體。
在匯入的部份,之前就看過了,import
都會包含兩個名稱,分別對應至匯入物件的第一層與第二層特性名稱,不過,import
包含的這兩個名稱,規格上是稱為模組名稱(module name)與匯入名稱(import name)。
這或許意謂著在安排匯入物件時,第一層特性應該是模組名稱空間,像是 Instance
實例上 exports
特性的角色,第二層特性就是模組被匯出的各個對象,像是函式、全域變數、表格與記憶體。
例如,若有個 foo
模組撰寫為:
(module
(func $foo1 (export "foo1") (result i32)
i32.const 1
)
(func $foo2 (export "foo2") (result i32)
i32.const 2
)
)
在這邊看到 export
的另一種風格,可以直接寫在要匯出的函式上,實際上這個模組沒有呼叫任何函式,因此 $foo1
、$foo2
是可以省略的。
若有另一個模組,會需要使用到 foo
模組匯出的函式:
(module
(import "env" "log" (func $log (param i32)))
(import "foo" "foo1" (func $foo1 (result i32)))
(import "foo" "foo2" (func $foo2 (result i32)))
(func $main
call $foo1
call $log
call $foo2
call $log
)
(start $main)
)
目前來說,瀏覽器尚未整合模組的載入、初始化等,因此這並不會下載 env
、foo
模組,目前得自己動手來做:
(async () => {
const foo = await WebAssembly.instantiateStreaming(fetch('foo.wasm'));
const importObj = {
env : {
log(n) {
console.log(n);
}
},
foo : foo.instance.exports
};
WebAssembly.instantiateStreaming(fetch('program.wasm'), importObj);
})();
當然,可以這麼寫的原因在於,事先知道 program
模組,需要從哪個模組 import
函式之類的,問題在於,撰寫 JavaScript 時,怎麼在拿到一個模組時,事先知道它當中要匯入哪些模組的東西呢?
WebAssembly.Module
定義了個 imports
函式,可以取得模組宣告的 import
相關資訊,給它一個 WebAssembly.Module
實例,它會傳回一個陣列,當中的各元素是個物件,擁有 kind
、module
、與 name
三個特性,分別表示匯入的是哪種對象(例如 'function'
)、模組名稱、匯入名稱。
因此,上例也可以改寫為:
function moduleNames(mod, importObj) {
return Array.from(
new Set(
WebAssembly.Module.imports(mod)
.map(impt => impt.module)
.filter(name => !(name in importObj))
)
);
}
(async () => {
const importObj = {
env : {
log(n) {
console.log(n);
}
}
};
const progModule = await WebAssembly.compileStreaming(fetch('program.wasm'));
const names = moduleNames(progModule, importObj);
const results = await Promise.all(
names.map(name => WebAssembly.instantiateStreaming(fetch(`${name}.wasm`)))
);
for(let i = 0; i < names.length; i++) {
importObj[names[i]] = results[i].instance.exports;
}
WebAssembly.instantiate(progModule, importObj);
})();
如果在建立 WebAssembly.Instance
實例之前,需要進一步知道模組匯出的東西,WebAssembly.Module
也有定義 exports
函式來處理這件事。
當然,上面的範例很單純,複雜的情況下,會有模組匯入的順序等問題,不過仔細想想,這應該是未來瀏覽器要處理的事情,或許是在 WebAssembly 可以整合 ES6 模組之後的事情,如果沒有,也應該會有個模組管理程式庫來做這類的事。
在匯入、匯出函式、全域變數、表格與記憶體時,其實有一些要注意的小細節,這些記載在〈Imports〉中,如果你可以跟著文件一路來到這邊,自行參閱應該是不成問題。