摘要
本文介绍了在windows环境下编译jieba分词WASM版本的完整过程
前言
我开发了一个浏览器插件,其中使用了jieba分词的WASM版本。但是在上架火狐的时候碰到了点小麻烦,火狐浏览器对于要上架的扩展,必须提供所有代码(包括打包成了wasm的代码),并要提供打包编译了的代码的打包流程[1]。而我用了WASM方案的jieba分词的,所以,我便尝试本地从源码构建并打包一个Jieba Wasm,我将我编译打包的所有过程,以及踩的坑全部记录在这里,希望对大家有所帮助。
相关知识
在开始编译打包之前,我们最好了解一下相关的知识
什么是jieba分词
Jieba分词(项目地址:https://github.com/fxsjy/jieba)是一个开源的基于Python的分词程序,能将输入的句子进行切割,切割成一个一个词组。比如:
欢迎访问我的网站
切割为
欢迎 / 访问 / 我 / 的 / 网站
什么是WASM
WASM(相关文档https://webassembly.github.io/spec/),全称WebAssembly技术,是一个可以运行在浏览器中的二进制格式,相比起传统的在浏览器端执行的js,WASM执行速度更快,性能更好。我们可以将不同的语言的程序(比如说C、Rust、Go等)编译为WASM并且提供给浏览器,让这些代码能够在网页上进行高性能执行。
关于RUST
RUST是由Mozilla开发的 [2] ,一个专注于内存安全的语言(官网https://rust-lang.org/),致力于高性能与尽量保证内存安全。我列了一张表(表1)给大家对比它与传统语言的区别。
| 运行速度 | 内存安全 | |
| C++/C | 速度快 | 容易因为程序员的疏忽产生内存安全问题 |
| Python | 速度偏慢 | 有自动内存垃圾回收,内存安全问题出现较少 |
| Rust | 速度快 | 在编译时检查代码中的内存访问,尽量减少编译出来的程序出现内存安全问题 |
编译实现思路
要将Jieba分词打包为适用于Web的Wasm版本,整体大概的流程是:先将Jieba转化为Rust语言版本(项目地址https://github.com/messense/jieba-rs),再将程序打包从Rust程序转化为通用的Wasm版本,最后转化为适配于Web平台的Wasm版本(项目地址https://github.com/fengkx/jieba-wasm)。
这里说一下我的猜测,为什么项目作者不用Python版本的原版Jieba分词直接打包成Wasm,而是使用Rust版本打包成Wasm。诚然,Python是可以打包成Wasm,但是受限于API限制,很多库是无法进行使用的,而且打包需要将整个解释器全部塞进去,包体积很大,且在浏览器端启动慢。而Rust打包成Wasm启动速度快,且体积小。所以项目作者使用Rust版本的Jieba分词。
基础环境准备
我们先要开始安装下基础的环境
rust环境
前往rust官网的下载地址,链接:https://rust-lang.org/tools/install/。官网下载界面如图1所示,其中红色方框圈起来的就是下载按钮。点击下载。

下载完后,打开下载的安装程序,会显示如图2所示的黑色窗口。因为Rust需要链接器以及相关的API库,所以Rust安装程序会询问,是否安装Visual Studio Community。这里输入1后回车进行安装。

输入1后稍等一会,会弹出Visual Studio Installer,如图3所示,点击继续即可。

当Visual Studio安装完后,回到Rust安装程序的黑窗口对话框,如图4。这时候Rust安装器会问你是否是标准安装,还是自定义安装。为了避免后续编译出现一些莫名其妙的错误,我这里推荐标准安装(直接按回车就OK了)。

安装过程,Rust会下载大量东西,等安装完成,Rust安装程序会显示“stable-x86_64-pc-windows-msvc installed”,如图5。

安装完成后,我们新开一个终端(请注意,不要使用安装之前的终端,因为安装的时候Rust安装程序修改了环境变量,安装之前打开的终端并没有刷新环境变量,会导致后续步骤出错),输入下面的命令,测试是否正确安装,输入后如果安装正确的话,应该会输出rustc以及cargo[3]的版本号,如图6所示
rustc --version
cargo --version

NodeJs环境
编译过程还需要Node环境,首先前往NodeJs的官网的下载界面(https://nodejs.org/en/download),点击 Windows Installer,下载Msi包(下载按钮我在图7中用红方框圈出来了)

下载好后,是一个msi文件,双击打开,并开始安装。安装过程如图8、图9、图10。

一路点击Next,直到如图9 有Install按钮,点击Install进行安装

安装完成后,界面应如图10所示

安装完成后,仍然是新开一个对话框(不要用安装之前打开的对话框,原因同上),输入node,看下终端是否有输出”Welcome to Node.js ….”,如图11所示。如果有,代表NodeJs成功安装。

CLang 安装
虽然前面安装了Msvc,但是由于编译过程中某一个组件依赖了Clang,如果系统中没有Clang,后续编译会报错(这个报错我会在后面编译过程中提到)。所以我们还是需要安装下Clang。
Clang有多种方案安装,可以通过Visual Studio进行安装,也可以通过官网安装,还可以通过windows上面的包管理器进行安装。我这边使用的是官网安装的方式。
我们前往llvm的官网发布页面[4],官网下载页面的地址是:https://releases.llvm.org,在Download栏目中,选择一个较新的版本,点击download,如图12所示。

点击Download后,会跳转到github的Release界面。找到windows installer 选项,点击这个选项对应的链接,下载安装器(installer下载按钮所处的地方我已经在图13中用红色方框框出来了)

打开下载好的LLVM安装程序,安装程序启动如图14所示,一路点击下一步。

当安装程序提示,是否将LLVM添加到环境变量(界面如图15所示),强烈建议你将LLVM添加到环境变量之中(如果你不想后面手动添加修改环境变量的话),选择”Add LLVM to the system Path for all users”(为所有用户添加LLVM环境变量)即可,选中后点击下一步。

安装完后,仍然是新开一个窗口,验证下Clang是否正确安装,验证命令在下方给出了。输入后若安装正确,屏幕应该输出“clang version….”等输出,如图16所示。
clang --version

编译过程
下载编译所需要的代码
前往github,将下面两个项目代码全部下载下来
jieba-rs:https://github.com/messense/jieba-rs (该项目将jieba改造成rust版本)
jieba-wasm:https://github.com/fengkx/jieba-wasm(该项目将jieba打包成wasm)
安装编译需要的组件 wasm-bindgen-cli
wasm-bindgen-cli是用于对编译出来的wasm进行处理的一个工具,生成方便js与ts调用的相关代码。下载命令如下。
cargo install wasm-bindgen-cli --locked
如果运行过程报link.exe not found,详细报错输出跟下面类似的话,这是没有使用develper powershell导致的。
error: linker `link.exe` not found
|
= note: program not found
note: the msvc targets depend on the msvc linker but `link.exe` was not found
note: please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option.
note: VS Code is a different product, and is not sufficient.
安装Visual Studio的时候,默认不会将link.exe等程序写入到系统的环境变量中,但是Visual Studio安装后,会附带有一个叫做developer powershell的工具,这个工具将会在启动的时候自动配置好msvc相关编译器与相关依赖组件的环境变量。[5]。我们在开始菜单中 搜索Developer Powershell (其实使用Developer Command Prompte命令提示符也是可以的,但下文中都是以Powershell为例子),打开即可,在菜单中搜索developer Powershell的结果如图17所示。下文中的操作,请都在developer powershell中执行。

打开Developer Powershell后,应打开了一个跟图18类似的powershell窗口(终端开头应会输出”Developer PowerShell的字样)。此时我们输入一下link.exe ,看当前环境中link.exe是否配置正确,如正确配置,输出应与图18中截图的输出类似。

此时重新运行cargo install wasm-bindgen-cli --locked
如果出现报错cannot open input file 'kernel32.lib(详细报错信息如下),这是因为Rust安装Visual Studio的时候默认没有安装windows 11/10的SDK,所以无法正常工作。
error: could not compile `serde` (build script) due to 1 previous error
error: could not compile `zerocopy` (build script) due to 1 previous error
error: linking with `link.exe` failed: exit code: 1181
|
= note: "link.exe" "/NOLOGO" "C:\\Users\\q2019715\\AppData\\Local\\Temp\\rustc7VqXvn\\symbols.o" "<2 object files omitted>" "<sysroot>\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib/{libstd-*,libpanic_unwind-*,libcfg_if-*,libwindows_targets-*,librustc_demangle-*,libstd_detect-*,libhashbrown-*,librustc_std_workspace_alloc-*,libunwind-*,librustc_std_workspace_core-*,liballoc-*,libcore-*,libcompiler_builtins-*}.rlib" "kernel32.lib" "kernel32.lib" "kernel32.lib" "ntdll.lib" "userenv.lib" "ws2_32.lib" "dbghelp.lib" "/defaultlib:msvcrt" "/NXCOMPAT" "/OUT:C:\\Users\\q2019715\\AppData\\Local\\Temp\\cargo-installN6DhWV\\release\\build\\icu_normalizer_data-0e4ce4f2ac102e3c\\build_script_build-0e4ce4f2ac102e3c.exe" "/OPT:REF,NOICF" "/DEBUG" "/PDBALTPATH:%_PDB%" "/NATVIS:<sysroot>\\lib\\rustlib\\etc\\intrinsic.natvis" "/NATVIS:<sysroot>\\lib\\rustlib\\etc\\liballoc.natvis" "/NATVIS:<sysroot>\\lib\\rustlib\\etc\\libcore.natvis" "/NATVIS:<sysroot>\\lib\\rustlib\\etc\\libstd.natvis"
= note: some arguments are omitted. use `--verbose` to show all linker arguments
= note: LINK : fatal error LNK1181: cannot open input file 'kernel32.lib'␍
解决方案也比较简单,在开始菜单中找到Visual Studio Installer这个软件(这个软件在刚刚安装Visual Studio的时候会自动安装),如图19,点击修改按钮。

然后在单个组件选项卡中找到Windows 11 SDK(如果是Windows10或者其他版本的Windows的话,勾选上对应系统版本的SDK),如图20所示。

点击修改,稍等片刻完成安装后,重新执行失败的安装命令cargo install wasm-bindgen-cli --locked。这次应该能够成功安装完这个组件,安装成功的截图如图21所示。

我们也测试下是否正确安装,测试命令如下,输入之后,应该有如图22一样的输出。
wasm-bindgen --version

安装编译所需要的组件 wasm-opt
wasm-opt是一个用于优化生成的wasm文件的程序,这也是我们编译jieba wasm版本中需要安装的组件。
正常情况下安装命令如下。(github项目 jieba-wasm的readme中也是说这样安装的)
cargo install wasm-opt --locked
但是如果我们执行这个安装命令的话,会出现如图23的报错

详细的报错内容如下:
error occurred: Command "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.44.35207\\bin\\HostX64\\x64\\cl.exe" "-nologo" "-MD" "-O2" "-Brepro" "-I" "C:\\Users\\q2019715\\AppData\\Local\\Temp\\cargo-installfcDy0e\\release\\build\\wasm-opt-sys-7ebe7db34fa31d38\\out\\cxxbridge\\include" "-I" "C:\\Users\\q2019715\\AppData\\Local\\Temp\\cargo-installfcDy0e\\release\\build\\wasm-opt-sys-7ebe7db34fa31d38\\out\\cxxbridge\\crate" "-I" "C:\\Users\\q2019715\\.cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\wasm-opt-sys-0.116.0\\binaryen\\src" "-I" "C:\\Users\\q2019715\\.cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\wasm-opt-sys-0.116.0\\binaryen\\src\\tools" "-I" "C:\\Users\\q2019715\\AppData\\Local\\Temp\\cargo-installfcDy0e\\release\\build\\wasm-opt-sys-7ebe7db34fa31d38\\out" "-I" "C:\\Users\\q2019715\\.cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\wasm-opt-sys-0.116.0\\binaryen\\third_party/llvm-project\\include" "-W4" "/std:c++17" "/w" "/DTHROW_ON_FATAL" "/DBUILD_LLVM_DWARF" "/DNDEBUG" "-FoC:\\Users\\q2019715\\AppData\\Local\\Temp\\cargo-installfcDy0e\\release\\build\\wasm-opt-sys-7ebe7db34fa31d38\\out\\4e4d2e940d1d64d9-fuzzing.o" "-c" "C:\\Users\\q2019715\\.cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\wasm-opt-sys-0.116.0\\binaryen\\src\\tools/fuzzing\\fuzzing.cpp" with args "cl.exe" did not execute successfully (status code exit code: 2).
error: failed to compile `wasm-opt v0.116.1`, intermediate artifacts can be found at `C:\Users\q2019715\AppData\Local\Temp\cargo-installfcDy0e`.
To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path.
cargo install wasm-opt --locked -vv 2>&1 | Tee-Object .\wasm-opt-build.log
然后我找了半天的编译日志,发现真正导致编译报错的原因在此(编译过程中有大量下面的报错):
2019:[wasm-opt-sys 0.116.0] C:\Users\q2019715\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wasm-opt-sys-0.116.0\binaryen\src\literal.h(739): error C2039: 'isBasic': is not a member of 'wasm::Type'
12022:[wasm-opt-sys 0.116.0] C:\Users\q2019715\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wasm-opt-sys-0.116.0\binaryen\src\literal.h(739): error C2039: 'isBasic': is not a member of 'wasm::Type'
这个是当前这个版本在windows上打包有点问题,要解决这个问题最快的解决方案是直接找一个打包好了的wasm-opt配置到电脑上面。(当然,硬要从源码上解决这个问题也不是不行,但是会非常非常麻烦,此方案本文不做过多介绍)
前往wasm-opt项目的发布地址(https://github.com/WebAssembly/binaryen/releases),找到一个带x86_64-windows版本的(注意,有的版本不带x86_64 windows,请不要下错了,尤其是不要下错成Windows Arm版本-除非你是Arm的windows),如图24所示, 红色框框圈出的是就是x86_64 windows版本。下载下来。

下载下来后,在一个适当的地方解压,解压出来的文件如图25所示。其中有一个文件夹名称叫做bin

然后前往计算机的环境变量设置,在用户变量处选择Path,选中后点击点击编辑,在弹出的 编辑环境变量窗口中,点击新建按钮,新建一行,新建内容的值是我们刚刚解压的wasm-opt文件夹下面的bin目录的地址,如图26所示。

然后添加完后,请一定要记得点击确定按钮。保存完后,在新的Developer Powershell中输入下面命令检查wasm-opt是否正确安装,如若正确安装,输出应与图27一致。
wasm-opt --version

配置npm依赖
在下载的jieba-wasm的项目路径,执行下方的命令,安装npm依赖
npm install
如果不执行这个命令的话,开始编译会报“'wireit' 不是内部或外部命令,也不是可运行的程序等错误。
或批处理文件。”
安装时候如若输出下面类型的提示,不必担心。其实依赖是已经安装成功了,这个是npm提示依赖链条有的依赖可能有安全问题,需要注意,但是我们这次是编译wasm的,可以暂且忽略这个问题。
2 vulnerabilities (1 moderate, 1 high)
To address all issues, run:
npm audit fix
Run `npm audit` for details.
开始编译-生成wasm本体
现在这一步就是生成wasm本体,也就是通用的wasm模块。执行下述命令开始编译
npm run build:cargo
如果编译时候报下面的错误
PS F:\jieba-wasm-master> npm run build:cargo
> jieba-wasm@2.4.0 build:cargo
> wireit
Updating crates.io index
error: failed to get `jieba-rs` as a dependency of package `jieba-rs-wasm v1.0.0 (F:\jieba-wasm-master)`
Caused by:
failed to load source for dependency `jieba-rs`
Caused by:
Unable to update F:\jieba-wasm-master\jieba-rs
Caused by:
failed to read `F:\jieba-wasm-master\jieba-rs\Cargo.toml`
Caused by:
系统找不到指定的文件。 (os error 2)
❌ [build:cargo] exited with exit code 101.
❌ 1 script failed.
这个是因为 下载jieba-wasm项目文件的时候没有下载jieba-rs文件夹下的东西。即rust版本的jieba并没有放到jieba-wasm的编译路径中。我们前往https://github.com/messense/jieba-rs 下载相关文件后,将文件放到jieba-wasm项目中的jieba-rs这个目录中,如图28所示。

备注:如果是使用git进行拉取的话,git clone --recurse-submodules https://github.com/fengkx/jieba-wasm 也可以达到同样的效果。
再次进行编译,如果报下面的错误
Compiling foldhash v0.2.0
Compiling zmij v1.0.19
Compiling equivalent v1.0.2
error[E0463]: can't find crate for `core`
|
= note: the `wasm32-unknown-unknown` target may not be installed
= help: consider downloading the target with `rustup target add wasm32-unknown-unknown`
For more information about this error, try `rustc --explain E0463`.
error: could not compile `memchr` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...
error: could not compile `cfg-if` (lib) due to 1 previous error
❌ [build:cargo] exited with exit code 101.
❌ 1 script failed.
这个是因为现在rust要编译wasm32-unknown-unknown这个目标的结果,但是当前本地计算机没有安装这个目标对应的标准库与依赖,所以报错了。解决方案很简单,输入下面的命令,加载相关的库与依赖。指令执行成功后应该有如图29所示的结果。
rustup target add wasm32-unknown-unknown

这个执行完后,再次执行npm run build:cargo ,如果报了下面的错误,那就是你Clang没有安装(前文已经提到了需要安装了Clang,请依照前文的需求,再次安装)。jieba-wasm中的项目依赖中有部分强行依赖了clang生态
cargo:rerun-if-env-changed=CFLAGS_wasm32-unknown-unknown
CFLAGS_wasm32-unknown-unknown = None
--- stderr
error occurred in cc-rs: failed to find tool "clang": program not found (see https://docs.rs/cc/latest/cc/#compile-time-requirements for help)
warning: build failed, waiting for other jobs to finish...
❌ [build:cargo] exited with exit code 101.
❌ 1 script failed.
安装完Clang后,输入下方指令清空下编译缓存,避免出现缓存污染的情况。
cargo clean
然后再次执行npm run build:cargo,这次应该能编译成功了,成功编译的截图如图30所示。

编译第二步-生成各平台绑定代码
生成了wasm本体之后,编译是还没有完成。先前编译的wasm只是一个基础的内核,还不能直接给不同的js环境使用(当然,如果你乐意,可以自己手搓一个兼容层来进行使用),还需要进行各个平台的绑定,否则无法正确的运行。[6]
执行下述命令进行平台绑定,执行完成后截图如图31所示
npm run build

执行成功后,可以在jieba-wasm的项目目录的\pkg\web文件夹 找到适配于web的jieba_rs_wasm_bg.wasm。\pkg\nodejs下面能找到适配nodejs的,还有各种其他环境的,如图32所示。这些编译成果文件我们就可以拿去调用了。至此,编译完成。

简易使用测试
编译完成后,我们可以测试下编译成果是否能够正常使用,执行下面的命令启动一个简单的测试环境
cd demo/web
npm install
npm run dev
运行完成后,程序会监听本地的3000端口,可以前往http://localhost:3000/ 进行访问测试,如图33所示。我们可以输入一段中文句子进行分词,点击分词按钮,网页会输出结果。输出结果,即代表编译成功。

相关环境的版本
系统:Windows11家庭中文版 64位
Rust版本: 1.93.0 x86_64-pc-windows-msvc
Node版本:v24.13.0
LLVM版本:21.1.0
Visual Studio版本:2022
wasm-opt版本:124
wasm-bindgen-cli:0.2.108
使用的jieba-rs github提交Hash:7f3b2737
使用的jieba-Wasm github提交 Hash:56ddcb89