使用wasm-pack构建rust库到npm

74 阅读2分钟

wasm-pack 是一个便捷工具, 当使用rust编写Web Assembly并且支持浏览器(JS)调用的库的时候, 需要一系列的工序. 使用wasm-pack后步骤简化成:

编写 rust 库代码 → build → npm publish

具体详情不赘述, 查看: rustwasm.github.io/docs/wasm-p…

这个文章讨论落地实际项目会遇到的问题和解决方案:

问题1:

使用 scripts 标签载入:

运行build后, wasm-pack 会在项目生成 pkg 文件夹, 文件夹内容如下:

Untitled.png

这时候可以选择把文件推送到远程cdn, 然后通过 script 标签加载, 在 html 文件中:


<script type="module">

import init, { hello } from "https://some-cdn.com/xxx/my_lib.js";

await init();

hello();

</script>

然而如果需要在业务模块中调用, 这种方式就有缺陷, 一个简单的解决办法是, 存在global对象中, 供需要的时候调用:


<script type="module">

import init, { hello } from "https://some-cdn.com/xxx/my_lib.js";

await init();

window.hello = hello;

</script>

这种方式污染全局变量, 不能按需加载, 并且升级版本也不便.

问题2:

查看 pkg 文件夹中 package.json , 这个内容已经满足发布npm包的条件了:

Untitled 1.png

然而, 如果我们需要部署到私域 npm 仓库是不行的, 我们需要在 name 字段加入私域名前缀, 比如私域前缀是 @abc ;

相关 issue: github.com/rustwasm/wa…

在项目新建 build.sh文件


#!/bin/bash

# 运行wasm-pack build命令

wasm-pack build --target web --release

# 在生成的pkg/package.json文件的name字段之前添加"@abc"字符串

sed -i 's/"name": "\(.*\)"/"name": "@abc\1"/' pkg/package.json

如果需要支持windows 平台的, 需要创建 build.bat文件


wasm-pack build --target web --release

@REM 在生成的pkg/package.json文件的name字段之前添加"@abc"字符串

powershell -Command "(Get-Content pkg/package.json) -replace '\"name\": \"(.*?)\"', '\"name\": \"@abc$1\"' | Set-Content pkg/package.json"

这时候每次构建时, 运行这个脚本


sh ./build.sh

问题3:

加载 wasm 文件跨域问题:

Untitled 2.png

查看 wasm-pack 生成出来的文件:

Untitled 3.png

如此看来, 是通过new URL加载 my_lib_bg.wasm 文件, 如果主站域名和静态资源的域名不一致, 导致不满足同源策略.

解决办法:

  1. 通过配置 nginx 支持跨域:


location ~* \.(js|eot|ttf|woff2?|otf|svg|svga|json|png|mp3|jpg|wav|tpl|css|js|html|mp4|wasm|asm|bin)$ {

add_header Access-Control-Allow-Origin *;

add_header Access-Control-Allow-Methods GET,POST,OPTIONS;

root /data/webapps/xxx.com/;

concat on;

concat_max_files 20;

}

  1. 通过webpack配置把wasm打包到JS文件中:

查看 __wbg_init 的实现:

  1. 如果 init 调用是是空, 就会默认加载 my_lib_bg.wasm 文件, 所以导致出现这个问题

  2. 如果 input 是字符串, 通过 fetch 请求字符串

  3. 否则 input 被当作 promise 调用

所以给这个方案提供了一个这种可能, 具体使用方法参考: webassembly - How can I make webpack embed my *.wasm for use in a web worker? - Stack Overflow


import init, { /* other stuff */ } from 'my-wasm-package';

import wasmData from 'my-wasm-package/my-wasm-package.wasm';

const wasmPromise = init(wasmData);