Files
jscriptx/errors_hints.go
T
what 7e1893b246 refactor: 文件按主类型划分,一个 struct 一个功能
约定:文件名 = 主类型名;同一个类型要拆多个文件时用 类型_子项.go。

  engine.go          355 → 231 行。原来混了三件不相干的事
  engine_globals.go  ← bind / lazyGlobal / freeze / defineReadOnly(96 行)
                       它是「Go 值 → 只读 JS 全局」的转换层,跟脚本缓存毫无关系
  engine_console.go  ← console.go,跟上面是同一主题
  script.go          325 → 100 行,只留公开方法
  script_vm.go       ← VM 的取、还、装载。上个 commit 合一的三条路径现在住一起
  script_static.go   ← static.go
  errors.go          364 → 150 行
  errors_goja.go     ← goja 错误的翻译层
  errors_hints.go    ← missingGlobalHint,一份 JS 运行时知识库,跟错误分类是
                       两回事;拆出来之后 missing_global_test.go 才有对应源文件
  bundle_finalize.go ← esmwrap.go
  bundle.go          收下 validIdent / hashVersion(原来住在 engine.go)

Engine.New 原来排在所有私有函数之后,挪到导出方法那一段。

classify 83 → 54 行:四个 errors.As 分支各手搓一个 8 字段的 &Error{},脚本上下文
那三行重复了 4 遍,抽出 gojaError 构造器。

顺带修四处注释漂移:

  Session/会话   代码里叫 Instance,注释里大面积残留。engine_globals.go 那条
                 「注入会话全局对象失败」还是用户可见文案,而公开 API 里根本
                 没有「会话」这个概念
  Extension      文档示例写 Module() string,接口是 Module() (path, source string)。
                 这是唯一一段教人写扩展的文档,照抄编译不过
  doc.go 的 freeze  说白名单「逐层拷贝成只读对象,不会跨 VM 共享可变的 Go map」,
                 但那只对 map[string]any 成立。结构体指针和 slice 是**共享同一个
                 对象**的——扩展走的正是这条路,不该被当成隔离保证
2026-09-10 15:28:59 +08:00

65 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package jscriptx
import "strings"
// 脚本作者最容易撞上的那几个「以为一定有、其实 goja 没有」的全局,
// 在 ReferenceError 后面补一句人话。
//
// 为什么是补错误信息、而不是把这些全局定义成「一调就报错」的桩:库里
// `typeof setTimeout !== "undefined"` 这种特性探测很常见,一旦定义了,
// 探测就会走进定时器分支,本来能优雅降级的库反而被弄坏。让它保持 undefined,
// 探测正常工作;真直接调用了,就在错误里说清楚。
// missingGlobalHint 针对几个"goja 没有、但脚本作者以为一定有"的全局,
// 在 ReferenceError 后面补一句人话。
//
// 为什么是补错误信息,而不是把这些全局定义成"一调就报错"的桩:
// 库里 `typeof setTimeout !== "undefined"` 这种特性探测很常见,一旦定义了,
// 探测就会走进定时器分支,本来能优雅降级的库反而被弄坏。让它保持 undefined,
// 探测正常工作;真直接调用了,就在错误里说清楚。
//
// 只认两种原样输出:goja 的 "ReferenceError: xxx is not defined"
// 以及 esbuild 给动态 require 埋的那句运行期错误。
func missingGlobalHint(msg string) string {
// require 不会以 ReferenceError 的形式出现——esbuild 在打包期就接管了它:
// 静态的 require("x") 直接当模块解析(解析不到就打包失败),
// 动态的 require(变量) 换成下面这句运行期错误。
if strings.HasPrefix(msg, "Error: Dynamic require of ") {
return "脚本走的是 ES 模块,用 import 而不是 require" +
"而且 import 的路径必须是字面量,不能是变量拼出来的"
}
const prefix = "ReferenceError: "
const suffix = " is not defined"
if !strings.HasPrefix(msg, prefix) || !strings.HasSuffix(msg, suffix) {
return ""
}
name := msg[len(prefix) : len(msg)-len(suffix)]
switch name {
case "setTimeout", "setInterval", "setImmediate",
"clearTimeout", "clearInterval", "clearImmediate":
return "脚本是同步的,没有事件循环——定时器用不了。" +
"要延迟或周期执行,把这段逻辑放到 Go 侧的 Job / Cron" +
"如果是第三方库里的 debounce / throttle 之类,换一个不依赖定时器的实现"
case "structuredClone":
return "goja 没有这个函数。深拷贝用 JSON.parse(JSON.stringify(x))" +
"或者换一个不依赖它的库(remeda 的 clone 就依赖它)"
case "module", "exports":
return "脚本走的是 ES 模块,没有 CommonJS 那套;导出用 export default"
case "process", "Buffer", "__dirname", "__filename":
return "脚本不跑在 Node 里,没有这些东西。要读配置用扩展提供的接口"
case "window", "document", "navigator", "localStorage":
return "脚本不跑在浏览器里,没有 DOM"
case "fetch", "XMLHttpRequest", "WebSocket":
return "脚本里发不了网络请求——那是异步的,而脚本是同步执行的。" +
"要调外部服务,在 Go 侧做好再通过扩展交给脚本"
}
return ""
}