Help us shape the future of Go! Take the 2026 Go Developer Survey

Gox

module
v0.14.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 22, 2026 License: MIT

README

Gox

用 Go 从零实现的 JavaScript 运行时:词法分析 → 语法分析 → 字节码编译 → 栈式虚拟机执行。 单二进制、零 cgo、零外部运行时依赖,自带软件光栅化 GUI 与脚本打包器。

Go License npm Platform Release test262 language test262 built-ins

📖 官网与使用教程 | GUI 开发指南 | npm 包

English version: README.en.md。两份 README 同步维护,描述同一个构建。

目录

项目简介

Gox 是一个用纯 Go 实现的 JavaScript(ES6+ 子集)运行时。整条编译管线 —— 词法分析、语法分析、 字节码生成、栈式虚拟机 —— 都从零实现,不依赖 V8/QuickJS 等任何现成引擎,也不依赖 cgo。

在此之上,Gox 补齐了脚本语言通常缺失的那一层:一套自研的桌面 GUI 渲染层(纯 Go 软件光栅化, flex 布局 + JSX + 信号驱动更新,win32 / X11 / cocoa 窗口后端)、一组宿主能力模块(文件、HTTP、剪贴板、 原生对话框、持久化存储),以及把脚本打包成独立可执行文件的工具链。

它的定位是"小而完整":一个 go build 得到一个可执行文件,可以把 JS 当脚本层嵌进 Go 程序, 也可以直接用它写带界面的小工具并打包分发。

设计取向

  • 单二进制分发 —— 静态编译、无动态库依赖;跨平台只需换 GOOS/GOARCH,无需目标机工具链。
  • 零 cgo —— 后端直连系统 API(Windows 走 syscall、Linux 走 X 协议),代价是不支持 -race 检测。
  • GUI 不是外挂 —— 渲染、布局、命中测试、脏矩形重绘全在运行时内部,脚本侧只有 JSX 与信号。
  • 确定性单线程 —— VM 单线程执行,网络 I/O 在 goroutine 中完成后投递回主线程,回调无需加锁。

适用场景:用 JS 写跨平台桌面小工具;把 JS 作为配置与插件层嵌入 Go 应用;学习编译器与虚拟机实现。

首屏示例:单双栏自适应列表页

窗口宽度 < 600dp 时单栏,拖宽到 ≥ 600dp 自动变双栏(左侧列表 + 右侧详情)。 拖拽窗口边缘即触发,不用监听 resize,也不用轮询尺寸。完整可跑文件: testdata/adaptive_list_demo.js(gox testdata/adaptive_list_demo.js)。

import { h, render } from "gx/gfx";
import { createSignal } from "gx/solid";
import { useBreakpoint, matchBreakpoint } from "gx/viewport";
import { Switch, Match } from "gx/view";

const bp = useBreakpoint();                 // () => "sm" | "md" | "lg" | "xl"(订阅型)
const [selected, setSelected] = createSignal(0);

const ITEMS = [
  { id: "a", title: "编译管线", desc: "词法 → 语法 → 字节码 → 栈式 VM,109 个操作码。" },
  { id: "b", title: "软件光栅化 GUI", desc: "flex 布局 + JSX + 信号驱动,三后端。" },
  { id: "c", title: "脚本打包器", desc: "把 .js 打成单个可执行文件。" },
];

// 布局分支由断点名声明(一张 sm/md/lg/xl 的表),
// 而不是把同一个宽度判断散落在 N 处 if 里 —— 加一档只改表,不改判断。
const layout = () => {
  bp();   // ← 无条件读一次 = 订阅登记。删掉这行,整页不再自适应(无警告)
  return matchBreakpoint({
    sm: { cols: 1, side: false },   // <600dp  单栏
    md: { cols: 2, side: true },    // >=600dp 双栏
    lg: { cols: 2, side: true },
    xl: { cols: 2, side: true },
  });
};

const List = () => (
  <column gap={6}>
    <view each={ITEMS} key="id">
      {(it, i) => (
        <row gap={6} padding={8} radius={6}
             background={selected() === i ? "#dce8ff" : "#f2f5fa"}
             onClick={() => setSelected(i)}>
          <text font={13} color="#334">{it.title}</text>
        </row>
      )}
    </view>
  </column>
);

const Detail = () => (
  <column gap={6} padding={12} radius={8} background="#ffffff">
    <text font={14} color="#223">{ITEMS[selected()].title}</text>
    <text font={12} color="#667">{ITEMS[selected()].desc}</text>
  </column>
);

render(
  <window title="自适应列表页" width={520} height={420}>
    <column gap={10} padding={14}>
      <text font={15}>{() => `breakpoint = ${bp()}   →   ${layout().side ? "双栏" : "单栏"}`}</text>
      <Switch>
        <Match when={() => layout().side}>
          <row gap={10}>
            <column width={210} gap={6}><List /></column>
            <column flex={1}><Detail /></column>
          </row>
        </Match>
        <Match when={() => true}>
          <column gap={8}>
            <List />
            <Detail />
          </column>
        </Match>
      </Switch>
    </column>
  </window>
);

两个最容易踩的坑(示例注释里同样写明):

  1. 纯读数会短路掉订阅 —— breakpoint() / posture() / hasFold() 是纯读数、不订阅;只有 useBreakpoint() 这类两段式返回的取值函数才订阅。把纯读数写在三元的第一操作数或 && 左侧, 被短路的那支一次都没读 ⇒ 零依赖 ⇒ 之后宽度变了这一支永不重跑,没有任何警告。 gfx/adaptive_list_test.go 把这条钉成了断言:纯读数快照拖宽后仍停在首帧的值。
  2. 坐标单位是两套 —— reservedRegions() 的 x/y/width/height 是设备像素(折痕、挖孔), safeAreaStyle() 与脚本显式写的尺寸属性是 dp(内核每帧乘 Display.Scale)。 折痕避让时不要把前者直接喂给布局属性,先除 pixelRatio。

措辞要说准:<Switch> / <Match> 本身就是条件表达,所以对外不说「无需任何 if 判断」; 准确的收益是布局分支由断点名声明,而不是散落在 N 处 if 里。

功能特性

  • 完整编译管线 —— 自研 lexer / parser / compiler / bytecode VM,109 个操作码,定长 3 字节指令编码 ([操作码 1B][操作数 2B 大端]),解码即取即用
  • ES6+ 语言子集 —— let/const/var、函数与箭头函数、闭包、class、async/await、 解构赋值、剩余/默认参数、展开、模板字符串、for...of、try/catch/throw、可选链 ?.、 空值合并 ??、ES 模块 import/export,以及 JSX 语法(编译期降级为 h(tag, props, ...children) 调用)
  • 丰富的内置对象 —— Array / String / Number / Object / Boolean / Math / JSON / Map / Set / WeakMap / WeakSet / Symbol / BigInt / RegExp / Proxy / Reflect / Iterator / Promise / ArrayBuffer / DataView(TypedArray 家族)/ WeakRef / FinalizationRegistry / 完整错误类型族 / Temporal(现代日期时间 API,本运行时不含 Date)
  • 宿主能力模块 —— 以全局对象注入:fs(Node 风格,同步 + 异步两套)、path、http (客户端 get/request + 服务端 createServer)、fetch、process、stats
  • 自研 GUI 渲染层 —— gx/gfx 模块:flex 风格布局(column/row/grid、百分比尺寸、min-max 钳位、 flexShrink、折行)、圆角/线性渐变/阴影装饰、命中测试、脏矩形局部重绘;win32(纯 syscall)、X11 与 macOS(cocoa, purego) 窗口后端。详见 GUI 开发指南
  • 媒体组件(<video> / <audio>) —— 内核只定义宿主契约(nativeVideoHost / nativeAudioHost): 能挂载、能收事件、playing / muted / loop / volume / fit 全受控,后端没这块能力就明说缺 (封面占位 + 一次 onError({code:"unsupported"}) + canIUse("video"/"audio") 照实回答 false), 绝不假装在播。Linux 默认构建挂 gfx/softmedia 软件后端,真的能播:视频 GIF(含 disposal 合成) 与 MJPEG,音频 WAV(PCM 8/16/24/32 位 + IEEE float),音频经 PulseAudio purego dlopen 输出(零 cgo), 无声卡时降级 null sink(播放时钟与事件照真)。h264 / aac / mp3 留给平台媒体层, 边界见 media-decision.md
  • 原生感的交互组件 —— 表单控件(input/textarea/select/slider/checkbox/radio/switch)、 弹层(dialog/drawer/toast)、选项卡(tabs,keep-alive 页语义 + 受控/非受控双模式)、滚动容器、自绘画布, 以及自绘菜单栏与右键菜单(下拉/子菜单/快捷键/禁用项,不依赖系统菜单 API)
  • 反馈与数据类组件 —— alert / tag / badge / avatar / empty / icon(内置 15 个 像素风图标)/ spinner / skeleton / pagination(完全受控);加载态复用同一根动画心跳, 静止时零开销。示例见 testdata/feedback_demo.js
  • 原生能力层 —— 六个模块共用一个宿主契约(NativeHost):gx/device(设备信息 / 电量 / 网络 / 震动 / 屏幕亮度)、gx/app(前后台 / 返回键 / 分享)、gx/geo(定位,含持续监听)、 gx/media(拍照 / 选图 / 选视频 / 保存)、gx/permission(权限查询与申请)、gx/viewport (安全区 / 软键盘高度 / 分屏与多窗口形态)。桌面由 win32 宿主直接实现,移动端原生壳只需实现 同一个契约即可接入;缺能力时明说缺(canIUse + 8 个统一错误码),不返回假数据
  • 事件循环 —— setTimeout / setInterval / requestIdleCallback,以及精度可控的严格定时器变体 (setStrictTimeout 等);GUI 模式下事件循环接入窗口消息泵
  • 响应式编程 —— Dart GetX 风格的 obs / computed / ever / once,以及 SolidJS 风格的 gx/solid 信号(createSignal / createEffect / createMemo / createResource / onMount / onCleanup);声明式界面是元素级指令:model 双向绑定、each 列表(keyed 复用)、 show 条件保活显隐,多分支用 gx/view 的 Switch + Match
  • npm 分发 —— @goxjs/goxjs 内置 macOS x64/arm64、Linux x64/arm64、Windows x64 五个平台的预编译二进制, npm i -g @goxjs/goxjs 即得 goxjs 命令
  • 工具链 —— 交互式 REPL、jsbuild 打包器(JS → 独立可执行文件,支持 GUI 应用与纯 Go 交叉编译)、 gx/dev 运行时快照(帧统计 / 缓存命中 / 最近警告,可做调试面板)

快速开始

环境要求
场景 要求
从源码构建 Go 1.26.2 或更高(零 cgo,无需 C 工具链)
仅运行脚本 无 —— npm 包已内置各平台预编译二进制,只需 Node.js ≥ 14
GUI 应用 Windows(win32)、Linux(X11)或 macOS(cocoa)
从源码构建

npm/ · website/ · gox-logo-concepts/ 是三个独立仓库(gox-npm · gox-website · gox-logo-concepts),2026-10-12 起不再以 git submodule 挂载,克隆主仓不需要 --recurse-submodules。CI 构建时会自动 checkout npm/ 与 website/;本地要跑 scripts/check-registries.py 的话,先 git clone https://github.com/14752222/gox-npm npm(否则读不到 npm/package.json)。

git clone https://github.com/14752222/Gox.git
cd Gox
go build ./cmd/gox   # 生成可执行文件;Windows 下为 gox.exe

也可以 go install github.com/14752222/Gox/cmd/gox@latest 直接装进 GOBIN(需先发布过 tag)。

通过 npm 安装
npm i -g @goxjs/goxjs    # 安装后可直接使用 goxjs 命令
npx goxjs app.js         # 或不安装,直接运行
交互式 REPL
./gox                # Unix;Windows 下为 gox.exe
Gox REPL (ES6 subset)
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 退出。

运行脚本
./gox example.js    # npm 安装的 goxjs 命令用法相同

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

使用方法

语言示例

以下示例均实际运行验证过。

基础语法:

let x = 10
let y = 20
console.log(x + y)                                                 // 30
console.log([1, 2, 3].map(v => v * 2))                             // [2, 4, 6]
console.log([1, 2, 3].filter(v => v > 1).reduce((a, b) => a + b))  // 5
console.log(Math.hypot(3, 4))                                      // 5

let name = "Gox"
console.log(`Hello, ${name}!`)                                     // Hello, Gox!

let [a, b] = [1, 2]                                                // 解构赋值
console.log(a + b)                                                 // 3

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

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

语法提示:异步函数写 async function 或 async 箭头 async () => {} 都行;顶层不能 await。

ES 模块 —— 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))
6.28
tick

响应式(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
2                  ← 顶层回显:最后一条赋值表达式的值
宿主能力模块

fs / path / http / fetch / process / stats / crypto / hash 以全局对象形式注入,直接使用,无需 import:

const text = fs.readFileSync("a.txt")          // 同步:失败抛 JS 异常
const data = await fs.readFile("a.txt")        // 异步:无回调时返回 Promise
fs.mkdirSync("a/b", { recursive: true })

const server = http.createServer(function (req, res) {
  res.writeHead(200, { "Content-Type": "text/plain" })
  res.end("hello")
})
server.listen(0, function () { console.log(server.port) })

const res = await fetch("https://example.com")
console.log(res.status, await res.text())

异步 I/O 的实现模型是跨 goroutine I/O + 投递回 VM 单线程:网络读写不触碰 VM 状态,结果经 零延时定时器回到事件循环执行 JS 回调,因此回调里访问 VM 无需加锁。

一份完整的 HTTP 示例(起服务于端口 0、路由/查询参数/JSON 请求体/404-405-500 各状态码、 回调式 http.get·http.request 与 Promise 式 fetch 对照、异步响应与 server.close 收尾): testdata/http_demo.js —— gox testdata/http_demo.js 即可运行。

摘要与随机数 crypto / hash

crypto 与 hash 是全局对象/函数(与浏览器同形,无需 import),底层走 Go 标准库 crypto/*,零 cgo。同一批实现也注册为内置模块 gx/crypto,可以 import:

hash("gox");                                 // 同步 → 小写 hex (缺省 SHA-256)
hash("gox", "SHA-512");                      // 算法名大小写/连字符不敏感
hash(new Uint8Array([103, 111, 120]));       // 入参: string / ArrayBuffer / TypedArray / 字节数组
hash([103, 111, 120]);                       // fs.readBytesSync 的返回形也能直接喂

const ab = await crypto.subtle.digest("SHA-256", "gox");   // Promise → ArrayBuffer (32 字节)
crypto.randomUUID();                                       // RFC 4122 v4
crypto.getRandomValues(new Uint8Array(16));                // 原地填充, 上限 64KB

口径与边界:

项 说明
算法 hash() 支持 MD5 / SHA-1 / SHA-256 / SHA-512;crypto.subtle.digest() 只认 SHA-1 / SHA-256 / SHA-512(WebCrypto 没有 MD5,抄过来的代码不该在这里通过)
参数序 hash(data, algo) 而 crypto.subtle.digest(algo, data) —— 前者是 Gox 的同步糖,后者与浏览器同形
字符串编码 按 UTF-8 编码(与 TextEncoder.encode 同口径)
不做的事 加解密 / 签名 / KDF 不在本模块内 —— 那需要密钥管理上的产品设计
错误 不支持的算法或入参一律 TypeError(digest 走 Promise reject),不会静默回退到别的算法

完整演示:testdata/crypto_demo.js —— gox testdata/crypto_demo.js 即可运行。

原生能力模块 gx/*

gx/device / gx/app / gx/geo / gx/media / gx/permission / gx/viewport 需要 import (不是全局对象)。三种调用形态要在写代码时区分开:

import { deviceInfo, battery, isOnline, canIUse } from "gx/device";   // 拉取型 + 上报型
import { getLocation, watchLocation } from "gx/geo";
import { takePhoto } from "gx/media";                                  // 动作型

const info = deviceInfo();                    // 同步可得
console.log(info.platform, info.model, battery().level + "%", isOnline());
// battery()/isOnline() 读的是快照;useBattery() 返回取值函数,宿主上报时自动刷新

if (canIUse("camera")) {                      // 事前判断能力,不要靠 catch 兜底
  const photo = await takePhoto({ count: 1 });      // 失败会 reject,带 errCode
}

const stop = watchLocation((loc) => console.log(loc.latitude, loc.longitude));
try {
  await getLocation({ highAccuracy: true });
} catch (e) {
  if (e.errCode === "permission-denied") console.warn(e.message);
}

桌面后端直接实现其中能实现的部分(电量 / 网络 / 亮度 / 屏幕常亮 / 打开系统设置页), 给不出的一律诚实报 unsupported。移动端同样实现这套 NativeHost 契约,不必改内核 —— Android 壳(Kotlin)与 iOS 壳(Swift)的六个模块基本齐备:Android 已在模拟器上实测通过 (上屏 / 触摸 / IME / 安全区 / 折叠屏),iOS 壳工程与构建脚本(含 TestFlight 打包)齐备、真机验收待做; 鸿蒙壳(ArkTS)目前只通了安全区与折叠上报两条纯上报通道,六个能力模块仍是桩。 语义、8 个错误码与各模块导出表见 GUI 开发指南 §9.6; 逐方法(模块 × 平台)状态表、壳构建说明与首帧自检清单见 app/NATIVE-HOST.md; 三种形态的完整演示:testdata/native_demo.js。

三端验证范围(CI 矩阵口径,看板单 rSm7zW / S4) —— 能力矩阵按下面的实际验证面写,不夸:

平台 构建冒烟(每次 push) 设备回归 折叠语义
Android mobile-smoke.yml 的 android job:libgox.so → assembleDebug APK android-fold-emulator job:pixel_fold 模拟器(API 34+,真能切铰链)上跑 CLOSED → HALF_FOLDED → OPENED 矩阵 + 截屏存档;真机侧 scripts/mobile-device-accept.sh --rounds 20(§4 生命周期循环) ✅ 模拟器已覆盖折叠态切换(cmd device_state)
iOS mobile-smoke.yml 的 ios job:xcodebuild 模拟器包 ios-device-matrix job:iPhone 16 / 16 Pro Max / iPad 三机型 × 竖横屏,simctl 截屏存档 ⚠️ 模拟器没有折叠屏:reservedRegions(iOS 27.1+)在模拟器只报 flat,是已知边界;真机验收待做
鸿蒙 交叉编译 + 宿主契约测试 + assembleHap 构建通过 — ❌ 模拟器验收待做(HAP 签名是硬阻塞:要在 DevEco GUI 里配华为账号,命令行替不了);不写「已支持鸿蒙」

「CI 里挂形态矩阵、失败即阻断合并」在桌面侧由 ci.yml 的 foldable-matrix job 承担 (fake surface 回放 4 档形态 + 20 轮生命周期 + README 示例四档宽度 + 权限三端口径); 设备侧由 mobile-smoke 的上述两个 job 承担。把哪个 job 标 required 是仓库的分支保护设置, 代码里写不了 —— job key 就是上面那几个名字。

GUI 桌面应用

gx/gfx + gx/solid 提供 JSX 声明式 UI 与信号驱动的响应式更新,渲染器为纯 Go 软件光栅化 (无 cgo、无动态库依赖)。

用脚手架起一个新工程 —— 生成出来的默认工程开箱即跑,目录布局就是脚手架的默认输出:

gox create my-app                          # 同为 goxjs create / npx @goxjs/goxjs create
gox create my-app --ts                     # TypeScript/TSX 版: .tsx 直接跑, 无 node 端构建
cd my-app && npm install && npm run dev

生成 package.json、入口 src/main.js、根组件 src/app.js、共享状态 src/store.js、 设计令牌 src/theme.js 与 src/components/ 下三个示例组件(计数器 / 待办列表 / 多状态), 把信号、元素级指令(each / show)、model 双向绑定与 Switch / Match 各演示一遍。 --ts 生成 TypeScript 版:入口改 src/main.tsx、附 tsconfig.json 与 src/gox.d.ts (IDE 类型提示),源码在加载时自动完成类型剥离(内嵌 esbuild 转译,JSX 原样保留)—— 没有 node 端构建步骤,类型检查交给 IDE,运行时零开销。 模板是 scaffold/template/ 下的真实文件,由 go:embed 嵌进二进制 (改模板要重新编译才生效;scaffold/scaffold_test.go 会保证生成出来的 JS/TS 仍能过 转译 + lexer → parser → compiler,gfx/scaffold_project_test.go 会真的把它挂载起来点一遍)。

最省事的最小手写版:

import { h, render, createSignal } from "gox"        // 聚合入口:所有 gx/* 导出的并集
// 等价的细分写法:import { h, render } from "gx/gfx"; import { createSignal } from "gx/solid"

const [count, setCount] = createSignal(0)

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

点击按钮 → 信号更新 → 依赖它的属性/文本节点标脏 → 脏矩形合并后只重绘受影响区域。render() 返回窗口句柄,可调用多次开多窗口(各有独立元素树与焦点,全关才退出进程)。

完整参考 —— 事件模型、内置元素属性表、弹性布局与装饰绘制、受控组件与输入法、过渡动画、 元素级指令(model / each / show)与 gx/view 多分支、路由(gx/router:路由表 / 参数匹配 / 三级守卫 / 懒加载 / 历史栈 / 多窗口作用域 / 折叠屏双栏)与屏幕信息(gx/screen)、 原生对话框 / 剪贴板 / 持久化存储 / 菜单栏、多窗口语义与平台差异 —— 见 docs/gui-guide.md,可直接运行的示例见 testdata/(counter_demo.js、form_demo.js、multiwindow_demo.js、 menu_demo.js、view_demo.js、router_demo.js、router_window_demo.js 等 35+ 个)。

路由与屏幕适配另有一份专门手册:docs/gui-router.md。

打包为独立可执行文件

jsbuild(packager/)把入口脚本及其相对 import 的模块嵌入一个生成的 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 交叉编译,无需目标机工具链
Usage: jsbuild <input.js> [options]

Options:
  -o, --out <path>     输出文件路径 (默认: <输入文件名>.exe)
      --name <name>    应用名 (用于错误信息显示, 默认取输入文件名)
      --windowed       窗口模式: 不显示控制台窗口 (仅 Windows)
      --gui            GUI 应用: 窗口消息泵事件循环 (配合 gx/gfx render)
      --target <os>/<arch>  交叉编译目标 (windows|linux|darwin / amd64|arm64|386)
      --icon <path>    应用图标 (.png 1024 源图或现成 .ico/.icns):
                       Windows 内嵌图标+版本资源 (.syso), darwin 产出 .app bundle
      --version <v>    版本号 (x.y.z, 写进 Windows 版本资源与 .app Info.plist)
  -v, --verbose        显示构建过程输出

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

命令行参数:打包产物与 gox <file> <args> 保持同一口径 —— process.argv 恒为 [可执行文件, 脚本路径, ...用户参数],用户参数始终从索引 2 开始 (打包产物没有“脚本路径”这个宿主参数,运行时会补上提取后的入口路径占位)。 于是同一份脚本源码跑和打包跑都能用 process.argv[2] 取到第一个用户参数, 支持「拖拽文件到 exe 上打开」「带参数启动」这类桌面工具的基本预期。

多平台配置(gox.json / 权限 / 图标)

gox create 生成的工程自带 gox.json(应用名、appId、版本、权限、各平台子配置) 与完整的 Android / iOS / 桌面资源骨架 + 默认图标:

  • gox sync —— 把 gox.json 里的 permissions 幂等注入 AndroidManifest 与 iOS Info.plist(恒定两条基线权限 INTERNET + ACCESS_NETWORK_STATE; 未声明的逻辑权限不写入);
  • gox icon —— 以 assets/icon.png(1024×1024)为单源生成 Android mipmap、 iOS AppIconSet、Windows .ico、macOS .icns、favicon;
  • gox cert —— 一键生成平台签名证书(快捷操作, 纯 Go 实现无需 JDK/openssl): android(PKCS12 keystore, gradle 自动接入签名)、windows(自签代码签名 .pfx)、harmony(ECC P-256 调试 .p12+.cer)、ios(密钥对+CSR, 换回 .cer 后 gox cert ios --cer x.cer 合成 .p12)。产物落在 certs/(已 gitignore); 自备正式证书时在 gox.json 的 cert 段配置, 不必用本命令;
  • gox build windows|macos|android|ios —— 统一构建入口(sync → icon → 平台打包); gox build android --release 走签名发布包(证书: gox.json cert 段 > certs/ 元数据 > 自动生成调试证书)。

字段说明、权限清单与图标规范见 docs/platform-config.md。

脚本运行时的权限模型(--allow-fs-write 等,默认最小权限)见 docs/permissions.md;主流 npm 库实测兼容性见 docs/compat-matrix.md。

配置说明

Gox 运行时本身不使用配置文件,全部行为由命令行参数、少量环境变量与脚本内 API 控制。 工程级配置(应用名、appId、版本、权限、图标)由 gox create 生成的 gox.json 承担, 见下文「多平台配置」一节。

命令行参数
命令 参数 说明
Gox / goxjs 无 启动交互式 REPL
Gox / goxjs create <目录> 按默认模板生成一个 GUI 工程(--ts 生成 TypeScript/TSX 版);别名 new / init
Gox / goxjs sync [目录] 把 gox.json 的权限声明幂等注入 Android/iOS 清单
Gox / goxjs icon [目录] 从 1024 源图一键生成全平台图标
Gox / goxjs build <android|ios|windows|macos> 统一构建入口(sync → icon → 平台打包)
Gox / goxjs <script.js> 执行脚本文件;报错写 stderr 并以非零码退出
Gox / goxjs help / version 显示用法 / 版本号
go run ./packager 见 打包 jsbuild 打包器参数表

create 的选项:--name <名字>(覆盖项目名,缺省取目录名)、-f / --force (目标目录已存在且非空时才需要,缺省拒绝覆盖)。参数只在本参数不像脚本路径时 才当子命令认(判据是扩展名与路径分隔符),所以 gox help.js、gox src/create.js 仍然老老实实当脚本执行。

REPL 内建命令::help(帮助)、:clear(重置全局环境)、:exit / :quit(退出)。

环境变量
变量 作用 缺省
GOX_STORAGE_DIR 覆盖 gx/storage 的数据根目录,测试与多实例隔离常用 空(走系统配置目录)
GOOS / GOARCH / CGO_ENABLED 仅在构建期影响交叉编译(见 scripts/build-npm.sh) 宿主平台
应用级配置
配置项 设置方式 说明
应用名 / 数据目录 setAppName("MyApp")(gx/storage) 数据落在 os.UserConfigDir()/Gox/<应用名> —— Windows %AppData%、Linux ~/.config、macOS ~/Library/Application Support;未设置时从脚本文件名推导
窗口标题与尺寸 JSX <window title width height> 或 render(tree, {title, width, height}) 缺省 Gox 400×300
字体 平台自动探测 Linux 惰性扫描系统字体目录(/usr/share/fonts、~/.local/share/fonts 等),CJK 字体优先,条目上限 2000
主题 / 颜色 元素属性(background / border / color) 支持命名色与 #rgb / #rgba / #rrggbb / #rrggbbaa / rgb() / rgba()
npm 包信息
项 值
包名 / 命令 @goxjs/goxjs → goxjs
内含平台 macOS x64 / arm64、Linux x64 / arm64、Windows x64
Node 版本 >=14(仅用于分发二进制,运行期不依赖 Node)
版本真源 npm/package.json 的 version 字段

技术架构

编译与执行管线
 .js 源码
    │  lexer        词法分析
    ▼
 Token 流
    │  parser       语法分析
    ▼
 AST (ast/)
    │  compiler     符号表解析 + 字节码生成 (常量池)
    ▼
 字节码 (bytecode/)   定长 3 字节: [opcode 1B][operand 2B 大端]
    │  vm           栈式调用帧执行 + 定时器事件循环 + 模块加载
    ▼
 结果
关键设计决策
  • 无 GC 的显式内存管理 —— 对象生命周期由运行时显式控制,循环引用有专门处理。
  • 单线程 VM + 跨 goroutine 调度 —— http.createServer 等网络回调跨 goroutine 捕获后调度回 VM 单线程执行,无需锁。挂起任务保活事件循环,计数归零才允许进程退出。
  • 定长指令编码 —— 无需变长解码状态机,直取操作数;常量池承担大对象引用。
  • 可选能力走可选接口 —— GUI 后端通过 Surface + WindowFactory 抽象,换后端零改动脚本; 平台独有能力(IME、剪贴板、原生对话框)按后端探测,缺失时静默降级而非报错。
目录结构
目录 职责
lexer/ 词法分析器(含 JSX 词法支持)
parser/ 语法分析器,生成 AST(含 JSX 语法降级)
ast/ AST 节点定义
compiler/ AST → 字节码编译器(含符号表)
bytecode/ 操作码与字节码格式定义
vm/ 栈式字节码虚拟机(调用帧、模块加载、定时器调度)
object/ 运行时对象系统(Number / Array / Map / Promise / Observable…)
runtime/ 全局环境 Environment
stdlib/ 标准库与宿主 API 实现(含 gx/solid 响应式信号)
gfx/ 自研 GUI 渲染层(软件光栅化、布局、命中测试、win32 / X11 / cocoa 后端)
packager/ jsbuild 打包器(GUI 应用、交叉编译)
scaffold/ gox create 的项目脚手架:template/ 是真实文件(go:embed 进二进制;ts/ 是 --ts 的 TSX 覆盖层),scaffold.go 负责占位符替换与目录校验
tools/ 开发工具与代码生成(2026-10-12 起 icongen/certgen/tstransform/update/perf 五个根目录归拢至此):tstransform/(TS/TSX → JS 入库转译,内嵌 esbuild Go API,vm 加载 .ts/.tsx 时调用)、icongen/(全平台图标生成)、certgen/(签名证书)、update/(自更新)、perf/(帧率/GC 基准)、bcdump/initpurity/propdump/rssmon(诊断工具)
test/ 测试相关:bench/ 性能剖析基准(fib、函数调用、对象操作、数值解析)
testdata/ 可直接运行的示例脚本(语言特性、宿主 API、GUI 示例)
docs/ 对外文档(GUI 指南、运行时 API 教程、分发与发版手册)——过程性台账由维护者在仓库外维护(不随仓库发布)
npm/ 独立仓库 gox-npm(CI 构建时自动 checkout):npm 包 @goxjs/goxjs 的定义(package.json / bin/gox.js / 包说明),二进制由发版流水线现场编译,不进仓库
scripts/ 构建与检查脚本:build-npm.sh 交叉编译五个平台的二进制;check-registries.py 注册表一致性(内置组件四处 / gx 模块三处 / 版本号 / npm 清单),配 check-registries-selftest.py 做负向自测(check-site.py 只认顶层 *.html,官网 2026-09-25 迁 VitePress 后已不适用,死链改由 vitepress build 把关);check-imports.py 文档/示例的 import 与内置模块导出表一致(编译期报错级 + 文件模块静默 undefined 级)
.github/workflows/ CI:ci.yml 常规闸门(注册表一致性 + go build/vet/test);release.yml 发版流水线(push main / tag / Release → npm);pages.yml 官网发布
website/ 独立仓库 gox-website(CI 构建时自动 checkout):官网源码,由 pages.yml 每日定时或手动触发发布到 GitHub Pages
gox-logo-concepts/ 独立仓库 gox-logo-concepts:logo 概念稿与官网资产生成脚本(make_assets.py)

开发与测试

go test ./...          # 运行全部测试 (bytecode / compiler / lexer / object / parser / runtime / vm)
go run ./test/bench    # 生成 cpu.prof 性能剖析

由于零 cgo,go test -race 在本项目不可用(Go 会要求 cgo 支持)。

深入参与开发(新增标准库 API、理解回调桥与内存管理)请阅读 docs/js-runtime-api-tutorial.md。

发版流程

@goxjs/goxjs 由 GitHub Actions 自动发布(.github/workflows/release.yml), 认证走 npm Trusted Publishing (OIDC),仓库里不需要任何 secret / token:

# 1. 改版本号 —— npm/package.json 的 version 是唯一真源,npm/ 是独立仓库(本地是它的工作副本):
$EDITOR npm/package.json
git -C npm commit -am "chore(release): 0.2.1" && git -C npm push
# 2. 回主仓打 tag 并推送 —— tag 必须与 gox-npm 默认分支的版本一致(decide 闸门会核对)
git tag v0.2.1 && git push origin v0.2.1
# 3. 剩下交给 CI:交叉编译五平台二进制 → 冒烟测试 → 打包校验 → npm publish --provenance
#    发布成功后自动打 v0.2.1 标签并建 Release
  • 触发:push 到 main、推送 v* 标签、发布 Release,以及手动 workflow_dispatch (可勾 dry_run 只构建打包、不上传)
  • 幂等:CI 先查 registry,该版本已存在则跳过并留一条 notice —— 重复推送、重跑历史工作流 都不会报红,也不会重复发布
  • 版本号:日常提交不会触发真发布,只有 registry 上还没有的版本才会被发出去;打标签时标签号 必须与 package.json 一致,否则直接失败
  • 本地手工发版(应急用,需要 OTP): bash scripts/build-npm.sh && cd npm && npm publish --access public
  • 排障:OIDC 失败与"Trusted Publisher 没配对"返回的是同一个误导性 404,先核对 npm 侧那四个 字段(见 workflow 头部注释);每次发布的日志里都会打印 npm 版本、NODE_AUTH_TOKEN 是否为空、 OIDC 端点是否可用

贡献指南

欢迎提交 Issue 与 Pull Request —— 完整的参与方式、本地闸门、与本仓库特有的 「改一处、同步多处」纪律,见 CONTRIBUTING.md; 行为准则见 CODE_OF_CONDUCT.md。首次贡献可从带 good first issue 标签的 issue 入手。

开发环境:Go 1.26.2+,零 cgo(无需 C 工具链)。提交前请确保:

gofmt -l .             # 应无输出
go build ./...
go vet ./...
go test ./...
python3 scripts/check-registries.py   # 注册表一致性(CI 同款闸门)
python3 scripts/check-imports.py      # 文档/示例 import 与导出表一致(CI 同款闸门)

提交规范:沿用仓库现有的 Conventional Commits 风格,scope 用受影响的模块或主题, 中文描述:

feat(gfx): 新增 <slider> 的键盘调节支持
fix(vm): 修正 SET_INDEX 对非对象类型静默丢弃的问题
docs(gui-guide): 补充多窗口事件泵的轮询语义
ci(release): 打包闸改回 tar 校验,不再解析 npm 的输出

新增内置 GUI 组件时须同步四处(漏改会静默失效,不出编译错误):

位置 作用
gfx/node.go 的 knownTags 注册标签名,否则 h() 会打印"未知标签"警告
gfx/layout.go 的 intrinsicSize 声明固有尺寸
gfx/layout.go 的布局分派 声明子节点如何参与布局
gfx/raster.go 的 drawNode 实现绘制;被 case 截走的标签需自行补画 background / border

其他约定:

  • 事件回调统一经 callHandler 分发;涉及窗口的操作从节点出发经 appOfNode(n) 取所属窗口(支持多窗口)
  • gx/* 的 JSX 属性与子节点在调用当场求值一次 —— 需要响应式就必须传函数(value: () => sig()), 子节点写 {() => sig()} 而不是 {sig()}),传值只是一张快照 —— 而且子节点写成快照 是全静默的(属性误用有警告,子节点没有),见 docs/gui-guide.md §8.1
  • 新增标准库 API 请同步更新 docs/js-runtime-api-tutorial.md; 未决与未实现项由维护者记入仓库外的过程性台账(不随仓库发布,贡献者无需更新)
  • 涉及 GUI 组件的改动,请同步 docs/gui-guide.md 与官网组件参考(过程性落地台账由维护者在仓库外维护)
  • 示例脚本放在 testdata/ 并确保可直接运行

相关文档

下表只列 docs/ 里已定稿、对外发布的文档。技术选型、开发计划、现状台账等 未定稿的过程性材料由维护者在仓库外维护,不进远端。

文档 内容
docs/tutorial.md 实战教程:API 调用与参数、内置模块导入、gox create 建工程、路由定义与注册(配可直接运行的示例脚本)
docs/gui-guide.md GUI 开发指南:元素/事件参考、布局、动画、宿主能力、示例索引
docs/platform-config.md 多平台配置:gox.json 字段说明、权限清单表、图标规范、gox build 各目标与真机验收步骤
docs/js-runtime-api-tutorial.md 运行时 API 教程:函数类型、回调桥、内存管理、新增 API 的完整流程
docs/gui-patterns.md 用户态模式手册(路由、状态、主题等惯用法)
docs/gui-model-binding.md model 双向绑定:接口设计、语义表、与 Vue 的对照、反例
docs/foldable-adaptation.md 折叠屏适配教程:纯读数短路订阅的坑、姿态不驱动布局、折痕避让单位换算、Tabletop 组合写法、三端验证范围
docs/npm-release.md @goxjs/goxjs 发版手册:版本号策略、构建步骤、OIDC 配置要求、验收口径
docs/desktop-distribution.md 桌面应用分发:图标、签名、各平台打包格式
官网 安装、运行脚本、写 GUI 应用与打包的在线教程

许可证

MIT License

Directories

Path Synopsis
Package bytecode 定义了虚拟机的字节码指令集。
Package bytecode 定义了虚拟机的字节码指令集。
cmd
gox command
cmd_build.go 实现 `gox build <target>`: 统一构建入口。
cmd_build.go 实现 `gox build <target>`: 统一构建入口。
goxwasm command
非 js/wasm 平台的占位:让 `go build ./...` / `go vet ./...` 在本机不至于因 "build constraints exclude all Go files" 报错。
非 js/wasm 平台的占位:让 `go build ./...` / `go vet ./...` 在本机不至于因 "build constraints exclude all Go files" 报错。
Package compiler 将 AST 编译为字节码。
Package compiler 将 AST 编译为字节码。
Package config 定义并解析 Gox 工程的项目级配置文件 gox.json。
Package config 定义并解析 Gox 工程的项目级配置文件 gox.json。
gfx
Package gfx 提供 Gox 的自研 GUI 渲染层 (P2: Windows 软件渲染)。
Package gfx 提供 Gox 的自研 GUI 渲染层 (P2: Windows 软件渲染)。
backend
Package backend 按构建平台选择窗口后端: 此文件在 Linux 构建时 注册 gfx/x11 (纯 Go X11 协议绑定 jezek/xgb, 无 cgo)。
Package backend 按构建平台选择窗口后端: 此文件在 Linux 构建时 注册 gfx/x11 (纯 Go X11 协议绑定 jezek/xgb, 无 cgo)。
bind
Package bind 是 gfx 的 JS 绑定层: 把内核能力以 object.RegisterBuiltinModule 注册成脚本可 import 的 "gx/xxx" 模块。
Package bind 是 gfx 的 JS 绑定层: 把内核能力以 object.RegisterBuiltinModule 注册成脚本可 import 的 "gx/xxx" 模块。
cocoa
Package cocoa 是 gfx 的 macOS 窗口后端 —— 基于 purego (纯 Go, 无 cgo) 的 objc runtime 桥, 直接驱动 AppKit。
Package cocoa 是 gfx 的 macOS 窗口后端 —— 基于 purego (纯 Go, 无 cgo) 的 objc runtime 桥, 直接驱动 AppKit。
gpu
Package gpu 的默认轨: 空实现。
Package gpu 的默认轨: 空实现。
hosttest
Package hosttest 是「宿主契约」的**平台无关**测试套件 (无 build tag)。
Package hosttest 是「宿主契约」的**平台无关**测试套件 (无 build tag)。
mobile
Package mobile 是移动端 (Android / iOS) 后端的**可移植内核部分**: 事件队列、触摸→指针映射、帧上传钩子、显示器 (density) 上报、事件泵唤醒。
Package mobile 是移动端 (Android / iOS) 后端的**可移植内核部分**: 事件队列、触摸→指针映射、帧上传钩子、显示器 (density) 上报、事件泵唤醒。
softmedia
Package softmedia 是 gfx 的**软件媒体后端** —— 用纯 Go 标准库可解码的 格式, 把 `<video>` / `<audio>` 从"契约 + 降级"变成"真的能播"。
Package softmedia 是 gfx 的**软件媒体后端** —— 用纯 Go 标准库可解码的 格式, 把 `<video>` / `<audio>` 从"契约 + 降级"变成"真的能播"。
x11
多显示器枚举 (factory 级 displayProvider 的 x11 实现, M4 2026-10-02)。
多显示器枚举 (factory 级 displayProvider 的 x11 实现, M4 2026-10-02)。
Package object 定义了 JavaScript 运行时中的所有值类型。
Package object 定义了 JavaScript 运行时中的所有值类型。
jsbuild 将 JavaScript 文件打包成独立的可执行程序。
jsbuild 将 JavaScript 文件打包成独立的可执行程序。
Package permissions 实现 Gox 的脚本权限模型(v1,Deno 风格)。
Package permissions 实现 Gox 的脚本权限模型(v1,Deno 风格)。
Package runtime 提供 JavaScript 运行时的基础组件: - Environment: 作用域环境链 (块作用域 + const 检查) - Iterator: 迭代器协议 (for...of)
Package runtime 提供 JavaScript 运行时的基础组件: - Environment: 作用域环境链 (块作用域 + const 检查) - Iterator: 迭代器协议 (for...of)
Package scaffold 是 Gox 自带的项目脚手架 —— 对标前端的 `npm create vite`: 一条命令铺出一个能直接跑起来的默认 GUI 工程。
Package scaffold 是 Gox 自带的项目脚手架 —— 对标前端的 `npm create vite`: 一条命令铺出一个能直接跑起来的默认 GUI 工程。
gx/crypto: 摘要与随机数的**最小可用集** (看板 ru3TZK)。
gx/crypto: 摘要与随机数的**最小可用集** (看板 ru3TZK)。
test
bench command
tools
bcdump command
bcdump 是调试工具: 编译一个 JS 源文件并反汇编主程序字节码。
bcdump 是调试工具: 编译一个 JS 源文件并反汇编主程序字节码。
certgen
Package certgen 为 `gox cert` 提供纯 Go 的证书生成能力。
Package certgen 为 `gox cert` 提供纯 Go 的证书生成能力。
icongen
Package icongen 负责"单源图标 + 平台资源"的全部纯 Go 生成逻辑:
Package icongen 负责"单源图标 + 平台资源"的全部纯 Go 生成逻辑:
initpurity command
Command initpurity 是「包级 init() 不得做平台 IO / 宏内核调用」的静态闸门。
Command initpurity 是「包级 init() 不得做平台 IO / 宏内核调用」的静态闸门。
perf
Package perf 是 M9「并发与性能」的**可复现性能基准设施**。
Package perf 是 M9「并发与性能」的**可复现性能基准设施**。
propdump command
Command propdump 从 gfx/*.go 的**属性读取点**反推「内核认得哪些属性 / 事件」, 供 scripts/gen-props-golden.py 生成 docs/props.golden.json(看板 rgQsDD)。
Command propdump 从 gfx/*.go 的**属性读取点**反推「内核认得哪些属性 / 事件」, 供 scripts/gen-props-golden.py 生成 docs/props.golden.json(看板 rgQsDD)。
rssmon command
tools/rssmon —— GUI 常驻进程 RSS 曲线采样器(r95WjJ)。
tools/rssmon —— GUI 常驻进程 RSS 曲线采样器(r95WjJ)。
tstransform
Package tstransform 把 TypeScript / TSX 源码转译为等价的 JS 源码。
Package tstransform 把 TypeScript / TSX 源码转译为等价的 JS 源码。
update
Package update 实现 Gox 桌面应用的自动更新 (gx/update v1.1)。
Package update 实现 Gox 桌面应用的自动更新 (gx/update v1.1)。
Package vm 实现了 JavaScript 的栈式字节码虚拟机。
Package vm 实现了 JavaScript 的栈式字节码虚拟机。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL