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:
2026-09-05 22:13:57 +08:00
parent 7fb8d0f966
commit 6ce9b483fd
34 changed files with 2403 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
import ResExecuter from "./ResExecuter"
import { nextId, tag } from "./shared"
export default class ResCreateController {
private ex: ResExecuter
private count = 0
constructor(private resName: string, private mode: string) {
this.ex = new ResExecuter()
}
Init(): string { return "ResCreate:" + this.resName + "/" + this.mode }
Store(cfg: string): string { this.count++; return this.ex.Store(cfg) + "#" + this.count }
Seq(): string { return tag("create:" + nextId()) }
Boom(): void { throw new Error("创建失败") }
}
+4
View File
@@ -0,0 +1,4 @@
import { nextId, tag } from "./shared"
export default class ResDeleteController {
Seq(): string { return tag("delete:" + nextId()) }
}
+5
View File
@@ -0,0 +1,5 @@
// 被同目录 controller import 的公共类
export default class ResExecuter {
Store(cfg: string): string { return "stored:" + cfg }
Destroy(id: string): string { return "destroyed:" + id }
}
+17
View File
@@ -0,0 +1,17 @@
import { upper } from "tinylib"
import { money } from "@fsdpf/util"
import { bracket } from "@lib/format"
import ResExecuter from "./ResExecuter"
export default class ResSchemaController {
private ex = new ResExecuter()
Describe(name: string, price: number): string {
return upper(name) + " " + money(price) + " " + this.ex.Store("s")
}
// 走 tsconfig 里配的 @lib/* 路径别名
Tag(s: string): string {
return bracket(s)
}
}
+4
View File
@@ -0,0 +1,4 @@
// 公共纯函数 + 一个模块级状态(用来验证状态不跨 controller 共享)
let seq = 0
export function nextId(): number { return ++seq }
export function tag(s: string): string { return "[" + s + "]" }