我们都知道,Rust 中有一个叫napi的项目,用于实现Rust 开发 nodejs 平台上的 npm 库。napi 本质上是 nodejs 的C-API FFI 的绑定。但是napi开发的库不能直接在纯浏览器环境中运行,因为 napi是 Node.js 的原生插件 API,依赖于 Node.js 的运行时环境。浏览器并没有 Node.js 的运行时。
对于纯浏览器环境,我们需要借助 WASM 来实现纯浏览器可用的功能。
幸运的是,Rust 在 WASM 领域也是一个热门领域,有比较成熟的 WASM 生态。我们可以使用wasm-pack 来简化我们的开发体验。
安装与使用
我们可以通过下面四种方式中的任意一种方式进行安装:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh # 方式1
cargo install wasm-pack # 方式2
npm install -g wasm-pack # 方式3
pnpm add -g wasm-pack # 方式4
我们以实现 png 图片转换成 jpeg 的功能为例子。创建项目:
wasm-pack new imagejs
打开项目,默认lib.rs 已经有了一些代码示例。
wasm-bindgen
#[wasm_bindgen] 属性表示它下面的函数可以在 JavaScript 和 Rust 中访问。
示例中有一段代码:
#[wasm_bindgen]
extern {
fn alert(s: &str);
}
上面的extern 块将外部 JavaScript 函数 alert 导入到 Rust 中。从 Rust 调用 alert 需要此声明。通过以这种方式声明它,wasm-bindgen 将为 alert 创建 JavaScript 存根,允许我们在 Rust 和 JavaScript 之间来回传递字符串。
这样我们就可以直接在 Rust 代码里调用这个 javascript 里的 alert 函数。当然我们调用 alert 的函数也必须声明#[wasm_bindgen] 属性,否则无法互相访问。
针对 png 图片转换成 jpeg 这个功能,我们的函数签名可以是这样的:
#[wasm_bindgen]
pub fn png_to_jpg(buf: &[u8]) -> Result<Vec<u8>, JsError> {
todo!()
}
函数接收一个 png 的字节组,转换后并返回 jpeg 的字节组。
为了实现图片格式转换,我们可以使用image 这个库:
cargo add image
需要注意的是,由于我们的运行环境是 wasm ,在 rust 里,并不是所有的 crate 都支持 wasm 环境的,使用一些 crate 前我们需要仔细查看是否支持 wasm。
image的使用比较简单:
#[wasm_bindgen]
pub fn png_to_jpg(buf: &[u8]) -> Result<Vec<u8>, JsError> {
let format = image::ImageFormat::Png;
let img = image::load_from_memory_with_format(buf, format)?;
let mut bytes: Vec<u8> = Vec::new();
img.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Jpeg)?;
Ok(bytes)
}
我们运行下面的命令:
wasm-pack build --target web
就能生成符合浏览器可用的 npm 标准包。生成内容如下:
pkg
├── imagejs_bg.wasm
├── imagejs_bg.wasm.d.ts
├── imagejs.d.ts
├── imagejs.js
├── package.json
└── README.md
前端的验证
我们创建一个标准 vue 项目来验证一下:
pnpm create vue@latest
这里需要注意2点:
1. 引入本地包
{
"dependencies": {
"imagejs": "file:../pkg",
"vue": "^3.5.13"
}
}
file 后面跟上路径。添加后需要运行一次 pnpm install。
2. vite 需要知道如何处理 wasm
使用 vite-plugin-wasm:
pnpm add -g vite-plugin-wasm
然后在vite.config.ts 配置:
import wasm from "vite-plugin-wasm";
export default defineConfig({
plugins: [
wasm(),
vue(),
vueDevTools(),
],
optimizeDeps:{
exclude: ['imagejs']
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})
功能验证
使用前必须初始化:
import init, {png_to_jpg} from 'imagejs'
onMounted(async () => {
await init()
})
创建一个表单,用于上传 png 图片,然后转换成jpg,并显示到浏览器页面上。
<template>
<div>
<h1>convert png to jpeg</h1>
<div>
<input type="file" @change="changeHandle">
<img :src="showUrl" alt=""></img>
</div>
</div>
</template>
实现逻辑如下:
// 用于存储并展示转换后的图片URL的响应式变量
const showUrl = ref('')
async function changeHandle(e: Event) {
// 将事件目标转换为文件输入元素(假设是input[type="file"]元素)
let target: HTMLInputElement = e.target as HTMLInputElement
// 检查是否有选择的文件
if (target.files) {
// 获取用户选择的第一个文件
let file = target.files[0]
// 将文件读取为ArrayBuffer(二进制数据)
let buf = await file.arrayBuffer()
// 将PNG文件转换为JPEG格式的二进制数据(假设png_to_jpg是自定义转换函数)
let buf2 = png_to_jpg(new Uint8Array(buf))
// 将处理后的二进制数据转换为可展示的URL:
// 1. 创建包含JPEG数据的Blob对象(指定MIME类型为image/jpeg)
// 2. 生成可用于<img>标签的Blob URL
let url = URL.createObjectURL(new Blob([buf2], { type: 'image/jpeg' }))
// 更新响应式变量,触发界面更新
showUrl.value = url
}
}
发布到 npm 官方注册表
我们可以使用 wasm-pack publish 来发布我们的 npm 包,前提是我们需要有对应的 npm 帐户以及登录:
wasm-pack login