Kotlin/JS IR 编译器

Kotlin/JS IR 编译器后端是围绕 Kotlin/JS 进行创新的主要焦点,并为该技术的发展铺平了道路 。

Kotlin/JS IR 编译器后端没有直接从 Kotlin 源代码生成 JavaScript 代码,而是利用了一种新途径。首先将 Kotlin 源代码转换为 Kotlin 中间表示(IR), 然后将其编译为 JavaScript。 对于 Kotlin/JS,这可以进行积极的优化,并可以改善以前的编译器中存在的痛点,例如生成的代码大小(通过消除无效代码)以及 JavaScript 与 TypeScript 生态系统的互操作性。

从 Kotlin 1.4.0 开始,可以通过 Kotlin Multiplatform Gradle 插件使用 IR 编译器后端。要在项目中启用它, 请将编译器类型传递给 Gradle 构建脚本中的 js 函数:

kotlin {
    js(IR) { // 或:LEGACY、BOTH
        // ...
        binaries.executable() // not applicable to BOTH, see details below
    }
}
  • IR 使用 Kotlin/JS 的新 IR 编译器后端。
  • LEGACY 使用旧版编译器后端。
  • BOTH 使用新的 IR 编译器以及默认的编译器后端编译项目。这个模式用于创作与两个后端兼容的库

The old compiler backend has been deprecated since Kotlin 1.8.0. Starting with Kotlin 1.9.0, using compiler types LEGACY or BOTH leads to an error.

{style="warning"}

还可以使用键值 kotlin.js.compiler=irgradle.properties 文件中设置编译器类型。 但是,build.gradle(.kts) 中的任何设置都会覆盖此行为。

Lazy initialization of top-level properties

For better application startup performance, the Kotlin/JS IR compiler initializes top-level properties lazily. This way, the application loads without initializing all the top-level properties used in its code. It initializes only the ones needed at startup; other properties receive their values later when the code that uses them actually runs.

val a = run {
    val result = // intensive computations
    println(result)
    result
} // value is computed upon the first usage

If for some reason you need to initialize a property eagerly (upon the application start), mark it with the @EagerInitialization{nullable="true"} annotation.

Incremental compilation for development binaries

The JS IR compiler provides the incremental compilation mode for development binaries that speeds up the development process. In this mode, the compiler caches the results of compileDevelopmentExecutableKotlinJs Gradle task on the module level. It uses the cached compilation results for unchanged source files during subsequent compilations, making them complete faster, especially with small changes.

Incremental compilation is enabled by default. To disable incremental compilation for development binaries, add the following line to the project's gradle.properties or local.properties:

kotlin.incremental.js.ir=false // true by default

The clean build in the incremental compilation mode is usually slower because of the need to create and populate the caches.

{style="note"}

Output mode

You can choose how the JS IR compiler outputs .js files in your project:

  • One per module. By default, the JS compiler outputs separate .js files for each module of a project as a compilation result.
  • One per project. You can compile the whole project into a single .js file by adding the following line to gradle.properties:

    kotlin.js.ir.output.granularity=whole-program // 'per-module' is the default
    
  • One per file. You can set up a more granular output that generates one (or two, if the file contains exported declarations) JavaScript file per each Kotlin file. To enable the per-file compilation mode:

    1. Add the useEsModules() function to your build file to support ECMAScript modules:

      // build.gradle.kts
      kotlin {
          js(IR) {
              useEsModules() // Enables ES2015 modules
              browser()
          }
      }
      

      Alternatively, you can use the es2015 compilation target to support ES2015 features in your project.

    2. Apply the -Xir-per-file compiler option or update your gradle.properties file with:

      # gradle.properties
      kotlin.js.ir.output.granularity=per-file // 'per-module' is the default
      

Minification of member names in production

The Kotlin/JS IR compiler uses its internal information about the relationships of your Kotlin classes and functions to apply more efficient minification, shortening the names of functions, properties, and classes. This reduces the size of resulting bundled applications.

This type of minification is automatically applied when you build your Kotlin/JS application in production mode, and enabled by default. To disable member name minification, use the -Xir-minimized-member-names compiler option:

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

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

The generation of TypeScript declaration files (d.ts) is Experimental. It may be dropped or changed at any time. Opt-in is required (see the details below), and you should use it only for evaluation purposes. We would appreciate your feedback on it in YouTrack.

{style="warning"}

Kotlin/JS IR 编译器能够从 Kotlin 代码生成 TypeScript 定义。在混合应用程序上工作时, JavaScript 工具与 IDE 可以使用这些定义来提供自动补全功能、支持静态分析器, 并使在 JavaScript 与 TypeScript 项目中更容易包含 Kotlin 代码。

If your project produces executable files (binaries.executable()), the Kotlin/JS IR compiler collects any top-level declarations marked with @JsExport and automatically generates TypeScript definitions in a .d.ts file.

If you want to generate TypeScript definitions, you have to explicitly configure this in your Gradle build file. Add generateTypeScriptDefinitions() to your build.gradle.kts file in the js section. For example:

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

可以在 build/js/packages//kotlin 中找到其定义以及相应的未打包 Web 的 JavaScript 代码。

IR 编译器的当前限制

新的 IR 编译器后端的主要变化是与默认后端 没有二进制兼容性。 A library created with the new IR compiler uses a klib format and can't be used from the default backend. In the meantime, a library created with the old compiler is a jar with js files, which can't be used from the IR backend.

如果要为项目使用 IR 编译器后端,则需要 将所有 Kotlin 依赖项更新为支持该新后端的版本。由 JetBrains 针对 Kotlin/JS 发布的针对 Kotlin 1.4+ 的库已经包含了与新的 IR 编译器后端一起使用所需的所有构件。

可能库开发者 希望提供与当前编译器后端以及新的 IR 编译器后端的兼容性,请另外查看“为 IR 编译器编写库”相关部分 部分。

与默认后端相比,IR 编译器后端也存在一些差异。在尝试新的后端时, 最好注意这些可能的缺陷。

  • 一些 依赖默认后端特定特性的库,例如 kotlin-wrappers,可能会显示一些问题。可以在 YouTrack 上跟踪调查与进度。
  • 默认情况下,IR 后端根本 不会使 Kotlin 声明可用于 JavaScript。要使 Kotlin 声明对 JavaScript 可见,必须使用 @JsExport 对其进行注解。

Migrating existing projects to the IR compiler

Due to significant differences between the two Kotlin/JS compilers, making your Kotlin/JS code work with the IR compiler may require some adjustments. Learn how to migrate existing Kotlin/JS projects to the IR compiler in the Kotlin/JS IR compiler migration guide.

为 IR 编译器创作具有向后兼容性的库

对于库维护者,希望提供与默认后端以及新的 IR 编译器后端的兼容性,那么可以使用编译器选择的设置,该设置可为两个后端创建构件, 从而保持与以下版本的兼容性。为现有的用户以及下一代 Kotlin 编译器提供支持。 可以使用 gradle.properties 文件中的 kotlin.js.compiler=both 设置打开这种所谓的 both 模式, 也可以将其设置为 build.gradle(.kts) 文件内 js 块内项目特有的选项之一:

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

When in both mode, the IR compiler backend and default compiler backend are both used when building a library from your sources (hence the name). This means that both klib files with Kotlin IR as well as jar files for the default compiler will be generated. When published under the same Maven coordinate, Gradle will automatically choose the right artifact depending on the use case – js for the old compiler, klib for the new one. This enables you to compile and publish your library for projects that are using either of the two compiler backends.