@ttsc/wasm
@ttsc/wasm(packages/wasm)是浏览器内 ttsc playground 的基础:一个 Go host 包(编译成 WebAssembly)加 JS 引导脚手架。它让 ttsc 的 build/check/transform/插件能在浏览器里跑,无需后端。
两半结构
JS 侧(src/index.ts)re-export boot helper、MemFS 桥、类型表面。Go 侧 host/ 随包 ship 在磁盘上但经 go build 加载,不从 JS import。
host.Expose:JS 桥接
Expose(apiName, cfg)(host/host.go:54)在 globalThis[apiName] 安装标准 verb 加 fountain verb,然后让 Go 运行时永久存活:
加上 fountain verb(snapshot/getDiagnostics/getNodeAtPosition/…,fountainAPIMap()),在 snapshot handle 表上工作。
几个 wasm 特定设计:
- 拒绝双 Expose(
host.go:65):第二次调用会泄漏第一批js.FuncOf(Go pin 住 js.Func 不 GC)、起第二个 keepalive goroutine、覆盖 ready resolver。所以用exposed atomic.BoolCAS 守卫,失败时 console.error + JS 可见的 reject 信号(globalThis[apiName+"Failed"])而非 panic——panic 会在任何 Ready resolver 触发前终止 Go 运行时,让bootTtsc的await ready无限挂起。 - 永久 idle goroutine(
host.go:111):for { time.Sleep(time.Hour) }让 wasm 运行时存活而不触发 Go 死锁检测器——select {}会在 FS 路径把请求交给 JS 那刻被误判为"所有 goroutine 睡眠"。
Promise 桥接
makePromise(host.go:261)把 Go 计算包成 JS Promise:executor 同步捕获 resolve/reject,工作本身在 goroutine 里跑,让 JS 事件循环能在 Go 收回调前驱动 fs.stat 等到完成。
stdout/stderr 捕获
wasm 里 syscall.Pipe 返回 pipe: not implemented on js——wasm 目标不支持管道。所以 runWithCapturedIO(host.go:325)用 MemFS 临时文件重定向 os.Stdout/os.Stderr:插件 Run 像原生旁车那样写 stdout/stderr,捕获后让 JS 宿主在 console 面板渲染,无需 spawn 子进程。captureMu 串行化临时替换进程全局 stdout/stderr,captureCounter 避免并发分派的临时文件名碰撞。
MemFS
createMemFS.ts + MemFSError.ts 实现内存文件系统桥,让浏览器里没有真实磁盘也能 open/write/read。这是 runWithCapturedIO 临时文件方案能工作的基础。
boot 流程
bootTtsc.ts 启动 wasm:JS 在 go.run 前注册 globalThis[${apiName}Ready] resolver,等 wasm boot(Expose 末尾 invoke ready resolver)。parseResult.ts 解析 ITtscResult 信封({ code, stdout, stderr, result },result 是 build/check/transform 的 JSON,JS 侧 JSON.parse)。
API 稳定性
README 与 host.go:35 标注:实验性,v1.0 前签名可能在 minor 版本间变。生产 playground 要 pin 精确版本。
不变量
Expose每个 wasm 实例至多调一次(CAS 守卫)。- 永久 idle goroutine 防 Go 死锁检测器误杀。
- stdout/stderr 经 MemFS 临时文件捕获(wasm 无管道)。
- build/check/transform 与 plugin 分派共享
{ code, stdout, stderr, result }形状,JS 一个错误分支处理。
维护者提示
- 别引入第二个
Expose路径——双 Expose 是 wasm 资源泄漏。 - 改 stdout 捕获时记住 wasm 无
os.Pipe,必须走 MemFS。 host_native.go是非 wasm 构建占位,让包在非 wasm 平台也能编译。