前端开发笔记[5]-rust的webassembly
摘要
基于rust开发webassembly入门,通过rust实现在网页中弹出警告框.
rust的webassembly开发方式
https://zhuanlan.zhihu.com/p/104299612 入门 Rust 开发 WebAssembly
Rust 编译为WebAssembly 在前端项目中使用 https://zhuanlan.zhihu.com/p/662991464
相对来说,使用 Rust 开发在开发效率和便捷性、包体积大小等方面还是有很大优势的,因此,笔者也建议使用 Rust 来作为 WebAssembly 的开发语言。
Rust+WebAssembly 的能力:
- 可以使用 Rust std,可以使用 Rust 的大多数第三方库.
- 可以调用几乎任何 JS 侧声明的方法,也可以暴露方法给 JS 调用.
- 可以和 JS 侧互相”传递“几乎任何的数据类型,包括但不限于基本的数字、字符串、对象、Dom对象等(使用wasm-bindgen).
- 可以直接在 Rust 侧“操作”Dom,甚至已经出现了 Rust 版本的 react.
wasm-bindgen简介
[https://rustwasm.wasmdev.cn/docs/wasm-bindgen/]
wasm-bindgen是一个用于促进WebAssembly模块和JavaScript之间高级交互的Rust库和CLI工具。它的主要功能是简化Rust和JavaScript之间的交互,使得在Rust中可以方便地调用JavaScript的API,并且可以将Rust代码编译成WebAssembly模块。
wasm-bindgen的作用是将一些元数据注入到编译后的WebAssembly模块中,然后使用一个独立的命令行工具读取这些元数据,生成一个适当的JavaScript包装器,其中包含开发人员想要绑定到Rust的函数、类和其他基本类型。
通过wasm-bindgen,可以实现更高级的交互,不仅限于整数和浮点数的传递,还可以通过字符串、JavaScript对象、类等进行通信。这使得在Rust中可以定义JavaScript类,从JavaScript获取字符串,或者返回JavaScript对象等。
总之,wasm-bindgen是一个强大的工具,可以简化Rust和JavaScript之间的交互,使得在WebAssembly中使用Rust变得更加方便和灵活。
webpack简介
[https://zhuanlan.zhihu.com/p/30981251?utm_id=0]
webpack是一个用于现代JavaScript应用程序的静态模块打包工具。它的主要功能是将项目中的各种静态资源(如JavaScript文件、CSS文件、图片等)视为模块,并通过一个开发时态的入口模块为起点,分析出所有的依赖关系,然后经过一系列的处理(如语法转换、资源压缩、模块合并等),最终生成运行时态的文件。
webpack具有以下功能:
- 代码转换:可以将TypeScript、SCSS等代码转换为浏览器可识别的JavaScript、CSS等格式。
- 文件优化:可以压缩JavaScript、CSS、HTML等代码,合并、压缩图片等,以减小文件体积,提高加载速度。
- 代码分割:可以提取多个页面的公共代码,将其打包为单独的文件,实现代码复用和减少重复加载。
- 模块合并:在采用模块化开发的项目中,可以将多个模块和文件合并为一个文件,减少网络请求次数。
- 自动刷新:可以监听本地源代码的变化,自动重新构建并刷新浏览器,提高开发效率。
- 代码校验:可以在代码提交前进行代码规范的校验,确保代码质量。
- 自动发布:可以自动构建出线上发布的代码,并传输给发布系统,简化发布流程。
安装webpack可以通过npm进行,它提供了两个包:webpack和webpack-cli。推荐使用本地安装方式,即在每个项目中使用自己的webpack版本进行构建。
实现
项目目录
.
├── Cargo.lock
├── Cargo.toml
├── dist
│ ├── f5c9d4a53ace9a9807f4.module.wasm
│ ├── index.html
│ ├── index.js
│ ├── pkg_hello_rust_wasm_js.index.js
│ └── vendors-node_modules_text-encoding_index_js.index.js
├── index.js
├── node_modules
│ ├── @discoveryjs
│ ├── @jridgewell
│ ├── @leichtgewicht
│ ├── @types
│ ├── @wasm-tool
│ ├── @webassemblyjs
│ ├── @webpack-cli
│ ├── @xtuc
│ ├── accepts
│ ├── acorn
│ ├── acorn-import-assertions
│ ├── ajv
│ ├── ajv-formats
│ ├── ajv-keywords
│ ├── ansi-html-community
│ ├── ansi-regex
│ ├── ansi-styles
│ ├── anymatch
│ ├── array-flatten
│ ├── balanced-match
│ ├── batch
│ ├── binary-extensions
│ ├── body-parser
│ ├── bonjour-service
│ ├── boolbase
│ ├── brace-expansion
│ ├── braces
│ ├── browserslist
│ ├── buffer-from
│ ├── bytes
│ ├── call-bind
│ ├── camel-case
│ ├── caniuse-lite
│ ├── chalk
│ ├── chokidar
│ ├── chrome-trace-event
│ ├── clean-css
│ ├── clone-deep
│ ├── color-convert
│ ├── color-name
│ ├── colorette
│ ├── command-exists
│ ├── commander
│ ├── compressible
│ ├── compression
│ ├── concat-map
│ ├── connect-history-api-fallback
│ ├── content-disposition
│ ├── content-type
│ ├── cookie
│ ├── cookie-signature
│ ├── core-util-is
│ ├── cross-spawn
│ ├── css-select
│ ├── css-what
│ ├── debug
│ ├── default-gateway
│ ├── define-data-property
│ ├── define-lazy-prop
│ ├── depd
│ ├── destroy
│ ├── detect-node
│ ├── dns-equal
│ ├── dns-packet
│ ├── dom-converter
│ ├── dom-serializer
│ ├── domelementtype
│ ├── domhandler
│ ├── domutils
│ ├── dot-case
│ ├── ee-first
│ ├── electron-to-chromium
│ ├── encodeurl
│ ├── enhanced-resolve
│ ├── entities
│ ├── envinfo
│ ├── es-module-lexer
│ ├── escalade
│ ├── escape-html
│ ├── escape-string-regexp
│ ├── eslint-scope
│ ├── esrecurse
│ ├── estraverse
│ ├── etag
│ ├── eventemitter3
│ ├── events
│ ├── execa
│ ├── express
│ ├── fast-deep-equal
│ ├── fast-json-stable-stringify
│ ├── fastest-levenshtein
│ ├── faye-websocket
│ ├── fill-range
│ ├── finalhandler
│ ├── find-up
│ ├── flat
│ ├── follow-redirects
│ ├── forwarded
│ ├── fresh
│ ├── fs-monkey
│ ├── fs.realpath
│ ├── function-bind
│ ├── get-intrinsic
│ ├── get-stream
│ ├── glob
│ ├── glob-parent
│ ├── glob-to-regexp
│ ├── gopd
│ ├── graceful-fs
│ ├── handle-thing
│ ├── has-flag
│ ├── has-property-descriptors
│ ├── has-proto
│ ├── has-symbols
│ ├── hasown
│ ├── he
│ ├── hpack.js
│ ├── html-entities
│ ├── html-minifier-terser
│ ├── html-webpack-plugin
│ ├── htmlparser2
│ ├── http-deceiver
│ ├── http-errors
│ ├── http-parser-js
│ ├── http-proxy
│ ├── http-proxy-middleware
│ ├── human-signals
│ ├── iconv-lite
│ ├── import-local
│ ├── inflight
│ ├── inherits
│ ├── interpret
│ ├── ipaddr.js
│ ├── is-binary-path
│ ├── is-core-module
│ ├── is-docker
│ ├── is-extglob
│ ├── is-glob
│ ├── is-number
│ ├── is-plain-obj
│ ├── is-plain-object
│ ├── is-stream
│ ├── is-wsl
│ ├── isarray
│ ├── isexe
│ ├── isobject
│ ├── jest-worker
│ ├── json-parse-even-better-errors
│ ├── json-schema-traverse
│ ├── kind-of
│ ├── launch-editor
│ ├── loader-runner
│ ├── locate-path
│ ├── lodash
│ ├── lower-case
│ ├── media-typer
│ ├── memfs
│ ├── merge-descriptors
│ ├── merge-stream
│ ├── methods
│ ├── micromatch
│ ├── mime
│ ├── mime-db
│ ├── mime-types
│ ├── mimic-fn
│ ├── minimalistic-assert
│ ├── minimatch
│ ├── ms
│ ├── multicast-dns
│ ├── negotiator
│ ├── neo-async
│ ├── no-case
│ ├── node-forge
│ ├── node-releases
│ ├── normalize-path
│ ├── npm-run-path
│ ├── nth-check
│ ├── object-inspect
│ ├── obuf
│ ├── on-finished
│ ├── on-headers
│ ├── once
│ ├── onetime
│ ├── open
│ ├── p-limit
│ ├── p-locate
│ ├── p-retry
│ ├── p-try
│ ├── param-case
│ ├── parseurl
│ ├── pascal-case
│ ├── path-exists
│ ├── path-is-absolute
│ ├── path-key
│ ├── path-parse
│ ├── path-to-regexp
│ ├── picocolors
│ ├── picomatch
│ ├── pkg-dir
│ ├── pretty-error
│ ├── process-nextick-args
│ ├── proxy-addr
│ ├── punycode
│ ├── qs
│ ├── randombytes
│ ├── range-parser
│ ├── raw-body
│ ├── readable-stream
│ ├── readdirp
│ ├── rechoir
│ ├── relateurl
│ ├── renderkid
│ ├── require-from-string
│ ├── requires-port
│ ├── resolve
│ ├── resolve-cwd
│ ├── resolve-from
│ ├── retry
│ ├── rimraf
│ ├── safe-buffer
│ ├── safer-buffer
│ ├── schema-utils
│ ├── select-hose
│ ├── selfsigned
│ ├── send
│ ├── serialize-javascript
│ ├── serve-index
│ ├── serve-static
│ ├── set-function-length
│ ├── setprototypeof
│ ├── shallow-clone
│ ├── shebang-command
│ ├── shebang-regex
│ ├── shell-quote
│ ├── side-channel
│ ├── signal-exit
│ ├── sockjs
│ ├── source-map
│ ├── source-map-support
│ ├── spdy
│ ├── spdy-transport
│ ├── statuses
│ ├── string_decoder
│ ├── strip-ansi
│ ├── strip-final-newline
│ ├── supports-color
│ ├── supports-preserve-symlinks-flag
│ ├── tapable
│ ├── terser
│ ├── terser-webpack-plugin
│ ├── text-encoding
│ ├── thunky
│ ├── to-regex-range
│ ├── toidentifier
│ ├── tslib
│ ├── type-is
│ ├── undici-types
│ ├── unpipe
│ ├── update-browserslist-db
│ ├── uri-js
│ ├── util-deprecate
│ ├── utila
│ ├── utils-merge
│ ├── uuid
│ ├── vary
│ ├── watchpack
│ ├── wbuf
│ ├── webpack
│ ├── webpack-cli
│ ├── webpack-dev-middleware
│ ├── webpack-dev-server
│ ├── webpack-merge
│ ├── webpack-sources
│ ├── websocket-driver
│ ├── websocket-extensions
│ ├── which
│ ├── wildcard
│ ├── wrappy
│ └── ws
├── package-lock.json
├── package.json
├── pkg
│ ├── hello_rust_wasm.d.ts
│ ├── hello_rust_wasm.js
│ ├── hello_rust_wasm_bg.js
│ ├── hello_rust_wasm_bg.wasm
│ ├── hello_rust_wasm_bg.wasm.d.ts
│ ├── index.d.ts
│ ├── index.js
│ ├── index_bg.js
│ ├── index_bg.wasm
│ ├── index_bg.wasm.d.ts
│ └── package.json
├── src
│ └── lib.rs
├── target
│ ├── CACHEDIR.TAG
│ ├── debug
│ └── wasm32-unknown-unknown
├── vendor
│ ├── bumpalo
│ ├── cfg-if
│ ├── js-sys
│ ├── log
│ ├── once_cell
│ ├── proc-macro2
│ ├── quote
│ ├── syn
│ ├── unicode-ident
│ ├── wasm-bindgen
│ ├── wasm-bindgen-backend
│ ├── wasm-bindgen-macro
│ ├── wasm-bindgen-macro-support
│ ├── wasm-bindgen-shared
│ └── web-sys
└── webpack.config.js
关键步骤和代码
- 搭建编译环境
- 使用docker镜像(arm64)
docker run -it -v "/Users/workspace/Desktop/projects/糖画机/software/exp14":/srv qsbye/build-env6:v0.2-arm64 bash
- 或者自建环境
curl https://Rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
cargo install wasm-bindgen-cli
cargo install cargo-offline --features=cargo-metadata
cargo install cargo-binstall
rustup target add wasm32-unknown-unknown
apt install -y nodejs
apt install -y npm --fix-missing
- 新建rust工程
cargo new hello_rust_wasm --lib
cd ./hello_rust_wasm
cargo add wasm-bindgen
cargo add web-sys
Cargo.toml
[package]
name = 'hello_rust_wasm'
edition = '2021'
version = '0.1.0'
[package.metadata]
last-modified-system-time = 1699179801
[dependencies]
wasm-bindgen = '0.2.88'
web-sys = '0.3.65'
[lib]
crate-type = ['cdylib']
required-features = []
src/lib.rs
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
// 导入 'window.alert'
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
// 导出一个 'helloworld' 函数
#[wasm_bindgen]
pub fn helloworld(name: &str) {
alert(&format!("Hello World : {}!", name));
}
- 新建npm工程
npm init
npm install webpack --save-dev
npm install webpack-cli --save-dev
npm install webpack-dev-server --save-dev
npm install html-webpack-plugin --save-dev
npm install @wasm-tool/wasm-pack-plugin --save-dev
npm install text-encoding --save-dev
index.js
// 直接引入了,刚才编译后的文件
const rust = import('./pkg/hello_rust_wasm.js');
rust
.then(m => m.helloworld('World!'))
.catch(console.error);
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
},
plugins: [
new HtmlWebpackPlugin(),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, ".")
}),
// 让这个示例在不包含`TextEncoder`或`TextDecoder`的Edge浏览器中正常工作。
new webpack.ProvidePlugin({
TextDecoder: ['text-encoding', 'TextDecoder'],
TextEncoder: ['text-encoding', 'TextEncoder']
})
],
mode: 'development',
experiments: {
asyncWebAssembly: true, // 启用异步WebAssembly实验功能
},
module: {
rules: [
{
test: /\.wasm$/,
type: 'webassembly/async', // 设置模块类型为webassembly/async
},
],
},
};
- 编译&打包
# cargo-offline是cargo命令的离线版本,仅第一次编译需要联网
cargo-offline build --target wasm32-unknown-unknown
# 保存所有rust工程依赖源码到工程目录的vendor文件夹,类似node_modules文件夹
cargo vendor --sync ./Cargo.toml
# 生成pkg文件夹(包含js文件)
wasm-bindgen target/wasm32-unknown-unknown/debug/hello_rust_wasm.wasm --out-dir ./pkg
# 使用webpack打包
npm run build
# 生成的文件在dist文件夹
- 生成的文件
.dist
├── f5c9d4a53ace9a9807f4.module.wasm
├── index.html
├── index.js
├── pkg_hello_rust_wasm_js.index.js
└── vendors-node_modules_text-encoding_index_js.index.js
小插曲:docker镜像中的webpack版本太旧不支持webassembly打包,宿主机的webpack版本太新,打包webassembly为实验性功能,需要手动开启,输出如下:
ERROR in ./pkg/hello_rust_wasm_bg.wasm 1:0
Module parse failed: Unexpected character '' (1:0)
The module seem to be a WebAssembly module, but module is not flagged as WebAssembly module for webpack.
BREAKING CHANGE: Since webpack 5 WebAssembly is not enabled by default and flagged as experimental feature.
You need to enable one of the WebAssembly experiments via 'experiments.asyncWebAssembly: true' (based on async modules) or 'experiments.syncWebAssembly: true' (like webpack 4, deprecated).
For files that transpile to WebAssembly, make sure to set the module type in the 'module.rules' section of the config (e. g. 'type: "webassembly/async"').
不过以上webpack.config.js的代码已经修正过了.