KMP Web 开发实战(一):Compose Web、Kotlin/JS、Vue + KMP 到底是什么关系?
2026/7/19 8:23:35 网站建设 项目流程

前言

学习 KMP 时,Android 和 iOS 的路线相对容易理解:

commonMain ├── 共享业务逻辑 ├── 共享数据模型 └── 共享网络请求 androidMain └── Android 平台实现 iosMain └── iOS 平台实现

但是到了 Web 端,Gradle 中突然出现了很多配置:

js { browser() }
js { browser() binaries.executable() }
wasmJs { browser() binaries.executable() }

于是很容易产生一系列疑问:

  • jswasmJs有什么区别?
  • KMP 到底能不能直接开发 Web 页面?
  • wasmJs打包出来的是网站吗?
  • 已经有 Vue 项目了,还需要 Compose Web 吗?
  • sharedLogic能不能编译成 JS,让 Vue 直接调用?
  • 为什么有些项目使用js,有些项目使用wasmJs

这些问题之所以容易混乱,是因为我们把以下三个维度混到了一起:

1. Web 页面由谁开发? 2. Kotlin 最终编译成什么? 3. 当前模块是完整应用,还是共享代码库?

这一篇先不深入 Gradle 细节,而是先把 KMP Web 的整体路线讲清楚。


一、先给出整体结论

从项目架构角度看,KMP Web 主要有两种落地方式。

方式一:KMP 同时负责业务逻辑和 Web UI

Kotlin 业务逻辑 + Compose Multiplatform UI ↓ Kotlin/Wasm ↓ 浏览器运行

对应配置通常是:

wasmJs { browser() binaries.executable() }

这种方式下,Web 页面不再由 Vue 或 React 编写,而是直接使用 Compose:

@Composable fun LoginScreen() { Column { Text("用户登录") Button( onClick = { // 登录 } ) { Text("登录") } } }

这套 Compose 页面可以和 Android、iOS、桌面端共享。

当前官方将 Kotlin/Wasm 与 Compose Multiplatform 结合,作为浏览器端共享 UI 的主要路线;Compose Multiplatform Web 当前处于 Beta 阶段。


方式二:Vue 负责 Web UI,KMP 只共享业务逻辑

KMP sharedLogic ↓ 编译成 JavaScript ↓ Vue 项目导入调用 ↓ Vue 负责页面和交互

对应配置的核心是:

js { browser() }

这种方式下:

Android UI → Compose iOS UI → Compose Multiplatform 或 SwiftUI Web UI → Vue

但是三端可以继续共享:

数据模型 参数校验 二维码解析 接口 Result 业务规则 状态计算 协议解析 网络请求逻辑

Kotlin/JS 会将 Kotlin 代码转换为 JavaScript,因此这些业务能力可以提供给 JavaScript、TypeScript、Vue 或 React 项目使用。


二、为什么又会看到三种配置?

虽然项目架构主要是两条路线,但是从编译形式看,确实可能看到三种情况。

① wasmJs + executable ② js + executable ③ js 共享库

它们分别解决不同问题。


三、第一种:Compose Multiplatform 开发完整 Web 应用

配置通常是:

kotlin { wasmJs { browser() binaries.executable() } }

逐步翻译就是:

wasmJs → 把 Kotlin 编译成 WebAssembly browser() → 程序运行在浏览器中 binaries.executable() → 当前模块是一个可以独立运行的完整应用

完整链路:

Kotlin 业务代码 + Compose 页面 ↓ 编译为 Wasm ↓ 生成网站发布文件 ↓ 部署到 Nginx ↓ 浏览器访问

1. Compose 代码就是 Web 页面源码

例如:

@Composable fun App() { Column { Text("设备管理系统") Button( onClick = { println("查询设备") } ) { Text("查询") } } }

在 Android 中,这段代码会显示成 Android 页面。

在 iOS 中,它可以显示成 iOS 页面。

在 Web 中,则通过 Kotlin/Wasm 在浏览器中渲染。

因此,这里的 Compose 代码相当于 Vue 项目中的:

App.vue LoginView.vue HomeView.vue DeviceList.vue

只是页面不再使用 HTML、CSS、Vue 编写,而是使用 Kotlin 和 Compose 编写。

Compose Multiplatform 的目标,就是让同一套声明式 UI 代码能够运行在 Android、iOS、桌面和 Web 等平台。


2. 打包结果类似 Vue 的dist

Vue 项目通常是:

Vue 源码 ↓ npm run build ↓ dist/ ├── index.html ├── assets/index.js └── assets/index.css

KMP Compose Web 则是:

Kotlin + Compose 源码 ↓ Gradle 构建 ↓ Web 发布目录 ├── index.html ├── JavaScript 加载文件 ├── WebAssembly 文件 └── 图片、字体等资源

所以可以简单理解为:

Vue 的 dist ≈ KMP Wasm 的 Web 发布目录

两者都属于:

已经构建完成、能够部署到 Web 服务器的网站文件。

区别只在于它们的源码和主要运行产物不同:

Vue → TypeScript/JavaScript + HTML + CSS → JavaScript Compose Multiplatform Web → Kotlin + Compose → WebAssembly + JavaScript 加载代码

官方 Kotlin/Wasm 教程也将最终结果描述为可以发布成网站的构建产物。


3.index.html只是启动外壳

Compose Web 项目中仍然可能存在:

index.html

但这不代表页面需要重新使用 HTML 编写。

这个 HTML 的主要职责是:

打开浏览器页面 ↓ 加载 JavaScript 启动代码 ↓ 加载 Wasm ↓ 启动 Compose UI

类似 Android 项目中:

AndroidManifest.xml + MainActivity

负责提供应用入口。

真正的页面依然写在:

@Composable fun App()

中。


四、第二种:Kotlin/JS 开发完整 Web 应用

有时我们还会看到:

js { browser { commonWebpackConfig { outputFileName = "webApp.js" } } binaries.executable() }

它的意思是:

Kotlin ↓ 编译成 JavaScript ↓ 通过 Webpack 打包 ↓ 生成完整 Web 应用

这里依然是完整网站,只是没有编译成 Wasm,而是编译成 JavaScript。


它和wasmJs的区别

Kotlin/JS

js { browser() binaries.executable() }

编译链路:

Kotlin ↓ JavaScript ↓ 浏览器

最终主要产物可能是:

index.html webApp.js 资源文件

Kotlin/JS 本身可以直接开发浏览器应用,并能够访问浏览器和 DOM API。


Kotlin/Wasm

wasmJs { browser() binaries.executable() }

编译链路:

Kotlin ↓ WebAssembly ↓ 浏览器

最终主要产物可能是:

index.html app.js app.wasm 资源文件

为什么现在 Compose Web 更常看到wasmJs

因为当前 Compose Multiplatform 的 Web 共享 UI 路线,主要建立在 Kotlin/Wasm 之上:

Compose UI ↓ Kotlin/Wasm ↓ 浏览器

而 Kotlin/JS 更适合以下场景:

  • 直接开发 JavaScript 浏览器程序;
  • 操作 DOM;
  • 使用 JavaScript 生态;
  • 把 Kotlin 业务逻辑提供给 JS/TS 项目;
  • 和 Vue、React 等前端框架集成。

因此:

js

不等于“只能做共享库”。

wasmJs

也不等于“唯一能做完整网站”。

两者都能产生浏览器程序,只是编译结果不同。官方分别提供了 Kotlin/JS 浏览器应用和 Kotlin/Wasm Compose 应用的完整入门路径。


五、第三种:sharedLogic 编译成 JS 给 Vue 使用

这才是已有 Vue 项目最关心的方式。

假设 KMP 项目结构是:

project ├── sharedLogic ├── sharedUI ├── androidApp ├── iosApp └── web-vue

其中:

sharedLogic

负责:

数据模型 接口请求 业务规则 参数校验 二维码解析 状态计算 数据转换

而:

web-vue

继续负责:

HTML CSS Vue 组件 Vue Router Pinia Element Plus 浏览器交互

整体调用链:

sharedLogic/commonMain ↓ Kotlin/JS 编译 ↓ JavaScript + TypeScript 声明 ↓ Vue import ↓ Vue 页面调用

1. KMP 业务代码

例如:

class PhoneValidator { fun validate(phone: String): Boolean { return phone.length == 11 && phone.all { it.isDigit() } } }

Android 可以调用:

val valid = PhoneValidator().validate(phone)

iOS 可以通过 KMP 导出的 Framework 调用。

Web 端则可以把这段逻辑编译成 JavaScript,再提供给 Vue 使用。

Kotlin/JS 支持将 Kotlin 声明导出给 JavaScript 和 TypeScript;通过@JsExport,可以让指定的 Kotlin API 以稳定名称暴露给外部 JavaScript。


2. Vue 中调用

前端的使用方式最终可能类似:

import { PhoneValidator } from "shared-logic" const validator = new PhoneValidator() const result = validator.validate("13800138000") console.log(result)

这里 Vue 并不知道 Kotlin 内部如何实现。

Vue 只需要把shared-logic当成一个普通的 JavaScript 或 TypeScript 依赖。


3. Vue 最后仍然生成自己的dist

完整流程是:

KMP sharedLogic ↓ 编译成 JS 业务库 ↓ Vue 导入 sharedLogic ↓ npm run build ↓ Vue dist ↓ 部署网站

也就是说:

sharedLogic.js

只是 Vue 项目的一个依赖。

它自己并不是网站。

真正完整的网站仍然由 Vue 打包生成:

dist/ ├── index.html ├── assets/index.js ├── assets/index.css └── 其他资源

这是它和:

wasmJs { browser() binaries.executable() }

最大的区别。


六、两条项目架构路线怎么区分?

现在回到最初的问题。

从项目架构角度看,主要还是两种方式。


路线一:KMP 直接负责 Web UI

Android UI ─┐ iOS UI ├── Compose Multiplatform Web UI ┘ 业务逻辑 └── commonMain

典型配置:

wasmJs { browser() binaries.executable() }

结果:

KMP 自己生成完整网站

适合:

  • 新项目;
  • Kotlin 团队主导;
  • Android 已经全面使用 Compose;
  • 希望 Android、iOS、Web 共享页面;
  • 后台系统、内部工具、业务型应用;
  • 没有大量历史 Vue 组件。

路线二:Vue 负责 UI,KMP 共享逻辑

Android UI → Compose iOS UI → Compose 或 SwiftUI Web UI → Vue 三端业务逻辑 └── sharedLogic/commonMain

核心配置:

js { browser() }

结果:

KMP 生成 JS 业务库 ↓ Vue 导入 ↓ Vue 生成完整网站

适合:

  • 已经有成熟 Vue 项目;
  • 前端团队熟悉 Vue;
  • 已经积累大量 Web 组件;
  • Web 页面需要充分使用 HTML、CSS、DOM;
  • 只希望复用业务规则和数据逻辑;
  • 不准备统一三端 UI。

七、那js + executable属于哪一类?

js { browser() binaries.executable() }

它处于两条常见路线之间:

Kotlin 自己负责完整 Web 应用 但是最终编译成 JavaScript

它不是 Vue 项目,也不是给 Vue 使用的普通共享逻辑库。

可以将三种技术形式整理为:

配置形式最终结果页面由谁开发
wasmJs + executable完整 Wasm Web 应用Compose
js + executable完整 JavaScript Web 应用Kotlin
js共享模块JavaScript 业务库Vue、React 等

所以:

项目架构上通常讨论两种路线,但底层技术上可能看到三种构建形态。

这正是很多初学者感觉混乱的根源。


八、不要把几个概念绑定死

误区一:看到js就认为是给 Vue 使用

不一定。

js { browser() binaries.executable() }

这是一个可以独立运行的 Kotlin/JS Web 应用。

而:

js { browser() }

如果没有生成可执行应用,才更可能是一个被其他模块或项目使用的代码模块。官方文档说明,省略binaries.executable()时,Kotlin/JS 默认生成的是 Kotlin 内部库产物,而不是可独立运行的应用文件。


误区二:只有wasmJs才能开发网站

不对。

下面两种都可以是完整 Web 应用:

js { browser() binaries.executable() }
wasmJs { browser() binaries.executable() }

区别只是:

js → 主要编译成 JavaScript wasmJs → 主要编译成 WebAssembly

误区三:browser()表示生成了页面

不对。

browser()

只表示:

当前编译结果运行在浏览器环境。

它没有决定:

  • 页面使用 Compose 还是 DOM;
  • 当前模块是应用还是库;
  • 最终编译成 JS 还是 Wasm。

这些分别由其他配置决定。


误区四:binaries.executable()就是 Wasm 配置

也不对。

它既可以用于:

js { binaries.executable() }

也可以用于:

wasmJs { binaries.executable() }

它只负责表达:

当前模块需要生成可以独立运行的应用产物。

官方 Gradle DSL 中,jswasmJswasmWasi等 Web 目标都支持binaries.executable()


九、为什么有些新项目同时出现jswasmJs

某些较新的 Compose Multiplatform Web 项目中,可能同时看到:

js { browser() binaries.executable() } wasmJs { browser() binaries.executable() }

这不一定代表项目同时开发了两套页面。

一种用途是:

现代浏览器 → 使用 wasmJs 不满足 Wasm 能力的浏览器 → 回退到 js

Kotlin 从 2.2.20 开始提供了 Compose Web 的 JS/Wasm 交叉编译与兼容回退能力,因此新模板中同时出现两种目标时,要结合项目配置整体判断。

这属于浏览器兼容方案,不是又增加了一种新的业务架构。


十、结合实际项目应该怎么选?

假设当前情况是:

Android 已经全面使用 Compose iOS 正在接入 KMP/CMP Web 前端长期使用 Vue 已经存在 Vue 项目和前端团队

更稳妥的架构通常是:

sharedLogic ├── commonMain ├── androidMain ├── iosMain └── jsMain sharedUI ├── commonMain ├── androidMain └── iosMain androidApp iosApp web-vue

平台关系:

模块AndroidiOSWeb
sharedLogic使用使用使用
sharedUI使用使用不使用
androidApp使用不使用不使用
iosApp不使用使用不使用
web-vue不使用不使用使用

最终实现:

Android + iOS → 共享业务逻辑 → 共享 Compose UI Android + iOS + Web → 共享业务逻辑 Web → 保留 Vue UI

这样既不会推翻已有 Vue 技术体系,又能让真正容易重复和出错的业务规则保持一致。


十一、什么时候适合 Compose Web?

Compose Multiplatform Web 更适合:

新项目 内部管理系统 业务后台 设备控制台 跨平台工具 高度强调多端 UI 一致 Kotlin 团队主导

因为它最大的价值是:

不仅共享业务逻辑 还可以共享 UI

但当前 Compose Multiplatform Web 仍处于 Beta。对于公开网站、强 SEO 页面、复杂 Web 原生生态集成或成熟前端团队项目,需要结合实际要求评估。


十二、什么时候适合 Vue + KMP?

Vue + KMP 更适合:

已有 Vue 项目 已有成熟前端团队 大量使用 Web 组件库 页面强调 HTML 和 CSS 控制 希望保留 Web 原生生态 只需要共享业务逻辑

此时不要为了“跨平台”而强行把所有 Vue 页面迁移成 Compose。

KMP 的价值并不是:

所有平台必须完全使用同一套代码。

而是:

把真正适合复用的部分放到commonMain,平台 UI 和平台能力继续保留自己的优势。


十三、最终总结

KMP Web 最容易混乱,是因为:

Web 架构路线 编译目标 运行环境 应用或代码库

这几个概念经常同时出现在 Gradle 中。

先记住下面这组关系:

js = Kotlin 编译成 JavaScript wasmJs = Kotlin 编译成 WebAssembly browser() = 编译结果运行在浏览器 binaries.executable() = 生成能够独立运行的完整应用

再看三种常见形式:

① Compose + wasmJs + executable → KMP 负责页面和业务逻辑 → 生成完整 Web 应用 → 类似 Vue 的 dist ② Kotlin/JS + executable → Kotlin 负责完整 Web 应用 → 最终主要编译成 JavaScript ③ sharedLogic + Kotlin/JS → KMP 只提供业务逻辑 → Vue 负责页面 → Vue 最后生成 dist

站在实际项目架构角度,重点关注两条路线即可:

Web 页面也准备使用 Compose → Compose Multiplatform + wasmJs Web 页面继续使用 Vue → Vue + Kotlin/JS sharedLogic

对已有 Vue 技术栈的项目而言,更稳妥的选择通常是:

Android、iOS 共享 UI 和业务逻辑 Android、iOS、Web 共享业务逻辑 Web 继续使用 Vue

这不是跨平台做得不彻底,而是根据不同平台和现有团队能力,选择更合理的共享边界。


下一篇预告

下一篇进入 Gradle 配置本身:

《KMP Web Gradle 配置看不懂?js、wasmJs、browser、executable 逐行拆解》

将重点讲清:

js 和 wasmJs 分别创建了什么 Target? browser() 到底配置了什么? binaries.executable() 为什么有时写、有时不写? commonWebpackConfig 是干什么的? outputFileName = "webApp.js" 控制了什么? commonMain、jsMain、wasmJsMain 如何参与编译? 为什么有些新项目会同时存在 js 和 wasmJs?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询