refactor: nest client manifest metadata under dsh

This commit is contained in:
imccyu
2026-08-10 20:39:26 +08:00
parent 854f6623bb
commit 717792b631
52 changed files with 379 additions and 269 deletions

View File

@@ -1,6 +1,6 @@
/**
* Node half of the client module system (dshClient dual-face package): scans
* the host Loader's entries for `dshClient` packages, composes the
* Node half of the client module system (`dsh.client` dual-face package): scans
* the host Loader's entries for packages declaring `dsh.client`, composes the
* `window.__DSH_BOOT__` entry graph (wire single source: {@link WebBootEntry}
* in `./client/manifest.ts`), serves `/plugins/<id>/client.js` and its source
* map, taps the index render to inject the boot manifest, and provides the
@@ -43,7 +43,7 @@ declare module 'cordis' {
}
}
/** package.json `dshClient` declaration fields, validated one by one after reading the file. */
/** package.json `dsh.client` declaration fields, validated one by one after reading the file. */
interface DshClientDeclaration {
inject?: string[]
platform: string
@@ -51,7 +51,7 @@ interface DshClientDeclaration {
immediately?: boolean
}
/** Resolved package metadata for one dshClient package (cached per name, never expires). */
/** Resolved package metadata for one `dsh.client` package (cached per name, never expires). */
interface PkgMeta {
clientPath: string
inject?: string[]
@@ -105,21 +105,21 @@ interface WebPluginRecord {
clientPath: string
}
/** Narrow an unknown parsed JSON value to the dshClient declaration, throwing on malformed fields. */
/** Narrow an unknown parsed JSON value to the `dsh.client` declaration, throwing on malformed fields. */
function parseDshClient(pkgName: string, value: unknown): DshClientDeclaration | undefined {
if (value === undefined) return undefined
if (typeof value !== 'object' || value === null) {
throw new Error(`client-modules: ${pkgName} has a non-object dshClient declaration`)
throw new Error(`client-modules: ${pkgName} has a non-object dsh.client declaration`)
}
const decl = value as Record<string, unknown>
if (typeof decl.platform !== 'string') {
throw new Error(`client-modules: ${pkgName} dshClient.platform must be a string`)
throw new Error(`client-modules: ${pkgName} dsh.client.platform must be a string`)
}
if (decl.inject !== undefined && (!Array.isArray(decl.inject) || decl.inject.some(i => typeof i !== 'string'))) {
throw new Error(`client-modules: ${pkgName} dshClient.inject must be a string array`)
throw new Error(`client-modules: ${pkgName} dsh.client.inject must be a string array`)
}
if (decl.immediately !== undefined && typeof decl.immediately !== 'boolean') {
throw new Error(`client-modules: ${pkgName} dshClient.immediately must be a boolean`)
throw new Error(`client-modules: ${pkgName} dsh.client.immediately must be a boolean`)
}
return {
platform: decl.platform,
@@ -175,7 +175,7 @@ export function injectBootManifest(html: string, graph: WebBootGraph): string {
}
/**
* The web plugin table service: incremental dshClient scan + wire composition
* The web plugin table service: incremental `dsh.client` scan + wire composition
* + bundle route + index tap. Construction runs the activation scan
* synchronously — a malformed declaration or missing bundle among the
* already-loaded entries aggregates into one loud throw (FAILED fiber; the
@@ -186,7 +186,7 @@ export class ClientModuleHostService extends Service {
private readonly table = new Map<string, WebPluginRecord>()
// Negative verdicts (unresolvable specifier — builtins like cordis:include,
// subpath rows — or a package without a web dshClient declaration) are
// subpath rows — or a package without a web `dsh.client` declaration) are
// cached as null and never expire: plugin-set changes take effect on restart.
private readonly pkgMeta = new Map<string, PkgMeta | null>()
private readonly rebuildListeners = new Set<(id: string, rev: string) => void>()
@@ -342,14 +342,18 @@ export class ClientModuleHostService extends Service {
return null
}
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as Record<string, unknown>
const decl = parseDshClient(pkgName, pkg.dshClient)
const dsh = pkg.dsh
const decl = parseDshClient(
pkgName,
dsh !== null && typeof dsh === 'object' ? (dsh as Record<string, unknown>).client : undefined,
)
if (decl === undefined || decl.platform !== 'web') {
this.pkgMeta.set(pkgName, null)
return null
}
const clientRel = clientExportOf(pkgName, pkg.exports)
if (clientRel === undefined) {
throw new Error(`client-modules: ${pkgName} declares dshClient but exports no "./client" bundle`)
throw new Error(`client-modules: ${pkgName} declares dsh.client but exports no "./client" bundle`)
}
const meta: PkgMeta = {
clientPath: join(dirname(pkgPath), clientRel),