Skip to content
源码版本47f9438 (dsh@0.1)

加载与发现

插件加载分三层:

  1. Loader(vendor/loader)服务管理 entry 树 + Node 内置 ModuleLoader(--expose-internals)做 ESM/CJS 导入
  2. Include 插件解析 cordis.patch.yml(含 !!js 表达式)
  3. dsh plugin CLI 把 pnpm 包安装与 bundle layer 对账

Loader 在 internal/plugin/internal/config/internal/update 事件上挂钩,自动处理 fiber.entry 绑定、config 插值、reload 写回。

Loader 服务

vendor/loader/src/index.ts:65-100class Loader extends EntryTree:
  • internal = ModuleLoader.fromInternal() — 拿 Node 内部 ESM loader
  • builtins dict 给内建 plugin
  • 构造时注册三个 internal/* 钩子:config 插值、update 写回、plugin 生命周期日志

unwrapExports:统一三种 shape

vendor/loader/src/index.ts:192-199unwrapExports(exports) 处理 default/__esModule 双重 interop,让 ESM/CJS/default-export 三种 shape 统一成 plugin 对象。

Node 内置 ModuleLoader

dsh--expose-internals 拿到 Node 内部的 ESM loader,这是它能精细控制模块加载(包括 HMR 的缓存清理)的前提。

V1 vs V2

vendor/loader/src/internal.ts:55-102 定义了 ModuleLoaderV1(Node 22/23)与 ModuleLoaderV2(Node 24+)接口,字段差异标注清楚:
  • getModuleJobForImportgetOrCreateModuleJob
  • resolve 变 private
  • 等等
vendor/loader/src/internal.ts:120-132ModuleLoader.fromInternal():Node major ≥24 走 v2,≥22 走 v1,通过 --expose-internalsnode-addon-require-builtininternal/modules/esm/loader 的 cascaded loader。

dsh plugin CLI:pnpm 对账

是否是 bundle layer

apps/cli/src/plugin.ts:36-45exportsPatch(packageName, profileDir):
  1. 解析 bundle dir
  2. 读 manifest
  3. 判定 dsh.bundle.patch 是否存在——即"是不是一个 bundle layer"

reconcilePlugins

apps/cli/src/plugin.ts:59-91reconcilePlugins(before, profileDir):

pnpm 跑完后对比 before/after 依赖:

  • 新依赖且是 bundle → push 进 dsh.profile.bundles
  • 依赖移除或新版不再声明 bundle → splice 出

路径锚定

apps/cli/src/plugin.ts:104-112anchorPathSpec(argument, cwd):把 ./../file:/link: 相对 spec 重写到调用者 cwd,避免在 profile 目录里自指。

patch.yml 的 entry 结构

packages/bundle/base/cordis.patch.yml:15-45 是 base bundle 的 insert 列表,每行:
yaml
- id: <stable>          # 稳定身份
  name: '<pkg or path>'  # 模块定位
  config: ...            # 可选
  disabled: !!js ...     # 可选,唯一被插值的元数据字段

关键机制

1. entry id 是稳定身份

Loader entry 的 id 是稳定身份:config 文件改后 loader 按 id diff,只 mount/unmount/reconfigure 变化的行;无 id 的 entry 每次读都生成新 id 被视为"删+加"。

2. disabled: !!js 分流

disabled: !!js <expr> 是唯一被插值的元数据字段,每次 mount 决策时对 loader context 求值——平台/环境差异(如 process.platform === 'win32')在此分流。

3. internal/config 钩子做插值

Loader internal/config 钩子做插值:tree carrier(Group/Include)的 config 保持 literal,叶子 entry 的 config 走 interpolate(ctx, config) 解析 !!js

4. Loader.Intercept.await

Loader.Intercept.await 让依赖 loader 的插件在 entry tree 还有未完成任务时保持 pending——避免插件在 boot 早期看到不完整的注册表。

5. Profile 是真实 pnpm workspace

dsh plugin addpnpm add,git 依赖的 prepare 脚本被 pnpm ≥10 阻止时 CLI 会提示用户加 allowBuilds。插件管理完全复用 pnpm 的依赖图。

接下来读什么