欢迎光临
我们一直在努力

KMP Web 中的 WebAssembly 到底是什么?从 Vue dist 和 JNI 讲清楚

引言

最近在研究 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/JS

Kotlin

编译或转换成 JavaScript

浏览器执行 JavaScript

对应的 Gradle 配置可能是:

js {
browser()
}

Kotlin/JS 更适合:

将 Kotlin 业务逻辑提供给 Vue、React 等项目使用

和现有 JavaScript、TypeScript 生态深度交互

使用 Web 原生 HTML、DOM 和 CSS 开发页面

例如:

sharedLogic

编译成 JavaScript

Vue 项目导入使用

这种模式下,Vue 仍然负责页面,Kotlin 主要负责共享业务逻辑。

2. Kotlin/Wasm

Kotlin

编译成 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 没有想象中差别那么大。

Vue:

Vue 源码

npm run build

dist

Nginx / CDN / 静态服务器

Kotlin/Wasm:

Kotlin + 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 @click="count++">
Count: {{ count }}
</button>
</template>

Vue 的核心仍然建立在:

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 JNIWebAssembly
Java/Kotlin JavaScript/Vue
JNI 调用边界 JS 与 Wasm 互操作边界
C/C++ 方法 Wasm 导出函数
.so 动态库 .wasm 模块
Native 内存 Wasm 线性内存
Native 回调 Java Wasm 调用导入的 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

.wasm

2. 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 的调用流程很像 JNI

Android:

Kotlin / Java

JNI

.so

Web:

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 之间的关系就基本串起来了。

赞(0)
未经允许不得转载:171主机测评 » KMP Web 中的 WebAssembly 到底是什么?从 Vue dist 和 JNI 讲清楚
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址