title: [WebAssembly 入門] 與 Webpack 聯動javascript
date: 2018-4-6 19:40:00html
categories: WebAssembly, 筆記java
tags: WebAssembly, JavaScript, Rust, LLVM toolchainnode
auther: Yiniaureact
常規的進行rust代碼編寫再手動編譯爲wasm文件是十分緩慢的,目前有幾種解決方案,接下來我將基於webpack來提高WebAssembly的編寫效率。webpack
首先,webpack 4 是必須的,此文寫下時的version是 webpack 4.5.0ios
具體的webpack安裝自行解決git
建立一個webpack.config.js
,輸入以下代碼github
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const paths = {
src: path.resolve(__dirname, 'src'),
entryFile: path.resolve(__dirname, 'src', 'index.js'),
dist: path.resolve(__dirname, 'dist'),
wasm: path.relative(__dirname, 'build'),
}
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: __dirname,
hot: true,
port: 10001,
open: true, // will open on browser after started
},
entry: paths.entryFile,
output: {
path: paths.dist,
filename: 'main.js'
},
resolve: {
alias: {
wasm: paths.wasm,
}
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: {
cacheDirectory: true,
}
}],
},
],
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'WebAssembly Hello World'
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
],
};
複製代碼
配置文件解讀就不作了,這是個很基礎的配置,若是想要學習進階配置能夠看看create-react-app
裏eject出來的webpack配置文件web
.babelrc
{
"presets": [
"env"
],
"plugins": [
"syntax-dynamic-import",
"syntax-async-functions"
]
}
複製代碼
這裏開啓了async
支持和import()
動態導入的支持
要注意的是,靜態的import導入.wasm
文件並不被webpack內置支持,webpack會在控制檯打印錯誤信息,提示你換成動態導入(Dynamic import)
webpack 4 內置支持解析 .wasm
文件,而且import不寫後綴時搜索的優先級最高
首先讓咱們看看目錄的狀況
𝝪 yiniau @ yiniau in /Users/yiniau/code/WebAssembly/hello_world
↪ ll
total 952
-rw-r--r-- 1 yiniau staff 52B 3 25 22:14 Cargo.lock
-rw-r--r-- 1 yiniau staff 143B 4 6 21:51 Cargo.toml
drwxr-xr-x 4 yiniau staff 128B 4 6 01:36 build
-rw-r--r-- 1 yiniau staff 12B 3 26 21:53 build.sh
-rw-r--r-- 1 yiniau staff 170B 4 4 16:26 index.html
drwxr-xr-x 809 yiniau staff 25K 4 6 20:34 node_modules
-rw-r--r-- 1 yiniau staff 782B 4 6 20:34 package.json
drwxr-xr-x 3 yiniau staff 96B 4 4 16:41 rust
drwxr-xr-x 4 yiniau staff 128B 4 4 17:16 src
drwxr-xr-x 5 yiniau staff 160B 3 29 15:29 target
-rw-r--r-- 1 yiniau staff 1.2K 4 6 15:51 webpack.config.js
-rw-r--r-- 1 yiniau staff 230K 4 6 01:07 yarn-error.log
-rw-r--r-- 1 yiniau staff 216K 4 6 16:09 yarn.lock
複製代碼
其中
rust
中存放 .rs
文件src
中存放 .js
文件build
中存放 .wasm
文件index.js
爲 entry 指定的入口文件,我在這裏引入polyfill𝝪 yiniau @ yiniau in /Users/yiniau/code/WebAssembly/hello_world
↪ ll src
total 16
-rw-r--r-- 1 yiniau staff 77B 4 6 20:39 index.js
-rw-r--r-- 1 yiniau staff 1.9K 4 6 21:30 main.js
複製代碼
ok,讓咱們在main.js
中完成主要的邏輯吧
main.js
(async () => {
import('../build/hello.wasm')
.then(bytes => bytes.arrayBuffer())
.then(res => WebAssembly.instantiate(bytes, imports))
.then(results => {
console.log(results);
const exports = results.instance.exports;
console.log(exports);
mem = exports.memory;
});
})()
複製代碼
oh heck!! 爲何會報錯!!
沒事,錯誤信息很明確
WebAssembly.Instance is disallowed on the main thread, if the buffer size is larger than 4KB. Use WebAssembly.instantiate.
若是 buffer的大小超過了 4KB,WebAssembly.Instance
在主線程中不被容許使用。須要使用WebAssembly.instantiate
代替,可是問題來了。
import()
並不能傳遞 importsObject。讓咱們去 webpack 的github上找找看issue:
linclark(a cartoon to WebAssembly 的做者) 提出使用 instantiateStreaming
代替 compileStreaming
,以免在ios上因快速內存的限制形成的影響。
sokra 對此表示有點反對(應該是很是反對!)
webpack試圖像ESM同樣對待WASM。 將適用於ESM的全部規則/假設也應用於WASM。假設未來WASM JS API可能會被WASM集成到ESM模塊圖中。
這意味着WASM文件中的imports
部分(importsObject)與ESM中的import
語句同樣被解析,exports
部分(instance.exports)被視爲像ESM中的export
部分。
WASM模塊也有一個start
部分,在WASM實例化時執行。
在WASM中,JS API的導入經過importsObject
傳遞給實例化的WASM模塊。
ESM規範指定了多個階段。一個階段是ModuleEvaluation
。在這個階段,全部的模塊都按照明確的順序進行評估。這個階段是同步的。全部模塊都以相同的「tick」進行評估。
當WASM在模塊圖中時,這意味着:
start
部分在相同的「tick」中執行對於使用Promise的instantiate
,這種行爲是不可能的。一個Promise老是將它的履行延遲到另外一個「tick」中。
只有在使用實例化同步版本(WebAssembly.Instance)時纔可能。
注意:從技術上講,可能會有一個沒有start
部分而且沒有依賴關係的WASM。在這種狀況下,這不適用。但咱們不能認爲狀況老是如此。
webpack想要並行下載/編譯wasm文件與下載JS代碼。使用instantiateStreaming
不會容許這樣作(當WASM具備依賴關係時),由於實例化須要傳遞一個importsObject
。建立importsObject
須要評估WASM的全部依賴項/導入,所以須要在開始下載WASM以前下載這些依賴項。
當使用compileStreaming
+ new WebAssembly.Instance
並行下載和編譯是可能的,由於compileStreaming
不須要一個importsObject
。能夠在WASM和JS下載完成時建立importsObject
。
我也想引用WebAssembly規範。它指出編譯發生在後臺線程中compile
和實例化發生在主線程上。
它沒有明確說明,但在我看來JSC的行爲是不規範的。
WASM也缺少使用導入標識符的實時綁定的能力。相反,importsObject
將被複制。這可能會伴隨奇怪的循環依賴和WASM上的問題。
在importsObject
中支持getter
而且可以在執行start
部分以前得到exports會更好。
.rs
wasm-loader
對於直接使用 rustup wasm32-unknown-unknown 編譯的.wasm文件支持有問題,看了下wasm-loader使用了基於emcc工具鏈產出的wasm文件,我試過直接使用
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: {
cacheDirectory: true,
}
}],
},
{
test: /\.wasm$/,
include: path.resolve(__dirname, 'wasm'),
use: 'wasm-loader',
},
],
複製代碼
可是會報錯:
這應該是工具鏈產出的編碼問題
因而我再次嘗試了使用rust-native-wasm-loader
:
webpack.config.js
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: {
cacheDirectory: true,
}
}],
},
{
test: /\.rs$/,
include: paths.rust,
use: [{
loader: 'wasm-loader'
}, {
loader: 'rust-native-wasm-loader',
options: {
release: true,
},
},]
},
],
複製代碼
rust/add.rs
#[no_mangle]
pub fn add(a: i32, b: i32) -> i32 {
eprintln!("add({:?}, {:?}) was called", a, b);
a + b
}
複製代碼
main.js
import loadAdd from 'rust/add.rs';
loadAdd().then(result => {
const add = result.instance.exports['add'];
console.log('return value was', add(2, 3));
});
複製代碼
BUT!
臣妾作不到啊!!
我已經徹底按照rust-native-wasm-loader的例子改了,但彷佛如今的插件都是在asm.js時代遺留的,都是在解析成.wasm
那一步失敗,是由於WebAssembly不適合以同步方式調用嗎。。就目前來看,若是在rust中調用std::mem
來操做Memory對象,文件大小會很是大——使用wasm-gc
後依舊有200多的KB
#![feature(custom_attribute)]
#![feature(wasm_import_memory)]
#![wasm_import_memory]
use std::mem;
use std::ffi::{CString, CStr};
use std::os::raw::{c_char, c_void};
/// alloc memory
#[no_mangle]
// In order to work with the memory we expose (de)allocation methods
pub extern fn alloc(size: usize) -> *mut c_void {
let mut buf = Vec::with_capacity(size);
let ptr = buf.as_mut_ptr();
mem::forget(buf);
ptr as *mut c_void
}
複製代碼
或許webpack的作法並不適合所有的web assembly的應用模式,以ESM的方式處理.wasm
彷佛很美好,可是實際使用可能會成問題,目前主要仍是js處理邏輯,爲了兼容低版本瀏覽器使用異步處理(或許是)必須的?
2018-4-17 12:00 更新
webassembly的webpack支持PR有更新了!一句不起眼的tip I don't know if this helps but it seems parceljs has got support for rust functions. BY pyros2097 https://medium.com/@devongovett/parcel-v1-5-0-released-source-maps-webassembly-rust-and-more-3a6385e43b95
ok
我滾去用Parcel了...
雖然不能傳imports,單函數開發的話也能用用