Kotlin 语言参考文档 中文版 Help

使用 IR 编译器

Kotlin/JS IR 编译器后端是 Kotlin/JS 的主要创新方向, 并为以后的技术发展探索道路.

Kotlin/JS IR 编译器后端不是从 Kotlin 源代码直接生成 JavaScript 代码, 而是使用一种新方案. Kotlin 源代码首先转换为 Kotlin 中间代码(intermediate representation, IR), 然后再编译为 JavaScript. 对于 Kotlin/JS, 这种方案可以实现更加积极的优化, 并能够改进以前的编译器中出现的许多重要问题, 比如, 生成的代码大小(通过死代码清除), 以及 JavaScript 和 TypeScript 生态环境的交互能力, 等等.

从 Kotlin 1.4.0 开始, 可以通过 Kotlin Multiplatform Gradle 插件使用 IR 编译器后端. 要在你的项目中启用它, 需要在你的 Gradle 构建脚本中, 向 js 函数传递一个编译器类型参数:

kotlin { js(IR) { // 或者: LEGACY, BOTH // ... binaries.executable() // 不兼容 BOTH, 详情请见下文 } }
  • IR 对 Kotlin/JS 使用新的 IR 编译器后端.

  • LEGACY 使用旧的编译器后端.

  • BOTH 编译项目时使用新的 IR 编译器以及默认的编译器后端. 主要用于 编写同时兼容于两种后端的库.

编译器类型也可以在 gradle.properties 文件中通过 kotlin.js.compiler=ir 来设置. 但是这个设置会被 build.gradle(.kts) 中的任何设置覆盖.

顶级属性(top-level property)的延迟初始化(Lazy initialization)

为了改善应用程序的启动速度, Kotlin/JS IR 编译器会对顶级属性(top-level property)进行延迟初始化(Lazy initialization). 通过这种方式, 应用程序启动时不会初始化它的代码中的全部顶级属性. 而只会初始化在启动阶段需要的那些顶级属性; 其他属性的初始化会被延迟, 直到使用它们的代码真正被执行时才会生成属性值.

val a = run { val result = // 假设这里是一段计算密集的代码 println(result) result } // 属性值直到初次使用时才会计算

如果由于某些原因你需要(在应用程序启动阶段)提早初始化一个属性, 可以对它标注 @EagerInitialization 注解.

对开发阶段二进制文件进行增量编译

JS IR 编译器提供了 对开发阶段二进制文件的增量编译模式, 可以对开发过程提高速度. 在这种模式下, 编译器会在模型层级缓存 Gradle task compileDevelopmentExecutableKotlinJs 的结果. 在后续的编译中, 对未修改的源代码文件使用缓存的编译结果, 可以使得编译更快完成, 尤其是在对代码进行少量修改的情况.

增量编译是默认启用的. 如果要对开发阶段二进制文件禁用增量编译, 请向项目的 gradle.propertieslocal.properties 文件添加以下设置:

kotlin.incremental.js.ir=false // 默认为 true

输出模式

你可以选择让 JS IR 编译器在你的项目中如何输出 .js 文件:

  • 对每个模块输出 .js 文件. 默认情况下, JS 编译器的编译结果是对项目的每个模块输出单独的 .js 文件.

  • 对每个项目输出 .js 文件. 你也可以将整个项目编译为单个 .js 文件, 方法是向 gradle.properties 添加以下设置:

    kotlin.js.ir.output.granularity=whole-program // 默认值是 'per-module'
  • 对每个文件输出 .js 文件. 你也可以设置更加细粒度的输出, 为每个 Kotlin 文件生成 1 个 JavaScript 文件 (如果 Kotlin 文件包含导出的声明, 则会生成 2 个 JavaScript 文件). 启用这个模式的方法如下:

    1. 向你的构建文件, 添加 useEsModules() 函数, 支持 ECMAScript 模块:

      // build.gradle.kts kotlin { js(IR) { useEsModules() // 启用 ES2015 模块 browser() } }

      或者, 你也可以使用 es2015 编译目标, 在你的项目中支持 ES2015 功能.

    2. 使用 -Xir-per-file 编译器选项, 或者更新你的 gradle.properties 文件, 如下:

      # gradle.properties kotlin.js.ir.output.granularity=per-file // 默认值是 `per-module`

在产品(Production)模式中对成员名称的极简化(Minification)

Kotlin/JS IR 编译器会使用它的内部信息 关于 你的 Kotlin 类和函数之间的关系, 来实现更加有效的极简化(Minification), 缩短函数, 属性, 和类的名称. 这样可以缩减打包完成的应用程序的大小.

当你使用 产品(Production) 模式构建你的 Kotlin/JS 应用程序时, 会自动应用这样的极简化处理, 并默认启用. 要关闭对成员名称的极简化处理, 请使用 -Xir-minimized-member-names 编译器选项:

kotlin { js(IR) { compilations.all { compileTaskProvider.configure { compilerOptions.freeCompilerArgs.add("-Xir-minimized-member-names=false") } } } }

预览功能: 生成 TypeScript 声明文件 (d.ts)

Kotlin/JS IR 编译器能够从你的 Kotlin 代码生成 TypeScript 定义. 在开发混合 App(hybrid app)时, JavaScript 工具和 IDE 可以使用这些定义, 来提供代码自动完成, 支持静态分析, 使得在 JavaScript 和 TypeScript 项目中包含 Kotlin 代码变得更加便利.

如果你的项目输出可执行文件 (binaries.executable()), Kotlin/JS IR 编译器会收集所有标注了 @JsExport 注解的顶级声明, 并自动在一个 .d.ts 文件中生成 TypeScript 定义.

如果你想要生成 TypeScript 定义, 你需要在 Gradle 构建文件中明确进行配置. 请在你的 build.gradle.kts 文件的 js 小节 中添加 generateTypeScriptDefinitions(). 例如:

kotlin { js { binaries.executable() browser { } generateTypeScriptDefinitions() } }

这些声明位于 build/js/packages/<package_name>/kotlin 目录中, 与相应的未经 webpack 处理的 JavaScript 代码在一起.

IR 编译器目前的限制

新的 IR 编译器后端的一个重要变化是与默认后端之间 没有二进制兼容性. 使用新的 IR 编译器后端创建的库, 会使用 klib 格式, 在默认后端中将无法使用. 同时, 使用旧编译器创建的库, 就是一个包含 js 文件的 jar, 也不能在 IR 后端中使用.

如果你希望在你的项目中使用 IR 编译器后端, 那么需要 将所有的 Kotlin 依赖项升级到支持这个新后端的版本. JetBrains 针对 Kotlin/JS 平台, 对 Kotlin 1.4+ 版本发布的库, 已经包含了使用新的 IR 编译器后端所需要的全部 artifact.

如果你是库的开发者, 希望为现在的编译器后端和新的 IR 编译器后端同时提供兼容性, 请阅读 针对 IR 编译器编写库 小节.

与默认后端相比, IR 编译器后端还存在一些差异. 试用新的后端时, 需要知道存在这些可能的问题.

  • 有些 库依赖于默认后端的独有的特性, 比如 kotlin-wrappers, 可能会出现一些问题. 你可以通过 YouTrack 跟踪这个问题的调查结果和进展.

  • 默认情况下, IR 后端 完全不会让 Kotlin 声明在 JavaScript 中可见. 要让 JavaScript 可以访问 Kotlin 声明, 这些声明 必须 添加 @JsExport 注解.

将既有的项目迁移到 IR 编译器

由于两种 Kotlin/JS 编译器的接口签名的不同, 要让你的 Kotlin/JS 代码能由 IR 编译器来编译, 可能需要你修改部分代码. 关于如何将既有的 Kotlin/JS 工程迁移到 IR 编译器, 请参见 Kotlin/JS IR 编译器 迁移向导.

针对 IR 编译器开发向后兼容的库

如果你是库的维护者, 希望同时兼容默认后端和新的 IR 编译器后端, 有一种编译器选择设置可以让你对两种后端都创建 artifact, 因此可以支持下一代 Kotlin 编译器, 同时又对你的既有用户保持兼容性. 这就是所谓的 both 模式, 可以在 gradle.properties 文件中通过 kotlin.js.compiler=both 来启用, 或者, 也可以在 build.gradle(.kts) 文件的 js 代码块之内, 设置为项目独有的选项:

kotlin { js(BOTH) { // ... } }

使用 both 模式时, 从你的源代码构建库时, IR 编译器后端和默认编译器后端都会被使用(如同它的名称所指的那样). 也就是说, Kotlin IR 的 klib 文件, 以及默认编译器的 jar 文件都会生成. 当发布到相同的 Maven 路径时, Gradle 会根据使用场景自动选择正确的 artifact – 对旧的编译器是 js, 对新的编译器是 klib. 因此对于使用两种编译器后端中的任何一个的项目, 你都可以编译并发布你的库.

最终更新: 2024/12/17