前端开发笔记[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具有以下功能:

  1. 代码转换:可以将TypeScript、SCSS等代码转换为浏览器可识别的JavaScript、CSS等格式。
  2. 文件优化:可以压缩JavaScript、CSS、HTML等代码,合并、压缩图片等,以减小文件体积,提高加载速度。
  3. 代码分割:可以提取多个页面的公共代码,将其打包为单独的文件,实现代码复用和减少重复加载。
  4. 模块合并:在采用模块化开发的项目中,可以将多个模块和文件合并为一个文件,减少网络请求次数。
  5. 自动刷新:可以监听本地源代码的变化,自动重新构建并刷新浏览器,提高开发效率。
  6. 代码校验:可以在代码提交前进行代码规范的校验,确保代码质量。
  7. 自动发布:可以自动构建出线上发布的代码,并传输给发布系统,简化发布流程。

安装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

关键步骤和代码

  1. 搭建编译环境
  • 使用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
  1. 新建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));
}
  1. 新建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
          },
        ],
    },
};
  1. 编译&打包
# 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文件夹
  1. 生成的文件
.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的代码已经修正过了.

效果

posted @ 2023-11-05 19:28  qsBye  阅读(59)  评论(0编辑  收藏  举报