使用教程

从安装到把脚本打包成独立可执行文件,大约需要十分钟。所有示例都实际运行验证过。

1安装 Gox

方式一:npm 安装(推荐)

@goxjs/goxjs 包内置 Windows / Linux / macOS × x64 / arm64(windows-arm64 除外共 5 个平台)的预编译二进制,无需 Go 环境:

npm i -g @goxjs/goxjs   # 安装后得到 goxjs 命令
goxjs              # 进入 REPL
goxjs app.js       # 运行脚本
npx goxjs app.js   # 或者不全局安装,直接跑

方式二:从源码构建

环境要求 Go 1.26+:

git clone https://github.com/14752222/Gox.git
cd Gox
go build           # Windows 下生成 Gox.exe,类 Unix 下生成 Gox
./Gox app.js       # 之后用 ./Gox 替代教程中的 goxjs
提示

教程统一用 goxjs 演示命令。如果你是源码构建,请自行替换成 ./Gox(Windows 为 Gox.exe),用法完全相同。

2REPL 与运行脚本

交互式 REPL

不带参数启动就是交互式环境,适合随手试验:

$ goxjs
Gox REPL (ES6 subset, no var)
Type :exit to quit, :help for help

> let x = 10
> let y = 20
> x + y
  30
> [1, 2, 3].map(v => v * 2)
  [2, 4, 6]

REPL 命令::help 查看帮助、:clear 重置环境、:exit 退出。

运行脚本

goxjs example.js

脚本执行完毕后会回显最后一个顶层表达式的值(undefined 除外),并等待所有定时器与异步回调执行完再退出:

// hello.js
let name = "Gox"
console.log(`Hello, ${name}!`)
setTimeout(() => console.log("tick"), 10)
"bye"
$ goxjs hello.js
Hello, Gox!
bye
tick

3语言基础

Gox 实现 ES6+ 语言子集。声明变量用 let / const —— 不支持 var(刻意的设计取舍,见常见问题)。

// 变量与常量
let x = 10
const PI = 3.14

// 箭头函数、闭包
const add = (a, b) => a + b
const counter = () => { let n = 0; return () => ++n }
let next = counter()

// class
class Point {
  constructor(x, y) { this.x = x; this.y = y }
  norm() { return Math.hypot(this.x, this.y) }
}
console.log(new Point(3, 4).norm())        // → 5

// 解构、剩余参数、默认参数、展开
let [a, b, ...rest] = [1, 2, 3, 4]
function greet(name = "world", ...tags) { return `hi ${name} ${tags}` }
let merged = [...[1, 2], ...[3, 4]]        // [1, 2, 3, 4]
let { host, port } = { host: "127.0.0.1", port: 8080 }

// 模板字符串、for...of、try/catch
for (const v of [1, 2, 3]) console.log(`v = ${v}`)
try {
  throw new Error("boom")
} catch (e) {
  console.log(e.message)                   // → boom
}

// 可选链与空值合并
let cfg = { db: { host: "localhost" } }
let host2 = cfg?.db?.host ?? "127.0.0.1"
注意

JSX 语法(<text>...</text>)也是语言子集的一部分,编译期会被降级为 h(tag, props, ...children) 调用,详见 GUI 桌面应用 一节。

4ES 模块

脚本通过 import / export 组织,相对路径以当前文件为基准解析。lib.js:

export const PI = 3.14
export function double(x) { return x * 2 }

main.js:

import { PI, double } from "./lib.js"

setTimeout(() => console.log("tick"), 10)
console.log(double(PI))
$ goxjs main.js
6.28
tick

也支持默认导出与动态 import():

import sq, { PI } from "./math_utils.js"     // 默认导出 + 命名导出

import("./math_utils.js").then(m => console.log(m.PI))   // 动态加载

5异步与事件循环

async / await 与 Promise 开箱即用。内置工具 delay(ms, value) 返回一个在 ms 毫秒后 resolve 为 value 的 Promise,写示例和测试都很方便:

async function main() {
  let v = await delay(10, "timer done")
  console.log(v)
}
main()
Promise { <pending> }     ← 顶层回显:main() 调用的返回值
timer done                ← 定时器到期后,await 继续执行

定时器家族:

API说明
setTimeout(fn, ms)延迟执行,返回定时器 id;clearTimeout(id) 取消
setInterval(fn, ms)周期执行;clearInterval(id) 取消
setStrictTimeout严格定时器变体,精度可控,适合对时间敏感的场景
requestIdleCallback(fn)空闲时机回调,不阻塞关键路径
退出语义

脚本不会在有未完成的定时器或异步回调时立刻退出 —— 事件循环会驱动它们全部执行完,这也是上面 main() 能打印结果的原因。

6内置对象速览

全局环境内置了现代 JS 的常用对象,无需 import:

类别对象
基础Object Array String Number Boolean Symbol BigInt Math JSON RegExp
集合Map Set WeakMap WeakSet
元编程Proxy Reflect Iterator
异步Promise
二进制ArrayBuffer DataView 与 TypedArray 家族
弱引用WeakRef FinalizationRegistry
错误完整错误类型族(TypeError / RangeError / SyntaxError / ReferenceError …)
日期时间Temporal —— 取代 Date 的现代日期时间 API
console.log([1, 2, 3].filter(v => v > 1).reduce((a, b) => a + b))  // → 5
console.log(Math.hypot(3, 4))                                       // → 5
console.log(JSON.stringify({ ok: true }, null, 2))

let m = new Map([["a", 1]])
m.set("b", 2)
console.log([...m.keys()])                                          // → [a, b]

7文件与系统(fs / path / process)

fspathprocess 是全局对象,风格与 Node.js 对齐,无需 import。每个 fs API 都有同步(xxxSync)与回调式异步两套:

// 同步风格
fs.writeFileSync("hello.txt", "hello gox")
console.log(fs.readFileSync("hello.txt", "utf-8"))    // → hello gox
fs.appendFileSync("hello.txt", "!")
console.log(fs.existsSync("hello.txt"))               // → true

let st = fs.statSync("hello.txt")
console.log(st.size, st.isFile(), st.isDirectory())   // 10 true false

for (const f of fs.readdirSync(".")) console.log(f)
fs.mkdirSync("logs", { recursive: true })

// 回调式异步(错误优先,Node 语义)
fs.readFile("hello.txt", "utf-8", (err, data) => {
  if (err) throw err
  console.log(data)
})

// 也支持 Promise 化的 await 用法
const text = await fs.readFile("hello.txt", "utf-8")
模块常用 API
fs readFileSync/readFilewriteFileSync/writeFileappendFileSyncreadBytesSync(返回字节数组)、existsSyncstatSync/statreaddirSync/readdirmkdirSync/mkdirrenameSynccopyFileSyncrmSyncunlinkSync/unlinkrmdirSync
path joinbasenamedirnameextnameresolveisAbsolutesepdelimiter
process argv(命令行参数)、env(环境变量)、cwd()chdir()exit(code)platform(windows/linux/darwin)、pid
let cfgPath = path.join(process.cwd(), "app.json")
console.log(path.extname("archive.tar.gz"))     // → .gz
console.log(process.platform, process.pid)
console.log(process.argv)                       // [goxjs, app.js, ...]
编码参数

fs.readFileSync(path) 默认按 UTF-8 返回字符串;传 'base64' 则返回 base64 编码;需要原始字节时用 fs.readBytesSync(path)

8网络请求与 HTTP 服务

fetch:全局 HTTP 客户端

const r = await fetch("https://httpbin.org/get?name=zed")
console.log(r.status, r.ok)             // 200 true
console.log(await r.text())

// POST 请求
const r2 = await fetch("https://httpbin.org/post", {
  method: "POST",
  body: JSON.stringify({ hello: "gox" }),
  headers: { "Content-Type": "application/json" }
})
const data = await r2.json()

http.createServer:HTTP 服务端

http 同样是全局对象,支持 get / request 客户端方法与 createServer 服务端:

// server.js
const server = http.createServer(function (req, res) {
  if (req.path === "/json") {
    res.writeHead(200, { "Content-Type": "application/json" })
    res.end(JSON.stringify({ msg: "hi " + req.query.name, method: req.method }))
  } else {
    res.end("hello world")
  }
})

server.listen(8080, function () {
  console.log("listening on http://127.0.0.1:8080")
})
$ goxjs server.js
listening on http://127.0.0.1:8080

# 另开一个终端验证
$ curl "http://127.0.0.1:8080/json?name=zed"
{"msg":"hi zed","method":"GET"}
并发模型

网络回调跑在 Go 的 goroutine 上,捕获后被调度回 VM 单线程执行 —— 你的 JS 代码永远单线程,无需加锁。

9响应式编程

Gox 内置两套响应式原语,均为全局函数。

obs:GetX 风格(Dart GetX 语义)

let count = obs(0)
ever(count, v => console.log("count =", v))   // 订阅时立即以当前值回调一次

count.value = 1
count.value = 2
count.value = 2   // 值未变化,不触发通知
count = 0
count = 1
count = 2
API说明
obs(value)创建可观察值,读写走 .value
computed(fn)由其他 obs 派生的计算值,依赖变化时自动重算
ever(obs, fn)持续订阅,每次变化都回调
once(obs, fn)只在下一次变化时回调一次

signals:SolidJS 风格(gx/solid 模块)

import { createSignal, createEffect, createMemo } from "gx/solid"

const [count, setCount] = createSignal(0)

createEffect(() => console.log("count is", count()))   // 立即执行一次
setCount(5)                                            // → count is 5

const doubled = createMemo(() => count() * 2)
setCount(10)
console.log(doubled())                                 // → 20
注意

obs 是全局函数可直接用;createSignal 等需要从 "gx/solid" 导入 —— 这是 Gox 内置模块(优先于文件系统解析),也是下一节 GUI 响应式更新的基石。

10GUI 桌面应用

gx/gfx 模块 + JSX 语法提供声明式 UI:渲染器是纯 Go 软件光栅化(无 cgo、无动态库依赖),flex 风格布局,脏矩形局部重绘。一个 16 行的计数器:

// counter.js
import { createSignal } from "gx/solid"
import { h, render } from "gx/gfx"

const [count, setCount] = createSignal(0)

render(
  <window title="Counter" width={400} height={300}>
    <column gap={8} padding={16}>
      <text font={20}>{() => `count: ${count()}`}</text>
      <button onClick={() => setCount(c => c + 1)}>加一</button>
    </column>
  </window>
)
goxjs counter.js          # 直接运行,弹出 400x300 窗口

它是怎么工作的

内置元素与属性

共 23 个脚本可写的内置元素(select-popup / menu-item 等由 Go 侧构造的内部标签不计), 按用途分七类。下面是速查表,每个组件的完整参数与示例见 组件参考

类别元素
布局容器 <column><row><scroll><separator><spacer>
表单控件 <button><checkbox><radio><switch><input><textarea><select><slider>
内容展示 <text>(wrap / ellipsis)、<image><progress>
反馈与弹层 <dialog><toast>,以及 gx/dialog 的原生 alert / confirm / openFile
导航与菜单 <menubar><menu><menuitem>(全局 shortcut)、openContextMenu(x, y, items)
媒体与自绘 <canvas>(onDraw(ctx) + 7 个绘制原语)
窗口 <window>(仅作 render() 的根元素,可多次调用开多窗口)

常用属性:

属性说明
width / height / margin整数像素;不写则由内容决定
gap / padding / flexGrow间距、内边距、主轴富余分配
alignItems / justifyContent交叉轴 / 主轴对齐
background / border / color / font样式;颜色支持 alpha(#rrggbbaa / rgba())
value / checked / open受控组件的状态,全部由 JS 的 signal 驱动
disabled沿祖先链继承;子树不响应事件也不参与焦点
zIndex / position / escapeClipping层叠、绝对定位、逃逸父盒裁剪
transition / opacity过渡动画(可动属性:width/height/left/top/opacity)

事件回调:onClickonMouseMoveonWheelonContextMenuonKeyDown / onKeyUponFocus / onBluronInput / onChangeonCloseonDrawonResize(窗口级:挂布局根,载荷 {width, height})。 事件沿祖先链"找第一个处理器"即停,不冒泡;其中 onClick 回调没有参数。

受控语义

所有输入类组件都不存自己的状态 —— value 决定显示什么,操作只派发 onInput / onChange。忘记把值写回 signal,输入框就会"打字没反应"、 滑块会"弹回原位"。这与 DOM 受控组件一致。

平台支持

窗口后端:Windows(纯 syscall win32)与 Linux(X11,Wayland 下走 XWayland)。macOS 的 GUI 后端尚未实现 —— CLI 脚本不受影响。 输入法(IME)与原生对话框目前仅 Windows 后端提供,其余平台会安全降级。

完整示例见仓库 testdata/ 目录:计数器 counter_demo.js、 表单 form_demo.js、菜单 menu_demo.js、 画布 canvas_demo.js、滑动条 slider_demo.js、 多窗口 multiwindow_demo.js 等 20 余个,每个都可直接 goxjs testdata/xxx_demo.js 运行。

11打包成独立可执行文件

jsbuild(仓库 packager 目录)把入口脚本及其相对 import 的模块嵌入一个生成的 Go 工程,编译成自带完整运行时的单文件程序 —— 目标机器不需要装 Gox、Go 或任何运行时:

go run ./packager app.js -o app.exe                    # CLI 应用
go run ./packager counter.js --gui -o counter.exe      # GUI 应用
go run ./packager app.js --gui --target linux/amd64    # 纯 Go 交叉编译
选项说明
-o, --out <path>输出文件路径(默认:<输入文件名>.exe)
--name <name>应用名,用于错误信息显示,默认取输入文件名
--windowed窗口模式:不显示控制台窗口(仅 Windows)
--guiGUI 应用:窗口消息泵事件循环(配合 gx/gfx render)
--target <os>/<arch>交叉编译目标:windows / linux / darwin × amd64 / arm64 / 386
-v, --verbose显示构建过程输出
交叉编译零负担

整个运行时是纯 Go(无 cgo),所以 --target linux/amd64 在 Windows 上也能直接编,不需要目标机的交叉工具链。

各平台的分发注意事项(Windows 图标与签名、Linux 打包格式、macOS .app bundle)见 docs/desktop-distribution.md

12常见问题

为什么不支持 var?

Gox 刻意只实现 ES6+ 子集:let / const 具备块级作用域,语义更清晰,省去了 var 提升等历史包袱。REPL 启动时的提示语 "ES6 subset, no var" 说的就是这件事。

和 Node.js / Bun / Deno 是什么关系?

定位不同。Gox 的价值在于从零走通完整编译管线(lexer → parser → compiler → 字节码 VM)并提供可用的语言与宿主能力,适合写脚本工具、CLI、小型桌面程序,以及学习运行时原理。它不追求替代生产环境的 Node 生态 —— 没有 npm 生态兼容,也没有 JIT。

macOS 能用吗?

CLI 完全可用:npm i -g @goxjs/goxjs 的预编译二进制包含 darwin-amd64 与 darwin-arm64。GUI 窗口后端尚未实现 macOS 支持。

怎么调试 / 参与开发?

go test ./...           # 运行全部测试
go run ./dbgtool        # 词法调试器:打印 Token 流
go run ./test/bench     # 性能剖析基准(fib、函数调用、对象操作)

想新增标准库 API 或深入理解回调桥与内存管理,阅读 JavaScript Runtime API 实现教程