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 "" }