你的第一个 WebAssembly 程序和 Web 应用(完全在浏览器中编写、测试和部署)

TL;DR · AI 摘要
本文详细介绍了如何在浏览器中编写、测试和部署第一个 WebAssembly 程序和 Web 应用,无需安装任何软件。
核心要点
- WebAssembly 允许 C、C++ 和 Rust 等编译语言直接在浏览器中以接近原生速度运行。
- 通过 GitHub Codespaces,可以在浏览器中完成整个开发流程,无需下载或安装任何软件。
- WebAssembly 使科学计算和高性能应用可以直接在浏览器中运行,无需用户安装环境或处理依赖冲突。
结构提纲
按章节快速跳转。
- §引言
介绍 WebAssembly 的重要性和本文的目标。
解释 WebAssembly 的概念及其对现代 Web 开发的影响。
说明如何将本地代码编译成 WebAssembly。
- §动手教程
详细步骤指导如何在浏览器中创建并运行第一个 WebAssembly 应用。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- WebAssembly 程序和 Web 应用
- 引言
- WebAssembly 简介
- 概念
- 影响
- 跨编译到 WASM
- 编译过程
- 优势
- 动手教程
- 创建应用
- 运行应用
金句 / Highlights
值得收藏与分享的关键句。
WebAssembly 允许 C、C++ 和 Rust 等编译语言直接在浏览器中以接近原生速度运行。
开发者可以重用几十年来优化的系统代码,而仍然将应用程序作为简单的网页分发。
无需每次安装和加载环境,无需管理库,无需处理依赖冲突,无需操作系统限制。
一系列专注于从零开始构建基于 WebAssembly 的 Web 应用的文章/教程,整个过程都在浏览器中完成,包括 GitHub、其 Codespaces、WebAssembly(或“WASM”)、C 代码、在线 Visual Studio Code、端口转发、HTML 和 JavaScript。希望你在准备过程中能像我一样享受乐趣,并且学到很多,激发你的好奇心!
_有人说,你学得越多,就越觉得自己什么都不知道,还有更多的东西需要学习。这正是我的感受,实际上这种感觉很好。_
让我们开始吧!
- * *
作为一名长期支持客户端 Web 应用的倡导者,我一直觉得奇怪的是,我对 WebAssembly 几乎一无所知。但最近当我尝试构建新的用于浏览器内分析分子结构和模拟的 Web 平台时(如果你想知道更多,可以查看其第一个版本的预印本),我找到了动力。事实证明,有一些高效库和科学应用是用 C 等语言编写的,对我的平台非常有用,例如 Gemmi 和 FreeSASA。对于这两个库,已经有人完成了创建 WASM 版本的工作,我可以直接使用。
但我自己能做到吗?我是说,我能拿一段 C 代码并将其编译成可以在浏览器中运行的形式吗?答案是肯定的!而且更重要的是,忠于我的 Web 倡导,我可以在完全在浏览器内完成这一切,正如我在 FreeSASA 上所展示的那样。
在我的学习过程中,我从甚至在 C 代码之前就开始了一个自我教程。我在这里带来了完整的解释,从零开始到生成 WASM 就绪文件,全部在浏览器中完成,免费,无需下载或安装任何软件,只需要一个免费的 GitHub 账户。
WebAssembly 是什么,为什么它很重要——简而言之
多年来,浏览器主要是一个展示层:非常适合界面和轻量级交互,但不适合严肃的计算。重型工作负载通常需要原生应用程序、Python 环境或远程服务器。WebAssembly (WASM) 正在改变这一点。它允许编译语言如 C、C++ 和 Rust 直接在浏览器中以接近原生的速度运行,使浏览器成为一个真正的计算平台,而不仅仅是显示表面。
实际上,WebAssembly 让开发人员可以将现有的高性能代码编译成浏览器可以高效执行的紧凑二进制格式。开发人员不必将性能关键逻辑重写为 JavaScript,而是可以重用数十年来优化的系统代码,同时仍然将应用程序作为简单的网页分发。结果是更快、更便携、更容易分享的软件:用户只需打开一个链接即可在本地运行复杂的应用程序,无需安装、依赖冲突或特定平台的设置。
这一转变的意义远不止于性能本身。WebAssembly 桥接了网络的可访问性和原生软件的强大功能之间的差距,使响应式的基于浏览器的工具能够用于模拟、数据分析、媒体处理、科学计算和交互式可视化。JavaScript 继续处理界面和用户交互,而 WASM 则作为后台的计算引擎。这种分离正在重塑现代 Web 应用程序的能力——即使你可能没有注意到,它在现代 Web 开发中已经很普遍。
交叉编译到 WASM
实际上,有了 WebAssembly,我们可以:
- 将原生代码编译成可移植的二进制格式,
- 直接加载到网页中,
- 并在浏览器中以接近原生的速度执行
对于数据科学家和科学开发者来说,这开启了一个令人兴奋的可能性:科学软件现在可以直接在浏览器中运行,而无需用户安装任何东西。无需每次安装和加载环境,无需管理库,无需依赖冲突,无需操作系统限制。
只是一个网页,让你的工具只需一个 URL 即可使用!
一个可以在浏览器中运行的详细教程
在这个教程中,我们将创建一个最小的 WebAssembly 应用程序,该程序将在网页上打印“Hello WASM!”并基于 C 代码:
#include <stdio.h>
int main() {
printf("Hello WASM!\n");
return 0;
}我们将把这段 C 代码编译成 WebAssembly,生成浏览器运行时文件,并直接在浏览器中执行它。
最重要的是:所有这些都将在网上完成,甚至包括编写 C 代码本身。一旦我们登录 GitHub,直到一切运行完毕,我们都不会离开浏览器!
我们将使用 GitHub Codespaces 进行基于浏览器的开发,
Emscripten(直接在 GitHub Codespaces 中)将 C 编译成 WebAssembly,以及一个简单的 Python HTTP 服务器(也从 GitHub Codespaces 中启动)来运行生成的 Web 应用程序。
_顺便说一句:什么是 GitHub Codespaces?它是一个即时的、基于云的开发环境,使用容器为你提供常见的语言、工具和实用程序。GitHub Codespaces 也是可配置的,允许你为项目创建自定义的开发环境。_
设定我们的工作流程
概念上,我们的工作流程如下所示:
C 代码 --> Emscripten 编译器 --> WebAssembly 模块 --> 浏览器执行 接下来我们将像阅读“傻瓜书”系列一样逐步了解所有步骤。
第一步 — 创建 GitHub 仓库
(顺便说一下,GitHub 是什么?GitHub 是一个基于网络的平台,类似于编程界的社交媒体,它作为软件项目的云托管协作家园。它源自 Git,一种版本控制软件,允许开发人员存储、跟踪更改并实时协同处理代码。)
首先,登录 GitHub 并创建一个新的仓库。例如,在我的免费账户中,截至 2026 年 5 月,它看起来是这样的(请参见左上角的红色箭头):

_(所有图片由作者提供)_
然后我们给它命名,并点击“创建仓库”,其他字段保持默认值不变:

在这个阶段,创建的仓库可能最初看起来是空的。为了初始化它,我们需要创建一个文件,可以是一个简单的 README 文件:

然后在填写了所需的最少信息后,下一个屏幕看起来是这样的:

然后通过右上角的“提交更改...”按钮触发更改提交,最后在弹出的框中点击“提交更改”。
第二步 — 启动 GitHub Codespaces
现在我们可以进入 Codespaces,开始编写代码!
(顺便说一下,GitHub Codespaces 是一个“即时”的基于网络的开发环境,允许您完全在浏览器中编写、运行和调试代码,通过在线版的 Visual Studio Code 实现。)
您可以通过点击页面顶部的 <> 代码按钮找到启动 Codespaces 的按钮,该页面是在您提交上述更改后显示的:

当您点击“在 main 上创建 codespace”时,几秒钟后您会看到在线版的 Visual Studio Code 应用程序,如下所示:

您会看到应用程序创建了一个具有唯一名称的环境,例如我得到的是“vigilant-space-rotary-phone”,但您的名称会不同。如果您检查当前的 URL,您会发现环境的名称也会出现在那里,例如我的是 “https://vigilant-space-rotary-phone-someunreadblestuff.github.dev/”
第三步 — 创建 C 程序
在资源管理器窗口(左侧)中,随着您开发应用程序,文件列表会越来越多。目前我们只有一个文件,即我们之前创建的 README。
现在点击新建文件(参见下方的红色箭头),并将其命名为带有 .c 扩展名的文件。这个文件将包含我们的应用程序代码:

文件创建后,在右侧输入代码,例如:
#include <stdio.h>
int main() {
printf("Hello WASM tutorial!\n");
return 0;
}使用 Ctrl+S(Mac 上为 Cmd+S)保存文件,文件现在已准备好编译。
第四步:在在线 Visual Studio Code 会话中使 Emscripten 可用
但是要将 C 代码编译为 WebAssembly,我们需要 Emscripten,而它目前还没有在环境中。Emscripten 是 WebAssembly 生态系统中的核心工具之一。它可以将 C 和 C++ 代码编译为 .wasm 文件,并生成与浏览器兼容的运行时文件,这些文件将提供应用程序的 HTML+JS 与 WASM 文件之间的接口。
要“安装”Emscripten(我加引号是因为实际上并没有在您的计算机上安装任何东西,因为这一切都发生在云端!),您需要在终端中输入一些命令,如下图所示的第一个命令:

第一个命令,如上图所示,只是从其 GitHub 仓库克隆(或“克隆”)当前版本的 Emscripten:
git clone https://github.com/emscripten-core/emsdk.git
输出将是这样的:

然后进入 emsdk 文件夹,该文件夹包含我们刚刚从 GitHub 拉取的内容,然后运行安装命令:

这将需要一些时间来运行,如果一切顺利(应该如此),那么您将在最后看到以下内容:

接下来激活 Emscripten:
./emsdk activate latest
最后加载环境变量:
source ./emsdk_env.sh
要验证安装,输入以下命令:
emcc --version
您现在应该看到 Emscripten 编译器的版本信息。
第五步 — 将程序编译为 WebAssembly!
仍然在终端中,我们在文件夹结构中上一级以退出安装 Emscripten 的 emsdk,然后运行编译命令 emcc file.c -o file.html:

您会在左上角的资源管理器中看到一些新文件出现:

目前的文件包括我们已经创建的 README 和 C 代码,以及由 Emscripten 创建的 .wasm、HTML 和 JS 文件。非常简单,对吧!
现在这些文件是什么,它们的作用是什么?
hello-wasm-tutorial.wasm是编译后的 WebAssembly 二进制文件,即来自 C 程序的核心可执行逻辑。
hello-wasm-tutorial.js是允许与 wasm 文件和其他 JavaScript 代码接口的 JavaScript 胶水代码,从而与 Web 应用程序本身接口。此文件加载 WASM 模块,初始化运行时内存,并管理浏览器与 WebAssembly 模块之间的通信。
hello-wasm-tutorial.html是由 Emscripten 自动生成的最小网页启动器。打开此页面将运行编译后的应用程序。
现在让我们继续测试它们!
第 6 步:测试(并在第 7 步中将文件移出并放入您自己的服务器!)
要在 Emscripten 创建的网页中测试我们的 wasm 应用程序,我们需要启动一个“本地”Web 服务器,这里的“本地”加了引号,因为我们实际上将在云中完成这一操作(但对 Codespaces 环境来说是“本地”的,因此得名)。
我们需要这样做是因为该应用程序旨在通过 Web 服务器提供服务。幸运的是,Python(已经在云环境中安装)提供了一个小型内置 HTTP 服务器。我们可以通过在终端中运行以下命令来激活此服务器:
python3 -m http.server 8000这是该命令的输出,包括一个按钮,点击后将在新窗口中启动 Web 应用程序:

当你点击该按钮时,你会看到一个目录列表,其中包含到目前为止的所有文件:

最后的魔法:当你点击 hello-wasm-tutorial.html 时,会加载该页面,该页面调用 wasm 程序并显示其输出(为了完整性,我也展示了浏览器的控制台):

- * *
通过简化 HTML 来理解(并回忆第 7 步是将文件移出并放入您自己的服务器!)
正如你在上面的例子中看到的(以及如果你按照教程在自己的计算机上操作的话),当 Emscripten 编译我们的代码时,生成了一个带有 CSS、整个用户界面(包括加载和其他状态消息、按钮等)的庞大 HTML 页面。这可能适合演示,但实际上会增加学习的复杂性。
我们可以做的是首先获取使 wasm 代码运行所需的最简 HTML 代码。事实证明,你需要的非常少!
只需在资源管理器中创建一个新的 HTML 文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello WASM 教程,最简单的!</title>
</head>
<body>
<h1>Hello WASM 教程,极简版</h1>
<script src="hello-wasm-tutorial.js"></script>
</body>
</html>保存它并返回到 Python 小型服务器上的目录列表页面,刷新以查看新文件,然后打开它。它现在看起来像这样,WASM 程序的字符串输出显示在控制台上,因为我们在 JS 代码中调用了它:

现在你可以探索不同的文件(主要是这个简单的 HTML 和 Emscripten 生成的 JS 文件,因为你已经编写了 C 代码,所以你知道它做了什么!)以更好地理解。
- * *
通过从 HTML+JS 接口调用 C 程序的函数来控制运行时间(下一步是第 7 步!)
准备好了吗?我们现在来看看如何创建一个带有按钮的 Web 应用程序,该按钮将调用由 Emscripten 从 C 代码编译的 WASM 程序中的函数,并将其结果应用到 HTML 文档中。
这里的主要问题是,我们必须创建独立于 main() 的 C 函数并导出它们,然后重新编译程序以生成新的 WASM 文件和新的 JS“胶水”文件,这些文件将与 HTML 接口以处理函数调用。
假设我们将 C 代码修改为以下内容,保留之前的 main() 函数并添加一个我们将在 HTML 中通过按钮调用的函数:
#include <stdio.h>
void button_message() {
printf("Hello from a function called specifically from the web page!\n");
}
int main() {
printf("Hello WASM tutorial (main function!)!\n");
return 0;
}请注意,在 main 函数中我们修改了 printf() 的输出;这是为了确保我们知道文本的来源。
现在,我们使用以下命令编译:
emcc hello-wasm-tutorial.c -o hello-wasm-tutorial.js -s EXPORTED_FUNCTIONS=_main,_button_message -s EXPORTED_RUNTIME_METHODS=ccall注意三点:(i) 我们没有要求重建整个 Web 应用程序,而只是重建 JS 胶水文件(当然还有 WASM 文件);(ii) 我们标记了要导出的函数(在这种情况下是两个);(iii) 函数名称前有一个下划线。
你可以回到 Python 服务器上的 Emscripten 生成的 HTML,你会发现它仍然像往常一样运行,只是由于我们在 main 函数中修改了 printf() 的参数,文本略有不同:

当然,由于我们编写的极简 HTML 页面调用了相同的 JS 胶水文件和相同的 WASM 文件,因此我们在 main 函数中对 printf() 调用的修改也会影响它(参见控制台日志):

现在,我们可以从极简 HTML 页面派生一个新的网页,其中添加一个按钮,该按钮将调用已编译到 WASM 文件中的 button_message() 函数。此 HTML 代码仍然调用相同的胶水 JS 文件,并且在 <script> 标签内有一些额外的 JS 代码来控制按钮的工作方式以及在加载时如何调用 main() 函数:
现在,这段代码有点复杂,原因在于我们非常简单的 C 程序中存在一个问题:它不会返回像字符串这样的数据,而这些数据是 Web 应用程序可以正确采样的。因此,我们使用了一种变通方法来捕获 printf() 打印的文本,并将其放入一个 JS 变量中,以便我们可以随意使用。我很快会介绍正确的处理变量传递的方法,但会在另一篇文章中!
目前,请先使用这种方法。当你加载这个新的带有按钮控制函数调用的简单页面时,首先你会看到:

然后点击按钮后,你会看到:

最后一步:下载相关文件并将其挂载到服务器上
由于没有后端,这个过程非常简单:下载 HTML、JS 和 WASM 文件,并将它们上传到服务器,确保所有文件都在同一个文件夹中。例如,这是我如何将我的最后一组文件移动到我的 Altervista 网站(顺便说一句,这是我最喜欢的免费托管平台,原因在这里):
首先,我们需要从 Codespaces 下载三个主要文件(HTML、WASM、JS)到本地计算机:

接下来,我们将它们上传到网站的正确文件夹中,在我的 Altervista 账户中,文件夹最终包含以下内容:

最后,我们只需打开 HTML 文件,一切就运行起来了!

- * *
是时候练习了,下次见!
我真的希望你在这个过程中获得了许多乐趣,就像我在学习这一切时一样。在我的下一篇帖子中,我将尝试涵盖以下内容:
- 从 C/WASM 返回值而不是依赖于
printf(), Module.ccall()的返回类型,- 从 JS 传递参数到 WASM,
- 在多次调用中保持持久的 WASM 状态,
- WASM 中的全局/静态变量,
- 一个在会话期间“保持活跃”的 WASM 模块模型,
- 浏览器与 WASM 程序之间的交互架构,
- 为什么
main()行为像初始化。
所有这些内容,同时构建一个类似本教程中的小型有状态交互示例。不用多说,再次使用 GitHub Codespaces 完成这一切。
下次见,这里有一份我通过结合 ChatGPT 生成的内容和手动编辑准备的文章摘要:
