2.5 JavaScript版HelloWorld

在Kotlin 1.1中,开始支持JavaScript和协程是引人注目的亮点。本节我们简单介绍Kotlin代码编译转化为JavaScript的方法。

为了极简直观地感受这个过程,我们先在命令行REPL环境体验一下Kotlin源码被编译生成对应的JavaScript代码的过程。

首先,使用编辑器新建一个HelloWord.kt

1. fun helloWorld(){
2. println("Hello,World!")
3. }

命令行使用kotlinc-js编译

1. kotlinc-js -output HelloWorld.js HelloWorld.kt

运行完毕,我们会在当前目录下看到HelloWorld.js, 其内容如下

1. if (typeof kotlin === 'undefined') {
2. throw new Error("Error loading module 'HelloWorld'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'HelloWorld'.");
3. }
4. var HelloWorld = function (_, Kotlin) {
5. 'use strict';
6. var println = Kotlin.kotlin.io.println_s8jyv4$;
7. function helloWorld() {
8. println('Hello,World!');
9. }
10. _.helloWorld = helloWorld;
11. Kotlin.defineModule('HelloWorld', _);
12. return _;
13. }(typeof HelloWorld === 'undefined' ? {} : HelloWorld, kotlin);

我们看到,使用kotlinc-js 转换成的js代码依赖’kotlin’模块。这个模块是Kotlin支持JavaScript脚本的内部封装模块。也就是说,如果我们想要使用HelloWorld.js,先要引用kotlin.js。这个kotlin.js 在kotlin-stdlib-js-1.1.2.jar里面。 下面我们使用IDEA新建一个Kotlin(JavaScript)工程。在这个过程中,我们将会看到使用Kotlin来开发js的过程。

首先按照以下步骤新建工程

Kotlin极简教程

Kotlin极简教程

Kotlin极简教程

Kotlin极简教程

等待Gradle初始化工程完毕,我们将得到一个Gradle KotlinJS 工程,其目录如下

1. .
2. ├── build
3. │   └── kotlin-build
4. │       └── caches
5. │           └── version.txt
6. ├── build.gradle
7. ├── settings.gradle
8. └── src
9. ├── main
10. │   ├── java
11. │   ├── kotlin
12. │   └── resources
13. └── test
14. ├── java
15. ├── kotlin
16. └── resources

18. 12 directories, 3 files

其中,build.gradle配置文件为

1. group 'com.easy.kotlin'
2. version '1.0-SNAPSHOT'

4. buildscript {
5. ext.kotlin_version = '1.1.2'

7. repositories {
8. mavenCentral()
9. }
10. dependencies {
11. classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12. }
13. }

15. apply plugin: 'kotlin2js'

17. repositories {
18. mavenCentral()
19. }

21. dependencies {
22. compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
23. }

其中,apply plugin: ‘kotlin2js’ 是Gradle的kotlin编译成js的插件。org.jetbrains.kotlin:kotlin-stdlib-js是KotlinJS的运行库。

另外,我们需要再配置一下Kotlin代码编译成JS的编译规则,以及文件放置目录等属性,如下所示

1. build.doLast {
2. configurations.compile.each { File file ->
3. copy {
4. includeEmptyDirs = false

6. from zipTree(file.absolutePath)
7. into "${projectDir}/web"
8. include { fileTreeElement ->
9. def path = fileTreeElement.path
10. path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
11. }
12. }
13. }
14. }

16. compileKotlin2Js {
17. kotlinOptions.outputFile = "${projectDir}/web/js/app.js"
18. kotlinOptions.moduleKind = "plain" // plain (default),AMD,commonjs,umd
19. kotlinOptions.sourceMap = true
20. kotlinOptions.verbose = true
21. kotlinOptions.suppressWarnings = true
22. kotlinOptions.metaInfo = true
23. }

其中,kotlinOptions.moduleKind配置项是Kotlin代码编译成JavaScript代码的类型。 支持普通JS(plain),AMD(Asynchronous Module Definition,异步模块定义)、CommonJS和UMD(Universal Model Definition,通用模型定义)。

AMD通常在浏览器的客户端使用。AMD是异步加载模块,可用性和性能相对会好。

CommonJS是服务器端上使用的模块系统,通常用于nodejs。

UMD是想综合AMD、CommonJS这两种模型,同时支持它们在客户端或服务器端上使用。

我们这里为了极简化演示,直接采用了普通JS plain 类型。

除了输出的 JavaScript 文件,该插件默认会创建一个带二进制描述符的额外 JS 文件。 如果你是构建其他 Kotlin 模块可以依赖的可重用库,那么该文件是必需的,并且应该与转换结果一起分发。 其生成由 kotlinOptions.metaInfo 选项控制。

一切配置完毕,我们来写Kotlin代码App.kt

1. package com.easy.kotlin

3. fun helloWorld() {
4. println("Hello,World!")
5. }

然后,我们直接使用Gradle构建工程,如下图所示

Kotlin极简教程

控制台输出

1. 23:47:05: Executing external task 'build'...
2. Using a single directory for all classes from a source set. This behaviour has been deprecated and is scheduled to be removed in Gradle 5.0
3. at build_3e0ikl0qk0r006tvk0olcp2lu.run(/Users/jack/easykotlin/chapter2_hello_world_kotlin2js/build.gradle:15)
4. :compileJava NO-SOURCE
5. :compileKotlin2Js
6. :processResources NO-SOURCE
7. :classes
8. :jar
9. :assemble
10. :compileTestJava NO-SOURCE
11. :compileTestKotlin2Js NO-SOURCE
12. :processTestResources NO-SOURCE
13. :testClasses UP-TO-DATE
14. :test NO-SOURCE
15. :check UP-TO-DATE
16. :build

18. BUILD SUCCESSFUL in 2s
19. 3 actionable tasks: 3 executed
20. 23:47:08: External task execution finished 'build'.

此时,我们可以看到工程目录变为

1. .
2. ├── build
3. │   └── kotlin-build
4. │       └── caches
5. │           └── version.txt
6. ├── build.gradle
7. ├── settings.gradle
8. └── src
9. ├── main
10. │   ├── java
11. │   ├── kotlin
12. │   └── resources
13. └── test
14. ├── java
15. ├── kotlin
16. └── resources

18. 12 directories, 3 files
19. jack@jacks-MacBook-Air:~/easykotlin/chapter2_hello_world_kotlin2js$ tree ..
20. ├── build
21. │   ├── kotlin
22. │   │   └── sessions
23. │   ├── kotlin-build
24. │   │   └── caches
25. │   │       └── version.txt
26. │   ├── libs
27. │   │   └── chapter2_hello_world_kotlin2js-1.0-SNAPSHOT.jar
28. │   └── tmp
29. │       └── jar
30. │           └── MANIFEST.MF
31. ├── build.gradle
32. ├── settings.gradle
33. ├── src
34. │   ├── main
35. │   │   ├── java
36. │   │   ├── kotlin
37. │   │   │   └── com
38. │   │   │       └── easy
39. │   │   │           └── kotlin
40. │   │   │               └── App.kt
41. │   │   └── resources
42. │   └── test
43. │       ├── java
44. │       ├── kotlin
45. │       └── resources
46. └── web
47. ├── js
48. │   ├── app
49. │   │   └── com
50. │   │       └── easy
51. │   │           └── kotlin
52. │   │               └── kotlin.kjsm
53. │   ├── app.js
54. │   ├── app.js.map
55. │   └── app.meta.js
56. ├── kotlin.js
57. └── kotlin.meta.js

59. 26 directories, 14 files

这个web目录就是Kotlin代码通过kotlin-stdlib-js-1.1.2.jar编译的输出结果。

其中,app.js代码如下

1. if (typeof kotlin === 'undefined') {
2. throw new Error("Error loading module 'app'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'app'.");
3. }
4. var app = function (_, Kotlin) {
5. 'use strict';
6. var println = Kotlin.kotlin.io.println_s8jyv4$;
7. function helloWorld() {
8. println('Hello,World!');
9. }
10. var package$com = _.com || (_.com = {});
11. var package$easy = package$com.easy || (package$com.easy = {});
12. var package$kotlin = package$easy.kotlin || (package$easy.kotlin = {});
13. package$kotlin.helloWorld = helloWorld;
14. Kotlin.defineModule('app', _);
15. return _;
16. }(typeof app === 'undefined' ? {} : app, kotlin);

18. //@ sourceMappingURL=app.js.map

里面这段自动生成的代码显得有点绕

1. var package$com = _.com || (_.com = {});
2. var package$easy = package$com.easy || (package$com.easy = {});
3. var package$kotlin = package$easy.kotlin || (package$easy.kotlin = {});
4. package$kotlin.helloWorld = helloWorld;

6. Kotlin.defineModule('app', _);
7. return _;

简化之后的意思表达如下

1. _.com.easy.kotlin.helloWorld = helloWorld;

目的是建立Kotlin代码跟JavaScript代码的映射关系。这样我们在前端代码中调用

1. function helloWorld() {
2. println('Hello,World!');
3. }

这个函数时,只要这样调用即可

1. app.com.easy.kotlin.helloWorld()

下面我们来新建一个index.html页面,使用我们生成的app.js。代码如下

1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <title>KotlinJS</title>

7. </head>
8. <body>

10. <!-- 优先加载kotlin.js,再加载应用程序代码app.js-->
11. <script type="text/javascript" src="kotlin.js"></script>
12. <script type="text/javascript" src="jquery.js"></script>
13. <script type="text/javascript" src="js/app.js"></script>

15. <script>
16. var kotlinJS = app;
17. console.log(kotlinJS.com.easy.kotlin.helloWorld())
18. </script>
19. </body>
20. </html>

我们需要优先加载kotlin.js,再加载应用程序代码app.js。 当然,我们仍然可以像以前一样使用诸如jquery.js这样的库。

在浏览器中打开index.html

Kotlin极简教程

我们可以看到浏览器控制台输出

Kotlin极简教程 这个helloWorld() JavaScript函数

1. var println = Kotlin.kotlin.io.println_s8jyv4$;
2. function helloWorld() {
3. println('Hello,World!');
4. }

对应kotlin.js代码中的3755行处的代码:

1. BufferedOutputToConsoleLog.prototype.flush = function() {
2. console.log(this.buffer);
3. this.buffer = "";
4. };

本节工程源代码:<https://github.com/EasyKotlin/chapter2_hello_world_kotlin2js>

参考资料

1.<https://kotlinlang.org/docs/reference/compiler-plugins.html>

2.<http://kotlinlang.org/docs/tutorials/javascript/working-with-modules/working-with-modules.html>