7.2.6 JavaScript 模块

原文链接: https://kotlinlang.org/docs/js-modules.html

7.2.6 JavaScript 模块

你可以把 Kotlin 项目编译为适用于各种流行模块系统的 JavaScript 模块。目前我们支持以下 JavaScript 模块配置:

  • ES Modules,即 JavaScript 中声明模块的标准方式(使用 JavaScript 的 import/export 语法)。当 target 设为 es2015 时默认使用它。
  • 统一模块定义 (UMD),它同时兼容 AMD 和 CommonJS。UMD 模块也可以在不被导入、或不存在模块系统的情况下执行。这是 browser 和 nodejs 目标的默认选项。
  • 异步模块定义 (AMD),RequireJS 库尤其使用它。
  • CommonJS,被 Node.js/npm 广泛使用(require 函数和 module.exports 对象)。
  • Plain。不为任何模块系统编译。你可以按名称在全局作用域中访问模块。

浏览器目标

如果你打算在 Web 浏览器环境中运行代码,并且想使用 UMD 之外的模块系统,可以在 webpackTask 配置块中指定所需的模块类型。例如,要切换到 CommonJS,请使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kotlin {
    js {
        browser {
            webpackTask {
                output.libraryTarget = "commonjs2"
            }
        }
        binaries.executable()
    }
}

Webpack 提供两种不同风格的 CommonJS:commonjs 和 commonjs2,它们会影响你的声明对外可用方式。大多数情况下,你可能需要 commonjs2,它会给生成的库加上 module.exports 语法。或者,你也可以选择 commonjs,它严格遵循 CommonJS 规范。要了解 commonjs 与 commonjs2 的区别,请参阅 Webpack 仓库。

JavaScript 库与 Node.js 文件

如果你要创建供 JavaScript 或 Node.js 环境使用的库,并且想使用其他模块系统,做法会稍有不同。

选择目标模块系统

要选择目标模块系统,请在 Gradle 构建脚本中设置 moduleKind 编译器选项:

Kotlin

1
2
3
tasks.withType<org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink> {
    compilerOptions.moduleKind.set(org.jetbrains.kotlin.gradle.dsl.JsModuleKind.MODULE_COMMONJS)
}

Groovy

1
compileKotlinJs.compilerOptions.moduleKind = org.jetbrains.kotlin.gradle.dsl.JsModuleKind.MODULE_COMMONJS

可用的取值有:umd(默认)、es、commonjs、amd、plain。

注意: 这与调整 webpackTask.output.libraryTarget 不同。库目标改变的是_由 webpack 生成_的输出(此时你的代码已经被编译完成)。compilerOptions.moduleKind 改变的是_由 Kotlin 编译器生成_的输出。

在 Kotlin Gradle DSL 中,还有设置 CommonJS 和 ESM 模块类型的快捷方式:

1
2
3
4
5
6
7
8
kotlin {
    js {
        useCommonJs()
        // 或者
        useEsModules()
        // ...
    }
}

@JsModule 注解

要告诉 Kotlin 某个 external 类、包、函数或属性是一个 JavaScript 模块,你可以使用 @JsModule 注解。假设你有如下名为 “hello” 的 CommonJS 模块:

1
module.exports.sayHello = function (name) { alert("Hello, " + name); }

你在 Kotlin 中应这样声明它:

1
2
@JsModule("hello")
external fun sayHello(name: String)

把 @JsModule 应用于包

有些 JavaScript 库导出的是包(命名空间),而不是函数和类。用 JavaScript 的话说,它是一个对象,其成员是类、函数和属性。把这些包作为 Kotlin 对象导入往往看起来不自然。编译器可以使用以下记法把导入的 JavaScript 包映射到 Kotlin 包:

1
2
3
4
5
6
7
@file:JsModule("extModule")

package ext.jspackage.name

external fun foo()

external class C

其中对应的 JavaScript 模块是这样声明的:

1
2
3
4
module.exports = {
  foo: { /* 一些代码 */ },
  C: { /* 一些代码 */ }
}

用 @file:JsModule 注解标记的文件不能声明非 external 成员。下面的例子会产生编译期错误:

1
2
3
4
5
6
7
@file:JsModule("extModule")

package ext.jspackage.name

external fun foo()

fun bar() = "!" + foo() + "!" // 这里报错

导入更深的包层级

在上一个例子中,JavaScript 模块只导出一个包。不过有些 JavaScript 库会从同一个模块中导出多个包。Kotlin 也支持这种情况,不过你必须为导入的每个包声明一个新的 .kt 文件。

例如,让我们把例子改得稍微复杂一点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
module.exports = {
  mylib: {
    pkg1: {
      foo: function () { /* 一些代码 */ },
      bar: function () { /* 一些代码 */ }
    },
    pkg2: {
      baz: function () { /* 一些代码 */ }
    }
  }
}

要在 Kotlin 中导入这个模块,你必须写两个 Kotlin 源文件:

1
2
3
4
5
6
7
8
@file:JsModule("extModule")
@file:JsQualifier("mylib.pkg1")

package extlib.pkg1

external fun foo()

external fun bar()

以及

1
2
3
4
5
6
@file:JsModule("extModule")
@file:JsQualifier("mylib.pkg2")

package extlib.pkg2

external fun baz()

@JsNonModule 注解

当某个声明被标记为 @JsModule 时,如果你没有把它编译为 JavaScript 模块,就不能从 Kotlin 代码中使用它。通常开发者会同时以 JavaScript 模块和可下载 .js 文件两种形式分发库,后者可以复制到项目的静态资源中并通过 <script> 标签引入。要告诉 Kotlin 可以在非模块环境中使用 @JsModule 声明,请添加 @JsNonModule 注解。例如,考虑以下 JavaScript 代码:

1
2
3
4
5
function topLevelSayHello (name) { alert("Hello, " + name); }

if (module && module.exports) {
  module.exports = topLevelSayHello;
}

你可以在 Kotlin 中这样描述它:

1
2
3
4
@JsModule("hello")
@JsNonModule
@JsName("topLevelSayHello")
external fun sayHello(name: String)

Kotlin 标准库使用的模块系统

Kotlin 以单个文件的形式分发 Kotlin/JS 标准库,它本身被编译为 UMD 模块,因此你可以把它与上述任何模块系统一起使用。对于大多数 Kotlin/JS 用例,推荐对 kotlin-stdlib-js 使用 Gradle 依赖,它也在 NPM 上以 kotlin 包的形式提供。