<!-- 封裝的模板下載和導入按鈕和功能組件--> <template> <span style="margin-left: 10px"> <el-button size="mini" class="el-icon-download" @click="downFiles"> 下載模板</el-but ...
前言
- 之前老是聽別人提到
WebAssembly
這個詞,一直對其比較模糊,不能理解是個啥東西,後來自己實踐了一下,發現其實就是一種提高代碼性能的手段。
簡介
- WebAssembly 是一種運行在現代網路瀏覽器中的新型代碼,並且提供新的性能特性和效果。它設計的目的不是為了手寫代碼而是為諸如 C、C++和 Rust 等低級源語言提供一個高效的編譯目標。(解釋來自MDN)
- 通俗一點來講,就是利用一些C、C++、Rust等偏底層的一些語言去實現部分功能並編譯成二進位文件然後暴露給第三方平臺(可能是瀏覽器端,也可能是服務端,還有可能是客戶端),可以豐富和優化相關的應用。
- 當然了,其實也有類似asm.js這種非底層語言去實現,但是它編譯後的二進位文件減少了編譯的過程,其實也是可以提高代碼的性能。
開始嘗試
準備工作
- 我準備使用rust來演示一下,所以需要用到的第三方環境如下
- 安裝
Rust
,curl https://sh.rustup.rs -sSf | sh -s -- --help
。windows上可以下載https://static.rust-lang.org/rustup/dist/i686-pc-windows-gnu/rustup-init.exe。安裝成功後可以在命令行輸入rustc -V
以及cargo -V
來看看是否安裝成功,cargo
是Rust
一個包管理器,類似npm
之於nodejs
。 - 安裝
wasm-pack
,參照官網https://rustwasm.github.io/wasm-pack/installer/。 Rust
基礎語法,可以參考https://course.rs/about-book.html這位大佬編寫的書,不枯燥又有趣,比官方的中文文檔可讀性更強。
- 安裝
開始
- 使用上面安裝好的
wasm-pack
新建一個空項目,wasm-pack new test-webassembly
。 - 查看目錄結構
test-webassembly
src
lib.rc // 主入口
utils.rs // 依賴的工具方法tests
web.rs // 測試文件
.appveyor.yml // 項目的前置依賴配置
.gitignore // git忽略的文件配置
.travis.yml // 該項目的CI環境配置文件
cargo.toml // 該項目的配置文件,類似package.json
README.md // 解釋文件
重點文件分析
- lib.rs,預設自帶的這個文件只實現了一個
greet
方法,並暴露給了瀏覽器端,裡面調用了alert
方法。use wasm_bindgen::prelude::*; // 引入wasm_bindgen::prelude下所有的類和方法 #[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; // 一般不需要用,可以選擇註釋 #[wasm_bindgen] extern { fn alert(s: &str); // 表示第三方環境可以直接用的方法定義,編譯的時候會直接去調用第三方環境中的同名方法 } #[wasm_bindgen] // 類似ts中的裝飾器,打上這個標簽,表示是需要與第三方環境去交互的,通俗一點,就是這個地方會被打包到第三方環境中。 pub fn greet() { alert("Hello, test-webassembly!"); }
- Cargo.toml,這個文件主要就是一些項目配置信息了。
[package] name = "test-webassembly" // 包名 version = "0.1.0" // 包版本 authors = ["xxx"] // 作者名 edition = "2018" // rust版本 [lib] // target設置,類似webpack種的target,將其打包成什麼樣的包 crate-type = ["cdylib", "rlib"] // 分別代表動態系統庫和rust靜態庫 [features] // 用來條件編譯 default = ["console_error_panic_hook"] // 這個可以刪掉不需要 [dependencies] wasm-bindgen = "0.2.63" // 這個就是主要的處理rust代碼轉化成webassembly的包 # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. # console_error_panic_hook = { version = "0.1.6", optional = true } 這個其實如果不是特別需要也可以不用 # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size # compared to the default allocator's ~10K. It is slower than the default # allocator, however. # wee_alloc = { version = "0.4.5", optional = true } 這個一般用不上,註釋掉就可以 [dev-dependencies] wasm-bindgen-test = "0.3.13" // 用來測試的包,自己寫一般可以不用 [profile.release] # Tell `rustc` to optimize for small code size. opt-level = "s"
改造
接下來實現一個簡單的圖片處理工具,幫助web端更快的處理圖片內容。
- 會用到
image
這個rust包。所以先改造它的Cargo.toml
。
[dependencies]
wasm-bindgen = "0.2.63"
// 在之前的dependencies下麵添加image包
image = "0.24.6"
- 就以處理圖片的其中內置的一個灰度方法來看一下。接下來改造
lib.rs
文件。
// 新增一個灰度方法,接受一個位元組類型的集合,返回一個新的位元組類型的集合
#[wasm_bindgen]
pub fn gray(_array: &mut [u8]) -> Vec<u8> {
let mut img = image::load_from_memory(_array).unwrap(); // 將傳入的u8集合轉化成一個image對象
img = img.grayscale(); // 調用第三方包的灰度方法得到新的圖片
let mut bytes: Vec<u8> = Vec::new(); // 定義一個新的u8集合
img.write_to(&mut Cursor::new(&mut bytes), image::ImageOutputFormat::Png).unwrap(); // 將變換後的image轉化成新的u8集合
bytes // 將u8集合作為返回值拋出
}
- 運行命令
wasm-pack build --release --target web
,將rust文件打包成目標為web項目的可用二進位文件以及載入的js文件。可以看到項目多了一個pkg
文件夾pkg
.gitignore
package.json
README.md
test_webassembly_bg.wasm // 二進位文件
test_webassembly_bg.wasm.d.ts
test_webassembly.d.ts
test_webassembly.js // 這個就是載入這個二進位的文件
// test_webassembly.js這個文件主要看幾個重點地方,其實不看也行,這個地方wasm-pack已經幫你處理好了,基本只需要學會用就行
// 載入二進位後的處理,返回暴露出的模塊的實例
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
// 在支持instantiateStreaming的情況下,優先使用instantiateStreaming載入對應的二進位文件
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
// 不支持的時候,就使用instantiate文件來載入
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
// 針對已經解析完的實例,做一些初始化操作,拋出模塊實例的exports
function finalizeInit(instance, module) {
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
cachedInt32Memory0 = null;
cachedUint8Memory0 = null;
return wasm;
}
// 文件暴露的入口
async function init(input) {
if (typeof input === 'undefined') {
input = new URL('test_webassembly_bg.wasm', import.meta.url); // 預設的二進位文件地址
}
const imports = getImports();
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input); // 載入對應的二進位文件
}
initMemory(imports); // 這個就是之前上面申請空間的,可以註釋掉的,不用看
const { instance, module } = await load(await input, imports); // 調用webassembly的api去載入二進位文件
return finalizeInit(instance, module); // 得到模塊中拋出的exports
}
在html中使用,新建一個index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="file" multiple="false" id="file">
<div id="box"></div>
<div>
<button id="gray-btn">灰度</button>
</div>
<div id="target"></div>
<script type="module">
import init, { gray } from './pkg/test_webassembly.js';
await init();
const file = document.getElementById('file');
const grayBtn = document.getElementById('gray-btn');
let current = null;
let uint8Array = [];
file.addEventListener('change', (e) => {
current = e.target.files[0];
var reader = new FileReader();
reader.readAsDataURL(current);
reader.onload = function() {
const img = new Image();
img.src = this.result;
document.querySelector('#box').appendChild(img);
}
});
grayBtn.addEventListener('click', () => {
var reader = new FileReader();
reader.readAsArrayBuffer(current);
reader.onload = function () {
uint8Array = new Uint8Array(this.result);
let newUnit8Array = gray(uint8Array); // 調用webassembly提供的灰度方法獲取到灰度之後的結果
const img = new Image();
let blob = new Blob([newUnit8Array], { type: 'image/png' });
img.src = window.URL.createObjectURL(blob);
document.querySelector('#target').appendChild(img);
}
})
</script>
</body>
</html>
使用npx http-server --port 3000
啟動靜態伺服器,因為webassembly是不支持本地的文件的,下麵就是最終效果,先選擇一張圖片,之後點擊灰度就能快速得到一張灰度的圖片
小結
WebAssembly
是一個很有意思的技術方向,它為web應用提供了很多可能,圖片處理,視頻解析、加密等等一些複雜的場景都可以變得更輕便化。