2.5 JavaScript版HelloWorld
在Kotlin 1.1中,开始支持JavaScript和协程是引人注目的亮点。本节我们简单介绍Kotlin代码编译转化为JavaScript的方法。
为了极简直观地感受这个过程,我们先在命令行REPL环境体验一下Kotlin源码被编译生成对应的JavaScript代码的过程。
首先,使用编辑器新建一个HelloWord.kt
fun helloWorld(){
println("Hello,World!")
}
命令行使用kotlinc-js
编译
kotlinc-js -output HelloWorld.js HelloWorld.kt
运行完毕,我们会在当前目录下看到HelloWorld.js
, 其内容如下
if (typeof kotlin === 'undefined') {
throw new Error("Error loading module 'HelloWorld'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'HelloWorld'.");
}
var HelloWorld = function (_, Kotlin) {
'use strict';
var println = Kotlin.kotlin.io.println_s8jyv4$;
function helloWorld() {
println('Hello,World!');
}
_.helloWorld = helloWorld;
Kotlin.defineModule('HelloWorld', _);
return _;
}(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的过程。
首先按照以下步骤新建工程
等待Gradle初始化工程完毕,我们将得到一个Gradle KotlinJS 工程,其目录如下
.
├── build
│ └── kotlin-build
│ └── caches
│ └── version.txt
├── build.gradle
├── settings.gradle
└── src
├── main
│ ├── java
│ ├── kotlin
│ └── resources
└── test
├── java
├── kotlin
└── resources
12 directories, 3 files
其中,build.gradle配置文件为
group 'com.easy.kotlin'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.1.2'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin2js'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}
其中,apply plugin: ‘kotlin2js’ 是Gradle的kotlin编译成js的插件。org.jetbrains.kotlin:kotlin-stdlib-js是KotlinJS的运行库。
另外,我们需要再配置一下Kotlin代码编译成JS的编译规则,以及文件放置目录等属性,如下所示
build.doLast {
configurations.compile.each { File file ->
copy {
includeEmptyDirs = false
from zipTree(file.absolutePath)
into "${projectDir}/web"
include { fileTreeElement ->
def path = fileTreeElement.path
path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
}
}
}
}
compileKotlin2Js {
kotlinOptions.outputFile = "${projectDir}/web/js/app.js"
kotlinOptions.moduleKind = "plain" // plain (default),AMD,commonjs,umd
kotlinOptions.sourceMap = true
kotlinOptions.verbose = true
kotlinOptions.suppressWarnings = true
kotlinOptions.metaInfo = true
}
其中,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
package com.easy.kotlin
fun helloWorld() {
println("Hello,World!")
}
然后,我们直接使用Gradle构建工程,如下图所示
控制台输出
23:47:05: Executing external task 'build'...
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
at build_3e0ikl0qk0r006tvk0olcp2lu.run(/Users/jack/easykotlin/chapter2_hello_world_kotlin2js/build.gradle:15)
:compileJava NO-SOURCE
:compileKotlin2Js
:processResources NO-SOURCE
:classes
:jar
:assemble
:compileTestJava NO-SOURCE
:compileTestKotlin2Js NO-SOURCE
:processTestResources NO-SOURCE
:testClasses UP-TO-DATE
:test NO-SOURCE
:check UP-TO-DATE
:build
BUILD SUCCESSFUL in 2s
3 actionable tasks: 3 executed
23:47:08: External task execution finished 'build'.
此时,我们可以看到工程目录变为
.
├── build
│ └── kotlin-build
│ └── caches
│ └── version.txt
├── build.gradle
├── settings.gradle
└── src
├── main
│ ├── java
│ ├── kotlin
│ └── resources
└── test
├── java
├── kotlin
└── resources
12 directories, 3 files
jack@jacks-MacBook-Air:~/easykotlin/chapter2_hello_world_kotlin2js$ tree ..
├── build
│ ├── kotlin
│ │ └── sessions
│ ├── kotlin-build
│ │ └── caches
│ │ └── version.txt
│ ├── libs
│ │ └── chapter2_hello_world_kotlin2js-1.0-SNAPSHOT.jar
│ └── tmp
│ └── jar
│ └── MANIFEST.MF
├── build.gradle
├── settings.gradle
├── src
│ ├── main
│ │ ├── java
│ │ ├── kotlin
│ │ │ └── com
│ │ │ └── easy
│ │ │ └── kotlin
│ │ │ └── App.kt
│ │ └── resources
│ └── test
│ ├── java
│ ├── kotlin
│ └── resources
└── web
├── js
│ ├── app
│ │ └── com
│ │ └── easy
│ │ └── kotlin
│ │ └── kotlin.kjsm
│ ├── app.js
│ ├── app.js.map
│ └── app.meta.js
├── kotlin.js
└── kotlin.meta.js
26 directories, 14 files
这个web目录就是Kotlin代码通过kotlin-stdlib-js-1.1.2.jar编译的输出结果。
其中,app.js代码如下
if (typeof kotlin === 'undefined') {
throw new Error("Error loading module 'app'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'app'.");
}
var app = function (_, Kotlin) {
'use strict';
var println = Kotlin.kotlin.io.println_s8jyv4$;
function helloWorld() {
println('Hello,World!');
}
var package$com = _.com || (_.com = {});
var package$easy = package$com.easy || (package$com.easy = {});
var package$kotlin = package$easy.kotlin || (package$easy.kotlin = {});
package$kotlin.helloWorld = helloWorld;
Kotlin.defineModule('app', _);
return _;
}(typeof app === 'undefined' ? {} : app, kotlin);
//@ sourceMappingURL=app.js.map
里面这段自动生成的代码显得有点绕
var package$com = _.com || (_.com = {});
var package$easy = package$com.easy || (package$com.easy = {});
var package$kotlin = package$easy.kotlin || (package$easy.kotlin = {});
package$kotlin.helloWorld = helloWorld;
Kotlin.defineModule('app', _);
return _;
简化之后的意思表达如下
_.com.easy.kotlin.helloWorld = helloWorld;
目的是建立Kotlin代码跟JavaScript代码的映射关系。这样我们在前端代码中调用
function helloWorld() {
println('Hello,World!');
}
这个函数时,只要这样调用即可
app.com.easy.kotlin.helloWorld()
下面我们来新建一个index.html页面,使用我们生成的app.js。代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>KotlinJS</title>
</head>
<body>
<!-- 优先加载kotlin.js,再加载应用程序代码app.js-->
<script type="text/javascript" src="kotlin.js"></script>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<script>
var kotlinJS = app;
console.log(kotlinJS.com.easy.kotlin.helloWorld())
</script>
</body>
</html>
我们需要优先加载kotlin.js,再加载应用程序代码app.js。
当然,我们仍然可以像以前一样使用诸如jquery.js这样的库。
在浏览器中打开index.html
我们可以看到浏览器控制台输出
这个helloWorld() JavaScript函数
var println = Kotlin.kotlin.io.println_s8jyv4$;
function helloWorld() {
println('Hello,World!');
}
对应kotlin.js代码中的3755行处的代码:
BufferedOutputToConsoleLog.prototype.flush = function() {
console.log(this.buffer);
this.buffer = "";
};
本节工程源代码: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