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
  17. 12 directories, 3 files

其中,build.gradle配置文件为

  1. group 'com.easy.kotlin'
  2. version '1.0-SNAPSHOT'
  3. buildscript {
  4. ext.kotlin_version = '1.1.2'
  5. repositories {
  6. mavenCentral()
  7. }
  8. dependencies {
  9. classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
  10. }
  11. }
  12. apply plugin: 'kotlin2js'
  13. repositories {
  14. mavenCentral()
  15. }
  16. dependencies {
  17. compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
  18. }

其中,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
  5. from zipTree(file.absolutePath)
  6. into "${projectDir}/web"
  7. include { fileTreeElement ->
  8. def path = fileTreeElement.path
  9. path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
  10. }
  11. }
  12. }
  13. }
  14. compileKotlin2Js {
  15. kotlinOptions.outputFile = "${projectDir}/web/js/app.js"
  16. kotlinOptions.moduleKind = "plain" // plain (default),AMD,commonjs,umd
  17. kotlinOptions.sourceMap = true
  18. kotlinOptions.verbose = true
  19. kotlinOptions.suppressWarnings = true
  20. kotlinOptions.metaInfo = true
  21. }

其中,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
  2. fun helloWorld() {
  3. println("Hello,World!")
  4. }

然后,我们直接使用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
  17. BUILD SUCCESSFUL in 2s
  18. 3 actionable tasks: 3 executed
  19. 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
  17. 12 directories, 3 files
  18. jack@jacks-MacBook-Air:~/easykotlin/chapter2_hello_world_kotlin2js$ tree ..
  19. ├── build
  20. ├── kotlin
  21. └── sessions
  22. ├── kotlin-build
  23. └── caches
  24. └── version.txt
  25. ├── libs
  26. └── chapter2_hello_world_kotlin2js-1.0-SNAPSHOT.jar
  27. └── tmp
  28. └── jar
  29. └── MANIFEST.MF
  30. ├── build.gradle
  31. ├── settings.gradle
  32. ├── src
  33. ├── main
  34. ├── java
  35. ├── kotlin
  36. └── com
  37. └── easy
  38. └── kotlin
  39. └── App.kt
  40. └── resources
  41. └── test
  42. ├── java
  43. ├── kotlin
  44. └── resources
  45. └── web
  46. ├── js
  47. ├── app
  48. └── com
  49. └── easy
  50. └── kotlin
  51. └── kotlin.kjsm
  52. ├── app.js
  53. ├── app.js.map
  54. └── app.meta.js
  55. ├── kotlin.js
  56. └── kotlin.meta.js
  57. 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);
  17. //@ 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;
  5. Kotlin.defineModule('app', _);
  6. 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>
  6. </head>
  7. <body>
  8. <!-- 优先加载kotlin.js,再加载应用程序代码app.js-->
  9. <script type="text/javascript" src="kotlin.js"></script>
  10. <script type="text/javascript" src="jquery.js"></script>
  11. <script type="text/javascript" src="js/app.js"></script>
  12. <script>
  13. var kotlinJS = app;
  14. console.log(kotlinJS.com.easy.kotlin.helloWorld())
  15. </script>
  16. </body>
  17. </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