用Kotlin编译成JavaScript并在ArkTS中调用 - KMP鸿蒙指南
本项目演示了使用Kotlin Multiplatform (KMP)实现鸿蒙跨端应用的技术方案。通过编写Kotlin代码并编译为JavaScript,在OpenHarmony ArkTS应用中调用,实现代码复用。核心流程包括:1) 编写带@JsExport注解的Kotlin函数;2) 配置Gradle构建,使用IR后端编译器生成优化的JS代码及TypeScript声明;3) 将编译输出的JS文件集


目录
项目概述
本项目演示如何使用 Kotlin Multiplatform (KMP) 实现鸿蒙跨端应用。我们编写 Kotlin 代码,将其编译成 JavaScript,然后在 OpenHarmony ArkTS 应用中调用。这是一个完整的端到端示例,展示了从 Kotlin 源代码到最终 OpenHarmony 应用的整个工作流程,实现真正的跨端代码复用。
Kotlin Multiplatform 是一个强大的跨平台开发框架,允许开发者使用单一的 Kotlin 代码库为多个平台编译。通过 KMP,我们可以编写一份业务逻辑代码,然后将其编译到不同的平台(如 JavaScript、Native 等),从而实现真正的代码复用和一致的业务逻辑。特别是在鸿蒙生态中,KMP 提供了完美的跨端解决方案。
核心优势
- 代码复用:一份 Kotlin 代码可编译到多个平台(JS、Native 等),避免重复编写相同的业务逻辑,完美支持鸿蒙跨端
- 类型安全:Kotlin 的强类型系统确保编译时安全,减少运行时错误
- 自动生成 TypeScript 声明:无需手写
.d.ts文件,编译器自动生成,保证类型定义与实现同步 - 高效开发:减少平台间的代码重复,提高开发效率和代码维护性,加快鸿蒙应用开发速度
- 现代编译器:使用 IR 后端编译器,生成优化的 JavaScript 代码
- 完整的工具链:Gradle 构建系统提供完整的依赖管理和构建流程,支持 OpenHarmony 应用开发
项目架构
kmp_openharmony/
├── src/
│ └── jsMain/
│ └── kotlin/
│ └── App.kt # Kotlin 源代码
├── build.gradle.kts # Gradle 构建配置
├── kmp_ceshiapp/ # OpenHarmony 应用
│ └── entry/src/main/ets/pages/
│ ├── Index.ets # ArkTS UI 页面
│ ├── hellokjs.js # 编译后的 JavaScript
│ └── hellokjs.d.ts # TypeScript 类型声明
└── build/ # 编译输出目录
└── js/packages/hellokjs/
文件说明
| 文件 | 说明 |
|---|---|
App.kt |
Kotlin 源代码,包含导出函数 |
build.gradle.kts |
配置 KMP 编译到 JS 的规则 |
hellokjs.js |
Kotlin 编译后的 JavaScript 文件 |
hellokjs.d.ts |
自动生成的 TypeScript 类型声明 |
Index.ets |
ArkTS UI 组件,调用 JS 函数 |
技术栈
后端(编译源)
- Kotlin 2.1.0:主要编程语言
- Kotlin Multiplatform:跨平台编译框架
- Gradle:构建工具
编译目标
- JavaScript (IR 后端):现代 JS 编译器
- Node.js:编译目标环境
- ES Modules:模块化方案
前端(调用方)
- ArkTS:OpenHarmony 应用开发语言
- ArkUI:UI 框架
工作流程
整个工作流程分为 5 个主要步骤,从编写 Kotlin 代码开始,最终在 ArkTS 应用中调用。每一步都有明确的目标和输出。
1. 编写 Kotlin 代码
在 src/jsMain/kotlin/App.kt 中编写函数,使用 @JsExport 注解导出。这一步是整个流程的起点,我们在这里定义所有需要被 JavaScript 调用的函数。只有标记了 @JsExport 注解的函数才会被编译器导出到 JavaScript 中。
@OptIn(ExperimentalJsExport::class)
@JsExport
fun helloFromKmp() {
println("hello from kmp")
}
@OptIn(ExperimentalJsExport::class)
@JsExport
fun basicExample1() {
val name = "Kotlin"
val version = 1.9
val isAwesome = true
println("Language: $name")
println("Version: $version")
println("Is Awesome: $isAwesome")
}
这段代码展示了如何在 Kotlin 中定义可以被 JavaScript 调用的导出函数。第一个函数 helloFromKmp() 是一个简单的示例,它只是打印一条消息。第二个函数 basicExample1() 演示了如何处理基本数据类型(字符串、浮点数、布尔值),并使用字符串模板进行输出。使用 @OptIn(ExperimentalJsExport::class) 注解来启用实验性的 JS 导出功能,这表示我们明确同意使用这个实验性功能。使用 @JsExport 注解标记函数为可导出,这告诉 Kotlin 编译器该函数需要被编译到 JavaScript 中,并且可以从 JavaScript 代码中调用。只有标记了 @JsExport 的函数才会被导出到 JavaScript 中,未标记的函数会被视为内部实现,这有助于减少编译输出的大小。
关键点:
@JsExport:标记函数为 JavaScript 可导出。这个注解告诉 Kotlin 编译器,该函数需要被编译到 JavaScript 中,并且可以从 JavaScript 代码中调用@OptIn(ExperimentalJsExport::class):启用实验性 JS 导出功能。这是一个编译器选项,表示我们知道这是一个实验性功能,并明确同意使用它- 只有标记的函数才能被 JS 调用:未标记的函数会被视为内部实现,不会被导出到 JavaScript 中,这有助于减少编译输出的大小
2. 配置 Gradle 构建
在 build.gradle.kts 中配置 JS 编译。这一步定义了如何将 Kotlin 代码编译到 JavaScript。Gradle 是 Kotlin 官方推荐的构建工具,它提供了强大的依赖管理和构建流程控制。
kotlin {
js(IR) { // 使用 IR 后端
moduleName = "hellokjs" // 模块名称
nodejs() // 编译目标
binaries.executable() // 生成可执行文件
generateTypeScriptDefinitions() // 生成 .d.ts 文件
useEsModules() // 使用 ES 模块
}
sourceSets {
val jsMain by getting {
dependencies {
// 添加依赖
}
}
}
}
这段代码是 Gradle 构建配置文件的核心部分,定义了如何将 Kotlin 代码编译到 JavaScript。kotlin 块配置了 Kotlin 编译器的行为。js(IR) 指定使用 IR(Intermediate Representation)后端,这是 Kotlin 编译器的现代后端,相比旧的后端,它生成的 JavaScript 代码更优化、更小。moduleName = "hellokjs" 设置编译后的模块名称,这个名称会用于生成的 JavaScript 文件名。nodejs() 指定编译目标为 Node.js 环境。binaries.executable() 指定生成可执行文件。generateTypeScriptDefinitions() 启用自动生成 TypeScript 声明文件的功能,这个选项会自动为所有导出的函数生成 .d.ts 文件,为 TypeScript/ArkTS 提供类型提示。useEsModules() 指定使用现代 ES 模块语法,这确保生成的 JavaScript 使用标准的 ES6 模块系统,而不是旧的 CommonJS 格式。sourceSets 块定义了源代码集合,jsMain 是 JS 编译目标的主源代码集合。
配置说明:
js(IR):使用最新的 IR 编译器后端。IR(Intermediate Representation)是 Kotlin 编译器的现代后端,相比旧的后端,它生成的 JavaScript 代码更优化、更小generateTypeScriptDefinitions():自动生成 TypeScript 声明文件。这个选项会自动为所有导出的函数生成.d.ts文件,为 TypeScript/ArkTS 提供类型提示useEsModules():使用现代 ES 模块语法。这确保生成的 JavaScript 使用标准的 ES6 模块系统,而不是旧的 CommonJS 格式
3. 编译到 JavaScript
运行 Gradle 构建命令。这一步执行实际的编译过程,将 Kotlin 源代码转换为可执行的 JavaScript 代码。编译过程包括语法检查、类型检查、优化和代码生成等多个阶段。
# Windows
.\gradlew.bat build
# Linux/Mac
./gradlew build
编译成功后,输出文件位置:
build/js/packages/hellokjs/
├── hellokjs.js # 编译后的 JavaScript(包含所有导出的函数)
└── hellokjs.d.ts # TypeScript 声明文件(为 ArkTS 提供类型提示)
这两个文件需要被复制到 OpenHarmony 应用的相应目录中,以便 ArkTS 代码可以导入和使用。
4. 生成的 TypeScript 声明
hellokjs.d.ts 自动生成,提供类型提示。TypeScript 声明文件是一个重要的中间产物,它定义了 JavaScript 函数的类型签名,使得 ArkTS 编译器可以进行类型检查和提供代码补全。
type Nullable<T> = T | null | undefined
export declare function helloFromKmp(): void;
export declare function basicExample1(): void;
5. 在 ArkTS 中导入和调用
在 Index.ets 中导入并使用。这是整个流程的最后一步,我们在 OpenHarmony 应用中使用编译后的 JavaScript 函数。ArkTS 是 OpenHarmony 的官方开发语言,它是 TypeScript 的超集,提供了完整的 UI 框架和系统 API 访问。
import { helloFromKmp, basicExample1 } from './hellokjs.js';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Column({ space: 20 }) {
Text(this.message)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('Call helloFromKmp()')
.onClick(() => {
helloFromKmp();
this.message = 'helloFromKmp() called!';
})
Button('Call basicExample1()')
.onClick(() => {
basicExample1();
this.message = 'basicExample1() called!';
})
}
.width('100%')
.height('100%')
.padding(20)
.justifyContent(FlexAlign.Center)
}
}
这段 ArkTS 代码是 OpenHarmony 应用的用户界面实现,展示了如何在 ArkTS 中导入和调用编译后的 Kotlin 函数。首先使用 import 语句从编译后的 JavaScript 模块 hellokjs.js 中导入两个函数 helloFromKmp 和 basicExample1。使用 @Entry 装饰器标记这是应用的入口页面,使用 @Component 装饰器标记这是一个 ArkUI 组件。使用 @State 装饰器定义一个响应式状态变量 message,用于存储显示在 UI 中的文本。build() 方法定义了 UI 的布局和样式。使用 Column 容器垂直排列各个元素,设置元素间距为 20。使用 Text 组件显示 message 的内容,设置字体大小为 24、加粗。使用两个 Button 组件,分别调用 helloFromKmp() 和 basicExample1() 函数。当用户点击按钮时,onClick 事件处理器被触发,执行 Kotlin 编译的 JavaScript 函数,然后更新 message 状态。整个 UI 占满屏幕,使用 FlexAlign.Center 进行居中对齐。
代码示例
本节展示了从 Kotlin 源代码到最终 ArkTS 调用的完整转换过程。通过这些示例,你可以理解 Kotlin 代码如何被编译成 JavaScript,以及如何在 ArkTS 中使用这些编译后的函数。
案例 1:基础 - 简单的数据类型操作
这个案例演示了如何在 Kotlin 中定义一个简单的函数,处理基本数据类型(字符串、浮点数、布尔值),然后将其编译到 JavaScript 并在 ArkTS 中调用。
Kotlin 源代码 (App.kt):
这是原始的 Kotlin 代码,定义了一个导出函数,该函数创建几个变量并打印它们的值。
@OptIn(ExperimentalJsExport::class)
@JsExport
fun basicExample1() {
val name = "Kotlin"
val version = 1.9
val isAwesome = true
println("Language: $name")
println("Version: $version")
println("Is Awesome: $isAwesome")
}
编译后的 JavaScript (hellokjs.js):
这是 Kotlin 编译器生成的 JavaScript 代码。注意编译器如何将 Kotlin 的字符串模板转换为 JavaScript 的字符串连接。
function basicExample1() {
var name = 'Kotlin';
var version = 1.9;
var isAwesome = true;
println('Language: ' + name);
println('Version: ' + version);
println('Is Awesome: ' + isAwesome);
}
这段代码是 Kotlin 编译器生成的 JavaScript 代码,展示了 Kotlin 源代码如何被转换为 JavaScript。Kotlin 中的 val 关键字被转换为 JavaScript 的 var 声明。字符串模板 $name 被转换为 JavaScript 的字符串连接操作 + name。函数的整体结构保持不变,但语法被转换为 JavaScript 的语法。println() 函数被保留,这是 Kotlin 标准库中的函数,编译器会将其编译为相应的 JavaScript 代码。这个转换过程是自动进行的,开发者无需手动编写 JavaScript 代码。编译器确保了 Kotlin 代码的语义在 JavaScript 中被正确地保留。
ArkTS 调用:
这是在 OpenHarmony 应用中调用该函数的方式。当用户点击按钮时,函数被执行,返回值被显示在 UI 中。
Button('Call basicExample1()')
.onClick(() => {
basicExample1();
this.message = 'basicExample1() called!';
})
输出:
Language: Kotlin
Version: 1.9
Is Awesome: true
这是函数执行后的输出结果。在我们的应用中,这些内容会同时显示在控制台和 UI 界面上。
UI 显示页面
本节详细介绍了 OpenHarmony 应用的 UI 设计和交互流程。我们使用 ArkUI 框架构建了一个简洁而功能完整的用户界面。
页面布局
以下是应用的页面布局示意图,展示了各个 UI 组件的位置和层级关系。
┌─────────────────────────────────┐
│ │
│ Hello World │
│ (显示消息文本) │
│ │
│ ┌─────────────────────────┐ │
│ │ Call helloFromKmp() │ │
│ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ Call basicExample1() │ │
│ └─────────────────────────┘ │
│ │
└─────────────────────────────────┘
完整 ArkTS 代码
这是完整的 ArkTS 页面代码,包含了所有的 UI 组件和交互逻辑。代码使用了 ArkUI 的声明式语法,使得 UI 定义更加简洁和直观。
import { helloFromKmp, basicExample1 } from './hellokjs.js';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Column({ space: 20 }) {
// 标题文本
Text(this.message)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
// 按钮 1:调用 helloFromKmp()
Button('Call helloFromKmp()')
.width('80%')
.height(50)
.fontSize(16)
.onClick(() => {
helloFromKmp();
this.message = 'helloFromKmp() called!';
})
// 按钮 2:调用 basicExample1()
Button('Call basicExample1()')
.width('80%')
.height(50)
.fontSize(16)
.onClick(() => {
basicExample1();
this.message = 'basicExample1() called!';
})
}
.width('100%')
.height('100%')
.padding(20)
.justifyContent(FlexAlign.Center)
}
}
这是完整的 ArkTS 页面代码,包含了所有的 UI 组件和交互逻辑。代码使用了 ArkUI 的声明式语法,使得 UI 定义更加简洁和直观。首先导入编译后的 Kotlin 函数。使用 @Entry 和 @Component 装饰器定义应用的入口页面。使用 @State 装饰器定义响应式状态变量 message,初始值为 ‘Hello World’。在 build() 方法中定义 UI 布局,使用 Column 容器垂直排列元素,设置间距为 20。第一个 Text 组件显示 message 的内容,设置字体大小为 24、加粗、居中对齐。两个 Button 组件分别调用 helloFromKmp() 和 basicExample1() 函数,设置宽度为 80%、高度为 50、字体大小为 16。当用户点击按钮时,onClick 事件处理器被触发,执行相应的 Kotlin 函数,然后更新 message 状态。整个 UI 占满屏幕(宽度 100%、高度 100%),设置内边距为 20,使用 FlexAlign.Center 进行居中对齐。
交互流程
应用的交互流程设计简洁而清晰,用户可以通过点击按钮来执行不同的 Kotlin 函数,并在 UI 中实时看到执行结果。
-
初始状态:
- 页面显示 “Hello World” 标题
- 两个按钮可供点击
- 输出区域为空
-
点击按钮 1(Call helloFromKmp()):
- 执行
helloFromKmp()函数 - 控制台输出:
hello from kmp - 页面标题更新为:
helloFromKmp() called! - 输出区域显示:
hello from kmp
- 执行
-
点击按钮 2(Call basicExample1()):
-
执行
basicExample1()函数 -
控制台输出多行内容
-
页面标题更新为:
basicExample1() called! -
输出区域显示:
Language: Kotlin Version: 1.9 Is Awesome: true
-
这种设计使得用户可以清楚地看到每个函数的执行结果,便于理解和调试。
常见问题
本节收集了开发者在使用 KMP 编译到 JavaScript 时可能遇到的常见问题和解决方案。
Q1: 如何导出更多函数?
A: 在 Kotlin 中添加 @JsExport 注解。这个过程非常简单,只需要在函数前添加两个注解即可。
@OptIn(ExperimentalJsExport::class)
@JsExport
fun myNewFunction(param: String): String {
return "Hello, $param"
}
这段代码展示了如何定义一个接受参数并返回值的导出函数。函数 myNewFunction 接受一个 String 类型的参数 param,并返回一个 String 类型的值。使用字符串模板 "Hello, $param" 来构建返回值。使用 @OptIn(ExperimentalJsExport::class) 和 @JsExport 注解标记函数为可导出。重新编译后,函数会自动出现在 hellokjs.js 和 hellokjs.d.ts 中。编译器会自动扫描所有标记了 @JsExport 的函数,并将其导出到生成的 JavaScript 文件中。在 ArkTS 中调用时,可以传入参数并获取返回值,例如 myNewFunction("World") 会返回 “Hello, World”。
Q2: 如何传递参数和返回值?
A: Kotlin 支持基本类型和复杂类型。你可以定义接受参数的函数,并返回各种类型的值。编译器会自动处理类型转换。
@OptIn(ExperimentalJsExport::class)
@JsExport
fun add(a: Int, b: Int): Int {
return a + b
}
@OptIn(ExperimentalJsExport::class)
@JsExport
fun greet(name: String): String {
return "Hello, $name!"
}
这段代码展示了如何定义接受不同类型参数并返回值的导出函数。add 函数接受两个 Int 类型的参数,返回它们的和。greet 函数接受一个 String 类型的参数,返回一个问候字符串。两个函数都使用 @OptIn(ExperimentalJsExport::class) 和 @JsExport 注解标记为可导出。
在 ArkTS 中调用:
let result = add(5, 3); // 返回 8
let greeting = greet("World"); // 返回 "Hello, World!"
这段代码展示了如何在 ArkTS 中调用这些导出的函数。add(5, 3) 调用 add 函数,传入两个整数参数,返回 8。greet("World") 调用 greet 函数,传入一个字符串参数,返回 “Hello, World!”。ArkTS 编译器会根据 .d.ts 文件中的类型定义进行类型检查,确保你传递的参数类型正确。如果传递了错误的参数类型,编译器会报错,防止运行时错误。
Q3: 编译失败怎么办?
A: 检查以下几点:
- Kotlin 版本:确保使用 2.0+。旧版本的 Kotlin 可能不支持 JS 导出功能
- Gradle 版本:使用兼容的 Gradle 版本。建议使用 7.0 或更高版本
- JDK 版本:建议使用 JDK 11+。JDK 8 可能存在兼容性问题
- Maven 仓库:确保网络连接正常。如果在中国,建议配置阿里云镜像以加快下载速度
查看编译错误日志:
./gradlew build --stacktrace
--stacktrace 选项会输出完整的错误堆栈,帮助你快速定位问题。
Q4: 如何调试 JavaScript 代码?
A: 使用浏览器开发者工具或 Node.js 调试器。你可以在编译后的 JavaScript 代码中添加 console.log() 语句来输出调试信息。
node --inspect build/js/packages/hellokjs/hellokjs.js
这个命令会启动 Node.js 调试器,你可以在 Chrome DevTools 中连接到它进行调试。
Q5: 性能如何?
A: Kotlin 编译的 JavaScript 性能接近手写 JS,因为:
- 使用现代 IR 后端:IR 后端使用了最新的编译优化技术,生成的代码更加高效
- 自动优化和 tree-shaking:编译器会自动移除未使用的代码,减少输出文件的大小
- 生成的代码可读性强:虽然是编译生成的,但代码结构清晰,易于调试
在大多数情况下,Kotlin 编译的 JavaScript 性能与手写的 JavaScript 相当。如果需要进一步优化,可以使用 Gradle 的发布配置来启用更激进的优化。
总结
本文档介绍了如何使用 Kotlin Multiplatform 编译到 JavaScript,并在 OpenHarmony ArkTS 应用中调用的完整过程。通过这个示例,你已经了解了整个工作流程的各个环节。
工作流程总结
以下是整个工作流程的简化视图,展示了从源代码到最终应用的完整路径。
Kotlin 源代码 (App.kt)
↓
Gradle 构建
↓
JavaScript 编译 (hellokjs.js)
↓
TypeScript 声明 (hellokjs.d.ts)
↓
ArkTS 导入调用 (Index.ets)
↓
OpenHarmony 应用运行
关键要点
- ✅ 使用
@JsExport标记可导出的函数:这是导出 Kotlin 函数到 JavaScript 的唯一方式 - ✅ 配置
build.gradle.kts启用 JS 编译:正确的 Gradle 配置是成功编译的前提 - ✅ 运行
./gradlew build编译:这个命令会执行完整的编译流程 - ✅ 在 ArkTS 中导入
hellokjs.js:确保导入路径正确 - ✅ 调用导出的函数:使用 TypeScript 类型提示进行类型安全的调用
- 欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)