Loading and Discovery
Plugin loading happens in three layers:
- Loader (
vendor/loader) service manages the entry tree + Node's built-in ModuleLoader (--expose-internals) for ESM/CJS imports - Include plugin parses
cordis.patch.yml(including!!jsexpressions) dsh pluginCLI reconciles pnpm package installation with the bundle layer
The Loader hooks onto the internal/plugin / internal/config / internal/update events, automatically handling fiber.entry binding, config interpolation, and reload write-back.
Loader Service
class Loader extends EntryTree at vendor/loader/src/index.ts:65-100:
internal = ModuleLoader.fromInternal()— get Node's internal ESM loaderbuiltinsdict for builtin plugins- On construction it registers three
internal/*hooks: config interpolation, update write-back, and plugin lifecycle logging
unwrapExports: Unifying Three Shapes
unwrapExports(exports) at vendor/loader/src/index.ts:192-199 handles the dual interop of default / __esModule, unifying ESM / CJS / default-export shapes into a single plugin object.
Node's Built-in ModuleLoader
dsh uses --expose-internals to get at Node's internal ESM loader — this is the prerequisite for fine-grained control over module loading, including HMR cache cleanup.
V1 vs V2
vendor/loader/src/internal.ts:55-102 defines the ModuleLoaderV1 (Node 22/23) and ModuleLoaderV2 (Node 24+) interfaces, with field differences clearly annotated:getModuleJobForImport→getOrCreateModuleJobresolvebecomes private- etc.
ModuleLoader.fromInternal() at vendor/loader/src/internal.ts:120-132: Node major ≥ 24 takes v2, ≥ 22 takes v1, obtaining the cascaded loader of internal/modules/esm/loader via --expose-internals or node-addon-require-builtin.
dsh plugin CLI: pnpm Reconciliation
Is It a Bundle Layer?
exportsPatch(packageName, profileDir) at apps/cli/src/plugin.ts:36-45:
- Resolves the bundle dir.
- Reads the manifest.
- Checks whether
dsh.bundle.patchexists — i.e. "is it a bundle layer".
reconcilePlugins
reconcilePlugins(before, profileDir) at apps/cli/src/plugin.ts:59-91:
After pnpm finishes, diffs the before/after dependencies:
- A new dependency that is a bundle → push into
dsh.profile.bundles - A removed dependency, or a new version that no longer declares a bundle → splice out
Path Anchoring
anchorPathSpec(argument, cwd) at apps/cli/src/plugin.ts:104-112: rewrites . / .. / file: / link: relative specs against the caller's cwd, to avoid self-reference inside the profile directory.
The Entry Structure of patch.yml
packages/bundle/base/cordis.patch.yml:15-45 is the base bundle's insert list; each line:- id: <stable> # stable identity
name: '<pkg or path>' # module locator
config: ... # optional
disabled: !!js ... # optional; the only interpolated metadata fieldKey Mechanisms
1. entry id Is a Stable Identity
A Loader entry's id is a stable identity: when the config file changes the loader diffs by id and only mounts / unmounts / reconfigures the changed lines; an entry without an id gets a fresh id on every read and is treated as "delete + add".
2. disabled: !!js Routing
disabled: !!js <expr> is the only interpolated metadata field; it is evaluated against the loader context on every mount decision, so platform / environment differences (e.g. process.platform === 'win32') are routed here.
3. internal/config Hook for Interpolation
The Loader internal/config hook does the interpolation: the config of tree carriers (Group / Include) stays literal, while the config of leaf entries goes through interpolate(ctx, config) to resolve !!js.
4. Loader.Intercept.await
Loader.Intercept.await keeps plugins that depend on the loader pending while the entry tree still has unfinished work — preventing plugins from seeing an incomplete registry early in boot.
5. Profile Is a Real pnpm Workspace
dsh plugin add is pnpm add; when pnpm ≥ 10 blocks the prepare script of a git dependency, the CLI prompts the user to add allowBuilds. Plugin management fully reuses pnpm's dependency graph.
What to read next
- Dependency Resolution and Ordering — how fibers wait for service availability
- Unloading and Hot Reload — how file changes trigger reload