ESM自文档化模块系统通过语义化导出、$$meta元数据、可执行类型契约和links模块图谱四方面实现逻辑自描述。模块用分组命名导出、嵌入$$meta.id/purpose/dependencies/lifecycle、配套validateInput/sampleInput等契约函数,并通过links.provides/requires构建可验证的语义依赖图。

要基于 ESM(ECMAScript Modules)构建具备“逻辑自描述能力”的自文档化模块系统,核心不是堆砌注释或生成外部文档,而是让模块自身的结构、导出、依赖和行为语义在运行时可被程序化识别与推断。这需要从模块设计、导出约定、类型契约和轻量元数据四方面协同实现。
用命名与导出结构表达意图
ESM 的 export 语法本身即是一种接口契约。避免扁平导出,改用具名分组与语义化命名:
- 将功能按职责分组导出,例如:
export const validators = { email, phone, password },而非零散导出emailValidator、phoneValidator - 对配置类模块,统一导出
schema(JSON Schema 片段)和defaults,使消费方能静态分析其输入约束 - 业务逻辑模块导出
inputShape和outputShape类型对象(Plain Object,非 TypeScript 类型),内容为字段名、类型提示、必选性等键值对
嵌入轻量运行时元数据
不依赖外部工具链,而是在模块顶层添加一个标准属性 $$meta(双美元前缀表明其为内部协议字段):
-
$$meta.id:模块唯一标识(如"payment/processor/stripe-v4") -
$$meta.purpose:一句话说明本模块解决什么问题(如"处理 Stripe Webhook 签名验证与事件路由") -
$$meta.dependencies:显式列出所依赖的其他模块 ID(非 import 语句,而是逻辑依赖,如["crypto/hmac", "http/middleware"]) -
$$meta.lifecycle:标注是否含副作用("pure"/"init-once"/"stateful")
该对象在模块加载后即可通过 import('./mod.js').then(m => m.$$meta) 访问,支持构建时扫描或运行时调试面板自动读取。
用类型契约替代注释文档
放弃 JSDoc 描述参数,转而导出可执行的类型断言函数和示例数据:
- 每个主函数导出配套的
validateInput和describeOutput函数,接收任意值并返回{ ok: boolean; error?: string } - 导出
sampleInput和sampleOutput常量,内容为真实结构的最小可行实例(JSON 可序列化) - 对异步模块,导出
expectedEvents数组,列举该模块可能触发的事件名及其 payload 结构示意
构建可组合的模块图谱
利用 ESM 动态导入与 import.meta.url,实现模块间关系的自动发现:
- 每个模块默认导出一个
links对象,声明它“向上提供”哪些能力(provides: ["auth/token-issuer"])和“向下需要”哪些能力(requires: ["storage/key-value"]) - 编写一个轻量
resolveModuleGraph()工具函数,递归解析模块的links.requires并定位对应模块,最终生成一个带语义标签的有向图 - 该图可直接渲染为可视化拓扑,也可用于运行时校验——例如启动时检查所有
requires是否已被满足,未满足则抛出带上下文的错误
这种设计不增加运行时负担,所有元数据均为 plain object,不依赖反射或装饰器;同时天然兼容 tree-shaking,因为元数据本身不参与执行逻辑。关键在于把“文档”变成模块接口的一部分,而不是附着在接口之外。

















