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,单函数开发的话也能用用