package esm import ( "fmt" "sync" "git.fsdpf.net/go/jscriptx" "github.com/evanw/esbuild/pkg/api" ) // outDir 只是给 esbuild 算相对路径用的虚拟目录,不落盘。 const outDir = "__jsx_out" // defaultTarget 是打包输出的默认 ECMAScript 版本。goja 对更新的语法覆盖不全, // 所以不跟着 esnext 走。Loader 和 Vendor 共用它——摊平出来的库要跟脚本同一档, // 否则库能打出脚本引擎跑不了的语法。 const defaultTarget = api.ES2017 // DefaultGlobs 是默认的入口规则:根目录第一层子目录下的 js/ts 文件。 // 对应 app/PkgVersion/PkgImportController.ts 这样的结构。 var DefaultGlobs = []string{"*/*.js", "*/*.ts", "*/*.mjs"} // Loader 从一个目录加载 ESM/TypeScript 源码,打包成 goja 能执行的代码。 // 它实现 jscriptx.Loader,交给 jscriptx.WithLoader 使用。 type Loader struct { dir string globs []string target api.Target define map[string]string nodePaths []string alias map[string]string exts []jscriptx.Extension label string // WithVersion 给这一层贴的版本标签,调用时用 name@label 点名 mu sync.Mutex bundles map[string]*bundle built bool } // Option 是 NewLoader 的配置项。 type Option func(*Loader) // WithVersion 给这一层贴一个版本标签。多层叠放时,调用点可以用 name@版本 点名 // 要哪一层的实现,不点名还是照旧"上层盖下层": // // v1, _ := esm.NewLoader("app/v1/src", esm.WithVersion("v1")) // v2, _ := esm.NewLoader("app/v2/src", esm.WithVersion("v2")) // e, _ := jscriptx.New(jscriptx.WithLoader(v1, v2)) // // e.New(ctx, "Resource/ResCreateController") // 不点名:拿到 v2(最上层) // e.New(ctx, "Resource/ResCreateController@v1") // 点名:要 v1 那一层的 // // 标签只在叠层里有意义,单个 Loader 用不上;各层的标签不能重名。详见 jscriptx.Overlay。 func WithVersion(v string) Option { return func(l *Loader) { l.label = v } } // Version 返回 WithVersion 贴的版本标签,没贴过是空串。 func (l *Loader) Version() string { return l.label } // WithGlobs 自定义入口规则(相对根目录的 glob,用 / 分隔)。 // 不设时用 DefaultGlobs:第一层子目录下的所有 js/ts 文件。 // // 只作为入口的文件才能按名字取到;被 import 的模块不需要是入口。 func WithGlobs(globs ...string) Option { return func(l *Loader) { l.globs = globs } } // WithExtensions 让脚本能 import 扩展的 TS 模块拿到类型: // // import store from "@jscriptx/store" // // 传进来的扩展只用于**打包阶段**——提供模块源码和类型。运行时真正注入哪些扩展, // 由每次调用的 ctx 决定(jscriptx.WithScope + ScopeExtensions)。所以这里传的 // 应该是"脚本可能用到的全部扩展",通常跟运行时那份是同一批。 // // 不传也能用扩展,只是脚本得直接写全局变量(没有类型提示)。 func WithExtensions(exts ...jscriptx.Extension) Option { return func(l *Loader) { l.exts = append(l.exts, exts...) } } // WithTarget 设置输出的 ECMAScript 版本,默认 ES2017。 // goja 对 ES6+ 的覆盖不是 100%,遇到脚本里用了 goja 不认识的语法时, // 调低这个值让 esbuild 把它降级掉。 func WithTarget(t api.Target) Option { return func(l *Loader) { l.target = t } } // WithDefine 设置编译期常量替换,比如 WithDefine(map[string]string{"__DEV__": "false"})。 func WithDefine(define map[string]string) Option { return func(l *Loader) { l.define = define } } // WithNodePaths 指定额外的 node_modules 搜索目录,相当于 Node 的 NODE_PATH。 // // 不设时按 Node 的默认规则来:从 import 所在文件的目录逐级往上找 node_modules, // 所以公共库放在加载目录里(app/node_modules)或它的任意上层目录都能被找到。 // 只有公共库放在完全不相干的路径下时才需要这个选项: // // esm.NewLoader("app", esm.WithNodePaths("/opt/fsdpf/js-libs")) // // 传进来的目录本身相当于一个 node_modules:包直接放在它下面(