Kotlin x Nodejs
很早就有人想讓我寫一個 kotlin x nodejs 的開發教程,利用 kotlin 可以編譯為 js 的特性,理論上是可以很好的與 nodejs 配合的,而事實上也是如此。本篇即是講述如何在 kotlin 的體系下玩轉 nodejs,並最終實現一個簡單的服務端應用。
首先我們要準備好 nodejs 的環境,採用以下命令來安裝,如果已經裝好了,可以跳過這一步:
for Mac $ brew install nodejs for Ubuntu $ apt install nodejs
接著建立一個 Kotlin(Javascript) 專案,需要注意的是,如果選擇使用本地 gradle 來編譯,那麼 gradle 的版本要大於 4.4,我自己的環境採用的版本是 4.6。
然後對 build.gradle
檔案進行一定的修改,加入 compileKotlin2Js
的配置項:
compileKotlin2Js { kotlinOptions.outputFile = "${projectDir}/web/ktnode.js" kotlinOptions.moduleKind = "commonjs" kotlinOptions.sourceMap = true }
需要特別注意的是,對於純前端的 KotlinJs 專案, moduleKind
應當被配置為 umd
,而對於後端(可以帶前端)的專案,應當配置為 commonjs
,我們此處開發的是後端專案。
另外,還需要再對 gradle 進行一些改造,以正常的 web 專案部署的目錄結構來輸出編譯結果,這樣我們可以直接用熱更新的方式來部署專案,方便除錯。
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/")) } } } copy { includeEmptyDirs = false from new File("src/main/resources") into "web" } }
準備好之後,我們可以把這個專案轉為使用 npm 管理,也就是可以直接拿來運行了,在專案的根目錄下執行命令:
$ npm init
這個命令將生成 package.json
檔案,同樣的,我們需要修改這個檔案,以完成執行和依賴的配置:
{ "name": "ktnode", "version": "1.0.0", "description": "", "scripts": { "start": "node ./web/ktnode.js" }, "author": "rarnu", "license": "GPLv3", "dependencies": { "kotlin": "^1.3.20", "express": "^4.15.4", "mongoose": "^4.11.7" } }
這裡需要注意的是,在 scripts
內的 start
選項,將指出從何開始執行 node 專案,此處指向我們的編譯目標,即 ktnode.js
,你也可以把這個名稱修改為你自己專案的。
接著是依賴,要使用 Kotlin 來開發,那麼也就必須在 nodejs 環境內安裝 Kotlin 的包,除此之外,其他都是 nodejs 的常規操作了,執行 install
命令來完成依賴的安裝:
$ npm install
現在可以嘗試寫一個檔案,來跑起第一個 Kotlin x Nodejs 伺服器了:
package com.rarnu.ktnode import kotlin.js.json external fun require(module: String): dynamic external val process: dynamic val express = require("express") app = express() fun main(args: Array<String>) { app.get("/") { _, resp -> resp.type("text/plain") resp.send("Hello Kotlin x Nodejs") } app.listen(port) { println("Listening on port $port") } }
然後編譯,執行之:
$ gradle build $ npm start
然後就可以在瀏覽器開啟 http://localhost:8888
並看到這個頁面啦。

第一個頁面
好了,有了這個基礎,我們可以走得更遠,下面來說一下如果要返回靜態頁面以及帶入靜態的 js 等檔案要怎麼操作。
假設我們已經在 Kotlin 專案的 resources
目錄下放置了 index.html
和 jquery.js
,html 的程式碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript"> function callapi() { $.ajax({ url :"/api", dataType: "text", data: { name: "rarnu" }, success: function (data) { $("#result")[0].innerHTML = data; } }); } </script> </head> <body> <p><input type="button" onclick="callapi();" value="CallAPI"></p> <div id="result"></div> </body> </html>
然後我們需要設定靜態頁面路徑,只有設定了靜態頁面路徑,這個頁面以及它載入的 js 才會正常展示:
var path = require("path") app.use(express.static(path.join(__dirname, "")))
同樣的,你可以嘗試不設定靜態頁面路徑,然後看看會發生什麼。
好了,暫時不去管那個內建的 js 函式,現在我們再來看看如何動態的返回頁面,這種技巧通常用於程式碼模板,從模板來生成一個頁面並返回給使用者。此時就需要從 resources
內載入頁面的模板檔案,然後將其返回給使用者:
app.get("/sample") { _, resp -> val fs = require("fs") fs.readFile("index.html", "utf8") { _, data -> resp.type("text/html") resp.send(data) } }
接著,要講一下如何來接受請求引數,在 nodejs 裡,有三種接引數的方式,如下表所示:
程式碼 | 描述 | 示例 |
---|---|---|
req.params | 獲取路由引數 | 從 /index 請求中獲取 index |
req.query | 獲取查詢字串中的引數 | 從 /index?id=1 中獲取 id |
req.body | 獲取經過 URLEncoded 的 body 引數 | 從 /index 中獲取 post 上來的 id |
在 Kotlin 程式碼中,可以輕易的完成對於引數的獲取:
app.get("/sample") { req, resp -> val id = req.query.id resp.type("text/plain") resp.send(id) }
要獲取 post 方法傳來的引數,需要額外的進行配置:
val bodyParser = require("body-parser") app.use(bodyParser.urlencoded(extended = false)) app.post("/sample") { val id = req.body.id ... ... }
在 Kotlin 中,包含了許多方便開發的庫,如果要返回的內容是一個 json 串,可以用一些簡單的方法來進行拼裝:
import kotlin.js.json ... ... app.get("/sample") { _, resp -> resp.type("text/json") resp.send(json("result" to 0, "message" to "success")) }
這段程式碼將返回以下 json 串:
{"result":0,"message":"success"}
還記得上面埋了個伏筆不,html 裡的那個 callapi
函式,現在可以利用上面的內容,把這個函式寫出來了:
app.get("/api") { req, resp -> val name = req.query?.name ?: "" resp.type("text/json") resp.send(json("result" to 0, "message" to "Hello World: $name")) }
再回過頭來看一些能夠方便開發的東西,比如說熱部署,你一定會發現,在使用 npm start
後,不論怎麼編輯頁面或程式碼,都不能立即在瀏覽器上看到效果。如果想立即看到效果要怎麼辦呢?
$ sudo npm install -g supervisor $ supervisor ktnode.js
這樣就可以搞定了, supervisor
會監控 ktnode.js
所在的目錄,在目錄內容有變化時,可以即時重新整理,這樣就可以瀏覽器內實時預覽了。
最後,在這個 Demo 程式的基礎上,其實還是可以做很多抽象的,比如一個好的基礎庫可以讓你事半功倍,比如這樣:
package com.rarnu.ktnode import kotlin.js.json fun main(args: Array<String>) { initServer() routing("/index") { req, resp -> resp.type("text/html") loadRes("index.html") { resp.send(it) } } routing("/api") { req, resp -> val name = req.query?.name ?: "" resp.type("text/json") resp.send(json("result" to 0, "message" to "Hello World: $name")) } startListen() }
寫成這樣就會比較舒服,專心的處理請求響應的邏輯而無需關心其他內容,這需要在實際開發中多加積累。
我在 Github 上放了一個簡單的 Demo,希望能起到拋磚引玉的作用。 點此去玩 Demo