WebAssembly (Wasm) 是一种新的低级语言,其设计考虑了 Web。 它的主要目标是使开发人员能够将用其他语言(例如 C、C++ 和 Rust)编写的代码编译成 WebAssembly,并在浏览器中运行该代码。 在传统上 JavaScript 是唯一选择的环境中,WebAssembly 是一种有吸引力的替代方案,它实现了可移植性,并承诺接近本机运行时。 WebAssembly 也已被用于将大量工具移植到 Web 上,包括 桌面应用程序,游戏,甚至 用 Python 编写的数据科学工具!
WebAssembly 的另一个应用是命令行 Playground,用户可以自由地玩模拟版本的命令行工具。在本文中,我们将探讨利用 WebAssembly 实现此目的的具体示例,特别是将工具 jq(通常仅限于命令行)移植到直接在浏览器中运行。
如果您还没有听说过,jq 是一种非常强大的命令行工具,用于在命令行上查询、修改和处理 JSON 对象。
为什么选择 WebAssembly?
除了 WebAssembly,我们还可以采用另外两种方法来构建 jq Playground
- 在您的服务器上设置一个沙盒环境,该环境执行查询并通过 API 调用将结果返回给用户。 尽管这意味着您的用户可以玩真实的东西,但托管、保护和清理此类应用程序的用户输入的想法令人担忧。 除了安全性之外,另一个问题是响应速度; 与服务器的额外往返可能会导致明显的延迟,并对用户体验产生负面影响。
- 使用 JavaScript 模拟命令行环境,您可以在其中定义用户可以执行的一系列步骤。 尽管此方法比选项 1 更安全,但它涉及更多的工作,因为您需要在 JavaScript 中重写工具的逻辑。 这种方法也有限制: 当我学习新工具时,我不仅仅对“快乐路径”感兴趣; 我想破坏东西!
这两种解决方案都不理想,因为我们必须在安全性和有意义的学习体验之间做出选择。 理想情况下,我们可以直接在浏览器中运行命令行工具,无需服务器,也无需模拟。 幸运的是,WebAssembly 正是我们实现这一目标所需的解决方案。
设置您的环境
在本文中,我们将使用 Emscripten 工具 将 jq 从 C 移植到 WebAssembly。 方便的是,它为我们提供了最常见的 C/C++ 构建工具的直接替代品,包括 gcc、make 和 configure。
与其 从头开始安装 Emscripten(构建过程可能需要很长时间),不如使用我整理的 Docker 镜像,该镜像预先打包了您为本文(以及更多!)所需的一切。
让我们首先拉取镜像并从中创建一个容器
# Fetch docker image containing Emscripten
docker pull robertaboukhalil/emsdk:1.38.26
# Create container from that image
docker run -dt --name wasm robertaboukhalil/emsdk:1.38.26
# Enter the container
docker exec -it wasm bash
# Make sure we can run emcc, Emscripten's wrapper around gcc
emcc --version
如果您在屏幕上看到 Emscripten 版本,那就一切顺利!
将 jq 移植到 WebAssembly
接下来,让我们克隆 jq 存储库
git clone https://github.com/stedolan/jq.git
cd jq
git checkout 9fa2e51
请注意,我们正在检出一个特定的提交,以防在本文发布后 jq 代码发生重大更改。
在我们编译 jq 到 WebAssembly 之前,让我们首先考虑一下我们通常如何编译 jq 以供在命令行上使用的二进制文件。
从 README 文件 中,这是我们将 jq 构建为二进制文件所需的内容(请勿立即输入)
# Fetch jq dependencies
git submodule update --init
# Generate ./configure file
autoreconf -fi
# Run ./configure
./configure \
--with-oniguruma=builtin \
--disable-maintainer-mode
# Build jq executable
make LDFLAGS=-all-static
相反,要将 jq 编译为 WebAssembly,我们将利用 Emscripten 的 configure 和 make 构建工具的直接替代品(请注意此处与先前条目的差异:运行和构建语句中分别为 emconfigure 和 emmake)
# Fetch jq dependencies
git submodule update --init
# Generate ./configure file
autoreconf -fi
# Run ./configure
emconfigure ./configure \
--with-oniguruma=builtin \
--disable-maintainer-mode
# Build jq executable
emmake make LDFLAGS=-all-static
如果您在之前创建的 Wasm 容器中键入以上命令,您会注意到 emconfigure 和 emmake 将确保使用 emcc 而不是 gcc 编译 jq(Emscripten 也有一个名为 em++ 的 g++ 替代品)。
到目前为止,这出奇地容易:我们只需在一些命令前加上 Emscripten 工具,并将一个包含数万行代码的代码库从 C 移植到 WebAssembly。 请注意,这并不总是那么容易,特别是对于更复杂的代码库和图形应用程序,但这将在 另一篇文章中讨论。
Emscripten 的另一个优点是,它可以为我们生成一些 JavaScript 胶水代码,用于处理初始化 WebAssembly 模块、从 JavaScript 调用 C 函数,甚至提供 虚拟文件系统。
让我们从 emmake 输出的可执行文件 jq 生成该胶水代码
# But first, rename the jq executable to a .o file; otherwise,
# emcc complains that the "file has an unknown suffix"
mv jq jq.o
# Generate .js and .wasm files from jq.o
# Disable errors on undefined symbols to avoid warnings about llvm_fma_f64
emcc jq.o -o jq.js \
-s ERROR_ON_UNDEFINED_SYMBOLS=0
为了确保它正常工作,让我们尝试直接在命令行上使用 jq 教程 中的一个示例
# Output the description of the latest commit on the jq repo
$ curl -s "https://api.github.com/repos/stedolan/jq/commits?per_page=5" | \
node jq.js '.[0].commit.message'
"Restore cfunction arity in builtins/0\n\nCount arguments up-front at definition/invocation instead of doing it at\nbind time, which comes after generating builtins/0 since e843a4f"
就这样,我们现在准备好在浏览器中运行 jq 了!
结果
使用上面 emcc 的输出,我们可以组合一个用户界面,该界面在用户提供的 JSON Blob 上调用 jq。 这是我构建 jqkungfu 采取的方法(源代码 可在 GitHub 上获得)

jqkungfu,一个通过将 jq 编译为 WebAssembly 构建的 Playground
尽管有类似的 Web 应用程序可以让您在浏览器中执行任意 jq 查询,但它们通常实现为在沙盒中执行用户查询的服务器端应用程序(上面的选项 #1)。
相反,通过将 jq 从 C 编译为 WebAssembly,我们可以兼得两全:服务器的灵活性和浏览器的安全性。 具体来说,好处是
- 灵活性:用户可以“选择自己的冒险”,并在限制较少的情况下使用该应用程序
- 速度:加载 Wasm 模块后,执行查询非常快,因为所有的魔法都发生在浏览器中
- 安全性:没有后端意味着我们不必担心我们的服务器被入侵或用于挖掘比特币
- 便利性:由于我们不需要后端,因此 jqkungfu 只是作为静态文件托管在云存储平台上
结论
WebAssembly 是一种强大的工具,可以将现有的命令行实用程序带到 Web 上。 当作为教程的一部分包含时,此类 Playground 可以成为强大的教学工具。 它们甚至可以让您的用户在安装工具之前试用该工具。
如果您想进一步深入研究 WebAssembly 并学习如何构建像 jqkungfu(或像 Pacman 这样的游戏!)这样的应用程序,请查看我的书 使用 WebAssembly 升级。
评论已关闭。