KMP Web 中的 WebAssembly 到底是什么?从 Vue dist 和 JNI 讲清楚 📅 2026/7/23 17:59:29 引言最近在研究 KMP Web 时我看到了一段配置OptIn(ExperimentalWasmDsl::class) wasmJs { browser() binaries.executable() }前面的browser()还比较容易理解表示程序运行在浏览器中。但wasmJs又是什么继续往下了解就会遇到一个此前可能很少听说的概念WebAssembly 简称 Wasm如果以前主要从事 Android、Java 后端或者普通 Vue 业务开发确实可能很少直接接触 WebAssembly。我一开始也有几个疑问WebAssembly 是不是一种新的前端框架 它和 Vue 打包出来的 dist 有什么区别 JavaScript 调用 Wasm为什么感觉很像 Android 的 JNI KMP 为什么可以直接使用 Wasm 开发完整的 Web 页面这一篇就站在 Android 开发者的角度把这些问题一次讲清楚。一、WebAssembly 到底是什么WebAssembly简称 Wasm。官方将它定义为一种面向栈式虚拟机的二进制指令格式同时也是多种编程语言可以选择的可移植编译目标。C、C、Rust、Kotlin 等语言都可以将代码编译成 Wasm再交给支持 WebAssembly 的运行环境执行。可以先把它理解成C / C / Rust / Kotlin ↓ 编译 ↓ .wasm ↓ WebAssembly 运行时因此WebAssembly 并不是 Vue、React 这样的前端框架也不是专门用来写网页样式的语言。它和下面这些东西不属于同一层Vue、React、Compose 属于 UI 框架层 JavaScript、JVM Bytecode、WebAssembly 更接近编译目标和运行格式层从 Android 开发者熟悉的技术来看可以进行一个初步类比Java / Kotlin ↓ 编译成 JVM 字节码 ↓ 由 JVM 或 ART 执行而 WebAssembly 是Kotlin / Rust / C ↓ 编译成 Wasm ↓ 由 Wasm 运行时执行这只是帮助理解JVM 字节码和 Wasm 在运行模型、类型系统、内存管理等方面仍然存在明显区别。二、为什么以前很少听说 WebAssembly这并不代表 WebAssembly 没有人使用而是普通业务开发者很少需要直接接触它。例如传统 Android 开发链路是Kotlin / Java ↓ Android Runtime ↓ Android 应用Java 后端开发链路是Java / Kotlin ↓ JVM ↓ 服务器应用Vue 前端开发链路通常是Vue / TypeScript ↓ JavaScript ↓ 浏览器这些技术链路已经能够完成绝大多数业务开发没有必要额外引入 Wasm。WebAssembly 更容易出现在下面这些场景中图像处理 音视频编解码 文件压缩 加密算法 浏览器游戏 3D 与图形渲染 CAD 科学计算 已有 C/C 或 Rust 库迁移到浏览器 跨平台 UI 框架在这些场景中开发者可能希望复用已有的非 JavaScript 代码 在浏览器中执行计算密集型逻辑 让同一套核心代码运行在多个平台因此很多前端开发者虽然使用过依赖 Wasm 的第三方库却不一定直接编写过.wasm模块。而我现在突然遇到 WebAssembly是因为开始研究Kotlin Multiplatform Compose Multiplatform Web这已经进入了跨平台编译、浏览器运行时和多目标产物这一层。三、Kotlin 进入 Web 的两条路线Kotlin 官方目前提供两种主要的 Web 开发路线Kotlin/JS Kotlin/Wasm二者的编译链路不同。1. Kotlin/JSKotlin ↓ 编译或转换成 JavaScript ↓ 浏览器执行 JavaScript对应的 Gradle 配置可能是js { browser() }Kotlin/JS 更适合将 Kotlin 业务逻辑提供给 Vue、React 等项目使用 和现有 JavaScript、TypeScript 生态深度交互 使用 Web 原生 HTML、DOM 和 CSS 开发页面例如sharedLogic ↓ 编译成 JavaScript ↓ Vue 项目导入使用这种模式下Vue 仍然负责页面Kotlin 主要负责共享业务逻辑。2. Kotlin/WasmKotlin ↓ 编译成 WebAssembly ↓ 浏览器中的 Wasm 运行时执行对应配置可能是wasmJs { browser() binaries.executable() }Kotlin/Wasm 可以配合 Compose Multiplatform共享 Android、iOS、桌面端和 Web 端的部分业务逻辑与 UI。Kotlin 官方目前将 Kotlin/Wasm 标记为 Beta并将“共享业务逻辑和 UI”列为它的重要使用方向。四、Vue 的 dist 和 WebAssembly 有什么区别这是最容易混淆的地方。Vue 项目执行npm run build通常会生成一个dist目录dist/ ├── index.html ├── assets/ │ ├── index-xxxx.js │ ├── index-xxxx.css │ └── 图片、字体等资源Vue 官方文档说明生产构建默认会生成可以部署的dist目录。这个目录是一个完整的部署产物。浏览器访问网站时大致流程是打开 index.html ↓ 加载 JavaScript 和 CSS ↓ 启动 Vue ↓ 创建并更新页面 DOM那么 Kotlin/Wasm 呢执行./gradlew wasmJsBrowserDistribution官方示例项目会将可发布产物生成到webApp/build/dist/wasmJs/productionExecutable这个目录可以作为网站产物进行部署。目录中通常会包含productionExecutable/ ├── index.html ├── 应用程序.wasm ├── JavaScript 或 MJS 启动文件 ├── Compose 资源 ├── 图片 ├── 字体 └── 其他静态资源具体文件名称会随着项目名、Kotlin 版本和构建配置变化但整体作用类似。因此不能这样比较Vue 的 dist VS WebAssembly因为二者不属于同一层。dist是一个完整的部署目录而.wasm只是其中一种程序文件格式。正确的对应关系应该是Vue 的 dist 目录 ≈ Kotlin/Wasm 的 productionExecutable 目录进一步拆开看Vue dist/ ├── index.html ├── JavaScript ├── CSS └── 静态资源Kotlin/Wasm productionExecutable/ ├── index.html ├── .wasm ├── JS/MJS 启动与桥接代码 └── 静态资源所以Wasm 不是用来替代dist的。更准确地说.wasm是 Kotlin/Wasm 最终部署目录中的核心程序文件之一。五、两者的部署方式其实很像从部署人员的角度看Vue 和 Kotlin/Wasm 没有想象中差别那么大。VueVue 源码 ↓ npm run build ↓ dist ↓ Nginx / CDN / 静态服务器Kotlin/WasmKotlin Compose 源码 ↓ wasmJsBrowserDistribution ↓ productionExecutable ↓ Nginx / CDN / 静态服务器最终都是把构建后的静态资源目录发布到 Web 服务器。用户访问https://example.com浏览器再加载对应的 HTML、脚本、Wasm 和资源文件。所以从“构建并部署一个前端应用”的角度看Vue dist 和 Kotlin/Wasm productionExecutable承担的是相同层面的职责。真正的区别在于应用主体最终被编译成了什么以及页面由什么框架负责显示。六、Vue 与 Kotlin/Wasm 的运行方式有什么不同Vue 的运行链路Vue / TypeScript ↓ 打包成 JavaScript ↓ 浏览器执行 JavaScript ↓ 操作 HTML DOM ↓ 显示页面例如template button clickcount Count: {{ count }} /button /templateVue 的核心仍然建立在HTML CSS DOM JavaScript之上。Kotlin/Wasm Compose 的运行链路Kotlin Compose ↓ 编译成 WebAssembly ↓ 浏览器加载 Wasm ↓ Compose Runtime 运行 ↓ 显示页面例如Composable fun App() { var count by remember { mutableStateOf(0) } Button( onClick { count } ) { Text(Count: $count) } }这段代码和 Android Jetpack Compose 非常接近。也就是说当项目使用 Kotlin/Wasm Compose Multiplatform 时开发者可以直接使用 Kotlin 和 Compose 实现 Web UI而不一定需要再使用 Vue 编写同一套页面。七、为什么我觉得 WebAssembly 很像 JNI真正让我快速理解 Wasm 的是 Android 中的 JNI。Android 中一条典型的 JNI 调用链路是Kotlin / Java ↓ JNI 桥接 ↓ C / C Native 方法 ↓ .so 动态库例如Android 上层可能声明external fun decode(data: ByteArray): ByteArray实际实现位于 C 或 C 动态库中。而传统 WebAssembly 的调用链路可能是Vue / JavaScript ↓ JavaScript 与 Wasm 互操作 ↓ C / Rust / Kotlin 等代码 ↓ .wasm 模块JavaScript 可能调用 Wasm 导出的函数const result wasmModule.decode(data)从开发者视角看两条链路确实很像上层业务代码 ↓ 跨运行环境调用 ↓ 另一种语言生成的编译产物 ↓ 执行底层能力WebAssembly 标准提供了 JavaScript API用于加载、编译、实例化 Wasm 模块并处理模块的导入与导出。因此可以建立下面这组对应关系Android JNIWebAssemblyJava/KotlinJavaScript/VueJNI 调用边界JS 与 Wasm 互操作边界C/C 方法Wasm 导出函数.so动态库.wasm模块Native 内存Wasm 线性内存Native 回调 JavaWasm 调用导入的 JavaScript 函数所以严格来说.wasm 更接近 Android 的 .so JavaScript 与 Wasm 的互操作机制 更接近 JNI不过从整个调用流程和开发体验来看把传统 Wasm 理解为“Web 里的 JNI 模式”是一个非常有效的类比。八、为什么二者的开发体验很相似JNI 和 JavaScript/Wasm 互操作都要面对跨边界调用问题。例如参数如何传递 字符串怎么转换 数组怎么传递 对象能不能直接传 内存由谁分配和释放 异常如何跨边界处理 回调如何实现 高频调用是否会有额外开销JNI 中Java 字符串需要转换为 Native 能够处理的数据Java String ↓ jstring ↓ char* 或其他 Native 表示在传统 Wasm 互操作中字符串和复杂对象也通常不能像普通 JavaScript 对象那样直接存在于 Wasm 线性内存中往往需要编码、复制、地址和长度等配合。概念上可以理解为JavaScript String ↓ 编码和桥接 ↓ Wasm 线性内存中的数据因此两种模式都会强调尽量减少没有必要的跨边界调用 接口尽量设计得粗粒度一些 避免频繁传递大量复杂对象 明确数据和内存的生命周期这也是为什么有 JNI 经验的 Android 开发者会很自然地感觉 Wasm 的调用方式似曾相识。九、但 Wasm 和 JNI 不能完全画等号虽然调用流程相似但两者仍然存在重要区别。1. JNI 是桥接机制Wasm 是程序格式和运行模型JNI 的核心职责是让 Java/Kotlin 与 Native 代码互相调用而 WebAssembly 的核心是定义一种可移植的二进制指令格式 以及对应的执行模型因此JNI 更像一座桥 Wasm 更像桥另一边运行的程序格式更准确的对照仍然是JNI ≈ JS/Wasm 互操作机制 .so ≈ .wasm2. Native.so与 Wasm 的运行环境不同JNI 加载的.so是本机机器码运行在应用进程的 Native 环境中。Wasm 模块运行在 WebAssembly 运行时中。WebAssembly 在 Web 环境下采用沙箱执行模型并受到浏览器同源策略和权限模型约束。所以 Wasm 并不是把一段普通 Native 机器码直接扔进浏览器运行。3. Wasm 不一定只是底层能力库JNI 在 Android 中通常是Kotlin/Java 负责应用主体 Native 负责部分底层能力但 Kotlin/Wasm 可以不止做到这一点。十、WebAssembly 有两种不同的使用方式第一种作为 Web 项目的底层能力模块这是最接近 JNI 的模式。Vue / React ↓ JavaScript ↓ Wasm ↓ 算法、压缩、图像、音视频等能力例如一个在线图片处理网站可以这样分工Vue 页面布局 菜单 按钮 文件选择 进度展示 用户交互Wasm 图片解码 滤镜计算 格式转换 图片压缩 复杂算法这和 Android 项目中的分工很像Kotlin / Compose 页面和业务交互 JNI .so 图片处理、音视频、算法等底层能力在这种模式下可以把 Wasm 理解为 Web 项目的 Native 能力模块。第二种作为完整 Web 应用的运行主体Kotlin/Wasm Compose Multiplatform 又向前走了一步。Kotlin Compose ↓ Kotlin/Wasm ↓ 完整 Web 应用此时Kotlin/Wasm 不只是提供一个算法函数而是可能承担UI 状态管理 业务逻辑 网络请求 导航 跨平台共享代码例如wasmJs { browser() binaries.executable() }这里的binaries.executable()表明当前目标需要生成可以运行和分发的应用产物而不只是一个供其他模块依赖的普通代码模块。这种模式就不能简单理解为Vue 调用一个 Wasm 基础库而应该理解为整个 Web 应用的大部分 Kotlin 代码 被编译成 Wasm 并运行在浏览器中JavaScript 或生成的启动代码仍然可能负责加载 Wasm、连接浏览器环境以及完成必要的互操作但应用主体已经可以由 Kotlin 和 Compose 实现。十一、放到 KMP 项目中应该怎么理解假设一个 KMP 项目采用下面的结构sharedLogic sharedUI androidApp iosApp webApp其中sharedLogic 负责业务模型、网络、数据处理和状态逻辑 sharedUI 负责 Compose Multiplatform 共享 UI androidApp 负责 Android 应用壳和平台能力 iosApp 负责 iOS 应用壳和平台能力 webApp 负责 Web 入口和 Web 平台配置如果webApp使用wasmJs { browser() binaries.executable() }那么整体链路是sharedLogic sharedUI webApp ↓ Kotlin/Wasm 编译 ↓ productionExecutable 部署目录 ↓ 浏览器运行最终产物可以像 Vue 的dist一样部署到静态服务器。这时 Web 端并不是先写一个 Vue 项目 再把 KMP 页面嵌进去而是可以直接使用 Compose Multiplatform 编写 Web UI 使用 Kotlin 编写状态和业务逻辑 最终编译成 Kotlin/Wasm Web 应用十二、Kotlin/JS 和 Kotlin/Wasm 应该怎么选择可以根据项目目标进行区分。已经有成熟 Vue 或 React 项目如果 Web 页面继续由前端团队使用 Vue 或 React 开发只想复用部分 Kotlin 业务逻辑优先考虑 Kotlin/JS结构可能是KMP sharedLogic ↓ 编译成 JavaScript ↓ Vue / TypeScript 项目导入Kotlin 官方也将 Kotlin/JS 推荐给需要与现有 JavaScript/TypeScript 代码直接互操作、使用 Web 原生 UI 的场景。希望 Android、iOS 和 Web 共享 UI如果希望Android 使用 Compose iOS 使用 Compose Multiplatform Web 也使用 Compose Multiplatform那么可以考虑Kotlin/Wasm Compose Multiplatform结构是sharedLogic sharedUI 各平台少量适配代码此时Wasm 是完整 Web 应用的重要运行基础而不只是一个底层算法库。十三、最终应该怎样理解 WebAssembly经过这一轮梳理可以把 WebAssembly 总结为四句话。第一句Wasm 不是前端框架Vue / React / Compose 是 UI 框架 WebAssembly 是二进制指令格式和编译目标第二句Vue 的 dist 不对应单个.wasm正确对应关系是Vue dist ≈ Kotlin/Wasm productionExecutable而.wasm只是 Kotlin/Wasm 部署目录中的核心程序文件之一。第三句传统 Wasm 的调用流程很像 JNIAndroid Kotlin / Java ↓ JNI ↓ .soWeb JavaScript / Vue ↓ JS/Wasm 互操作 ↓ .wasm严格来说.so ≈ .wasm JNI ≈ JS 与 Wasm 的互操作机制第四句Kotlin/Wasm 不只可以做基础库传统 Wasm 经常作为 Vue、React 项目的底层计算模块。而在 KMP 中Kotlin/Wasm Compose Multiplatform还可以承载完整 Web 应用的 UI、状态和业务逻辑。总结Android 开发者第一次看到 WebAssembly 时完全可以先从 JNI 入手理解。传统 WebAssembly 模式确实很像上层 JavaScript 通过互操作边界 调用底层 Wasm 模块就像Android 上层 Kotlin/Java 通过 JNI 调用 Native .so这个类比能够快速建立基本认知。但还需要再往前走一步WebAssembly 并不只是 Web 端的 Native 基础库。在 Kotlin/Wasm Compose Multiplatform 中它还可以成为完整 Web 应用的运行主体。所以在 KMP Web 中看到wasmJs { browser() binaries.executable() }可以理解为把 Kotlin 和 Compose 编写的 Web 应用 编译成浏览器能够运行的 Wasm 产物而它最终生成的productionExecutable就相当于 Vue 项目构建后的dist到这里Vue、Wasm、KMP Web 和 JNI 之间的关系就基本串起来了。