23 JavaScript 后端
第 23 章:JavaScript 后端
23.1 基本编译
# 编译为 JavaScript
nim js -o:output.js main.nim
# 优化
nim js -d:release -o:output.js main.nim
# main.nim
echo "Hello from Nim (compiled to JS)!"
在浏览器中使用:
<!DOCTYPE html>
<html>
<head><title>Nim JS</title></head>
<body>
<script src="output.js"></script>
</body>
</html>
在 Node.js 中使用:
node output.js
23.2 条件编译
# 根据后端选择不同实现
when defined(js):
proc alert(msg: string) {.importjs: "alert(#)".}
proc fetch(url: string): Future[JsObject] {.importjs: "fetch(#)".}
echo "Running in JavaScript!"
else:
echo "Running natively!"
# 检查运行环境
when defined(js):
when defined(nodejs):
echo "Running in Node.js"
else:
echo "Running in browser"
23.3 JavaScript 互操作
23.3.1 importjs
# 导入 JavaScript 函数
proc consoleLog(msg: cstring) {.importjs: "console.log(#)".}
proc setTimeout(callback: proc(), ms: int): int {.importjs: "setTimeout(@)".}
proc parseInt(s: cstring): int {.importjs: "parseInt(#)".}
consoleLog("Hello!")
discard setTimeout(proc() = echo "Delayed!", 1000)
echo parseInt("42")
23.3.2 JsObject
import std/jsffi
# 创建 JavaScript 对象
let obj = newJsObject()
obj["name"] = "Nim".toJs
obj["version"] = 2.toJs
# 访问属性
echo obj["name"].to(cstring)
echo obj["version"].to(int)
# 创建数组
let arr = newJsArray()
arr.push(1.toJs)
arr.push("hello".toJs)
echo arr.length
# JSON 操作
let json = JSON.parse("""{"key": "value"}""")
echo json["key"].to(cstring)
23.3.3 回调与异步
import std/[jsffi, asyncjs]
# 异步函数
proc fetchData(url: string): Future[JsObject] {.async.} =
let response = await fetch(url.toJs)
let data = await response.json()
return data
# 使用
proc main() {.async.} =
let data = await fetchData("https://api.example.com/data")
console.log(data)
discard main()
23.4 DOM 操作
23.4.1 基本 DOM
import std/dom
# 获取元素
let el = document.getElementById("app")
let items = document.getElementsByClassName("item")
let first = document.querySelector(".container")
# 修改元素
el.innerHTML = "<h1>Hello from Nim!</h1>".cstring
el.setAttribute("class", "active".cstring)
el.style.color = "red".cstring
# 创建元素
let div = document.createElement("div")
div.innerHTML = "New element".cstring
document.body.appendChild(div)
# 事件处理
el.addEventListener("click", proc(event: Event) =
echo "Clicked!"
)
23.4.2 事件处理
import std/dom
proc onButtonClick(event: Event) =
let input = document.getElementById("name-input")
let name = cast[InputElement](input).value
let output = document.getElementById("output")
output.innerHTML = ("Hello, " & $name & "!").cstring
# 绑定事件
let button = document.getElementById("submit-btn")
button.addEventListener("click", onButtonClick)
# 表单事件
let form = document.getElementById("my-form")
form.addEventListener("submit", proc(event: Event) =
event.preventDefault()
echo "Form submitted!"
)
23.5 Node.js 模块
23.5.1 文件系统
import std/jsffi
type FSModule = JsObject
let fs: FSModule = require("fs")
# 读取文件
proc readFileSync(path: cstring): cstring {.importjs: "fs.readFileSync(#, 'utf8')".}
proc writeFileSync(path, data: cstring) {.importjs: "fs.writeFileSync(#, #)".}
let content = readFileSync("data.txt".cstring)
echo content
writeFileSync("output.txt".cstring, "Hello from Nim!".cstring)
23.5.2 HTTP 服务器
import std/jsffi
type HttpModule = JsObject
let http: HttpModule = require("http")
proc createServer(callback: proc(req, res: JsObject)): JsObject =
http.createServer(callback)
let server = createServer(proc(req, res: JsObject) =
res.writeHead(200.toJs, %*{"Content-Type": "text/plain"})
res.end("Hello from Nim Node.js server!".toJs)
)
server.listen(3000.toJs, proc() =
echo "Server running on port 3000"
)
23.6 前端框架集成
23.6.1 Karax(Nim 的 Virtual DOM 库)
# nimble install karax
import karax / [karaxdsl, vdom, kdom]
var count = 0
proc render(): VNode =
buildHtml(tdiv):
h1: text "Counter: " & $count
button(onclick = proc(ev: Event, tgt: VNode) =
inc count
):
text "Increment"
button(onclick = proc(ev: Event, tgt: VNode) =
count = 0
):
text "Reset"
setRenderer render
编译:
nim js -d:release -o:app.js frontend.nim
<!DOCTYPE html>
<html>
<head>
<script src="app.js"></script>
</head>
<body id="ROOT">
</body>
</html>
23.6.2 Karax 路由
import karax / [karaxdsl, vdom, kdom, kajax]
var currentPage = ""
proc renderHome(): VNode =
buildHtml(tdiv):
h1: text "Home Page"
a(href = "#/about"): text "About"
proc renderAbout(): VNode =
buildHtml(tdiv):
h1: text "About Page"
a(href = "#/"): text "Home"
proc render(): VNode =
let hash = $window.location.hash
case hash
of "#/about": renderAbout()
else: renderHome()
setRenderer render
23.7 实战示例
🏢 场景:全栈 Nim 应用
后端(Jester):
# backend.nim - 编译为本地二进制
import jester, json
var todos: seq[JsonNode] = @[]
routes:
get "/api/todos":
resp %*{"data": todos}
post "/api/todos":
let todo = parseJson(request.body)
todos.add(todo)
resp Http201, %*{"created": todo}
前端(Karax):
# frontend.nim - 编译为 JavaScript
import karax / [karaxdsl, vdom, kdom, kajax]
import std/jsffi
var todos: seq[cstring] = @[]
var newTodo = ""
proc addTodo() =
if newTodo.len > 0:
todos.add(newTodo)
newTodo = ""
proc render(): VNode =
buildHtml(tdiv):
h1: text "Todo App"
input(`type` = "text",
value = newTodo,
oninput = proc(ev: Event, tgt: VNode) =
newTodo = $tgt.value
)
button(onclick = proc(ev: Event, tgt: VNode) =
addTodo()
):
text "Add"
ul:
for todo in todos:
li: text todo
setRenderer render
🏢 场景:数据可视化
import std/dom
type ChartConfig = JsObject
proc createChart(canvas: Element, config: ChartConfig) {.importjs: "new Chart(#, #)".}
# 初始化图表
let canvas = document.getElementById("myChart")
let config = %*{
"type": "bar",
"data": {
"labels": ["Jan", "Feb", "Mar", "Apr", "May"],
"datasets": [{
"label": "Sales",
"data": [12, 19, 3, 5, 2],
"backgroundColor": "rgba(54, 162, 235, 0.5)"
}]
}
}
createChart(canvas, config)
本章小结
| 功能 | 语法 | 模块 |
|---|---|---|
| 编译 JS | nim js | — |
| JS 导入 | {.importjs.} | — |
| DOM 操作 | dom 模块 | std/dom |
| 异步 | asyncjs | std/asyncjs |
| JS 对象 | JsObject | std/jsffi |
| 虚拟 DOM | Karax | 第三方 |
练习
- 使用 Nim + Karax 创建一个单页应用
- 为 Node.js 编写一个命令行工具
- 创建一个前后端都用 Nim 编写的应用
扩展阅读
← 上一章:C 后端深入 | 下一章:最佳实践 →