feat(esm): 按目录加载脚本、打包与热更新
把一个目录当脚本仓库:按路径寻址、esbuild 打包、内容变了自动重编。
- Loader 扫目录建索引,Load(name) 给出打好包的源码和版本号
- bundler esbuild 的封装。ESM 格式而不是 IIFE——IIFE 会附带一整套
CommonJS interop helper,每建一个 VM 都要重跑一遍
- plugin 把扩展的 TS 模块变成可以 import 的虚拟模块,磁盘上没有文件
- typings 把这些虚拟模块的类型按 node_modules 布局落盘,编辑器才认识
esbuild 原生实现了 Node 的模块解析,所以脚本能直接 import node_modules
里的第三方库。写出来的类型文件是 index.ts 而不是 index.d.ts:扩展给的是
真正的模块源码,里面可能带实现,声明文件里不允许有实现。
node_modules 不参与热更新的版本计算——依赖包是装出来的,改动总伴随显式的
安装动作,而真实的 npm 包动辄上千个文件,每次取脚本 stat 一遍太贵。
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package esm
|
||||
|
||||
import (
|
||||
"git.fsdpf.net/go/jscriptx"
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
)
|
||||
|
||||
// extNamespace 是扩展模块在 esbuild 里的命名空间。这些模块不在磁盘上,
|
||||
// 靠插件在解析阶段临时提供,所以要跟真实文件区分开。
|
||||
const extNamespace = "jscriptx-ext"
|
||||
|
||||
// extensionPlugin 把扩展的 TS 模块变成可以 import 的虚拟模块:
|
||||
//
|
||||
// import store from "@jscriptx/store"
|
||||
//
|
||||
// 磁盘上没有这个文件,路径由扩展自己的 Module() 给出,源码也是它提供的。
|
||||
// 模块本身只是个门面——把全局对象转发出来并附上类型声明,真正的实现在 Go 侧,
|
||||
// 由 jscriptx 在建 VM 时注入。
|
||||
//
|
||||
// 拿不到扩展实例时(比如脚本 import 了但 ctx 里没带这个扩展),打包阶段不会报错,
|
||||
// 因为模块只声明全局;真正缺东西要到运行时才暴露——这跟直接用全局变量的行为一致。
|
||||
func extensionPlugin(exts []jscriptx.Extension) api.Plugin {
|
||||
modules := make(map[string]string, len(exts))
|
||||
for _, ext := range exts {
|
||||
path, source := ext.Module()
|
||||
if path == "" || source == "" {
|
||||
continue
|
||||
}
|
||||
modules[path] = source
|
||||
}
|
||||
|
||||
return api.Plugin{
|
||||
Name: "jscriptx-extensions",
|
||||
Setup: func(build api.PluginBuild) {
|
||||
// 认领扩展的模块路径,交给下面的 OnLoad 提供内容
|
||||
build.OnResolve(api.OnResolveOptions{Filter: `.*`}, func(args api.OnResolveArgs) (api.OnResolveResult, error) {
|
||||
if _, ok := modules[args.Path]; !ok {
|
||||
return api.OnResolveResult{}, nil // 不是扩展模块,让 esbuild 照常解析
|
||||
}
|
||||
return api.OnResolveResult{Path: args.Path, Namespace: extNamespace}, nil
|
||||
})
|
||||
|
||||
build.OnLoad(api.OnLoadOptions{Filter: `.*`, Namespace: extNamespace},
|
||||
func(args api.OnLoadArgs) (api.OnLoadResult, error) {
|
||||
source := modules[args.Path]
|
||||
return api.OnLoadResult{Contents: &source, Loader: api.LoaderTS}, nil
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user