fix(web): include the HMR receiver in the initial client graph
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/boot/cmdline/README.md
|
||||
README.md: dc267080d32d492e132df4592ddf742454a95ad2
|
||||
README.zh.md: e183156ab4a7907f8ae1e3259b2e09d458cec47d
|
||||
README.md: 571ea7acf9f7be1ee2bdadafae2fc71b99d4536a
|
||||
README.zh.md: 271acd6be4d58bf12d41bc02dd3ccabc7359a269
|
||||
|
||||
@@ -56,7 +56,7 @@ Every row the app configures from flags then reads what the startup row resolved
|
||||
|
||||
Loader defers a row's `!!js` interpolation until that row's declared injections are active, then evaluates against the row's plugin context. The example above can therefore read `ctx.webStartup` directly: Cordis has already populated that injected service before Loader asks for `webserver`'s config. Include trees preserve nested expression nodes until each target row reaches this point. Provider replacement and live patch reload repeat interpolation against the current injected services, so a launch flag cannot be silently reset.
|
||||
|
||||
`enableRow(ctx, id)` turns on a row a bundle ships disabled because only some invocations want it (`dsh web --dev` and its client-plugin reload chain). Loader applies the enabled row's ordinary injection ordering.
|
||||
`enableRow(ctx, id)` turns on a row a bundle ships disabled because only some invocations want it (`dsh web --dev` and its client-plugin reload chain). The activation is an in-memory override: it does not rewrite the row's configured `disabled` value and survives config reapplication for that mounted entry. Loader applies the enabled row's ordinary injection ordering.
|
||||
|
||||
### One command line, one owner
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ Loader 行的注入同时也是发现声明,因此无需组合包 manifest 字
|
||||
|
||||
Loader 会把一行的 `!!js` 插值推迟到该行声明的注入全部激活之后,再基于该行的插件上下文求值。所以上例可以直接读取 `ctx.webStartup`:Loader 索取 `webserver` 的配置之前,Cordis 已经填入了这个注入服务。Include 树会保留嵌套表达式节点,直到各个目标行到达这一时点。提供方替换与活动 patch 重载都会针对当前注入服务重新插值,因此启动 flag 不会被悄悄重置。
|
||||
|
||||
`enableRow(ctx, id)` 打开某个组合包以禁用状态交付、只有部分调用才需要的行(`dsh web --dev` 及其客户端插件重载链路)。Loader 会对启用后的行应用普通的注入顺序。
|
||||
`enableRow(ctx, id)` 打开某个组合包以禁用状态交付、只有部分调用才需要的行(`dsh web --dev` 及其客户端插件重载链路)。该激活是内存中的覆盖:它不会改写行所配置的 `disabled` 值,并会在已挂载条目的配置重新应用后继续生效。Loader 会对启用后的行应用普通的注入顺序。
|
||||
|
||||
### 一条命令行,一个所有者
|
||||
|
||||
|
||||
@@ -221,6 +221,8 @@ export function runStartup<T>(
|
||||
* A row cannot be inserted from inside a mounting plugin — the Loader returns a
|
||||
* prefixed id it then fails to resolve — so a conditional row ships disabled
|
||||
* and a row mounted beside it enables it after startup resolves the invocation.
|
||||
* The Loader keeps that activation in memory, separate from serialized options,
|
||||
* so reapplying the composition cannot restore the invocation's row to disabled.
|
||||
* @param ctx - plugin context whose Loader tree carries the row.
|
||||
* @param id - the row id.
|
||||
* @returns nothing once the row has started or is waiting for its dependencies.
|
||||
@@ -231,7 +233,7 @@ export async function enableRow(ctx: Context, id: string): Promise<void> {
|
||||
if (loader === undefined) throw new Error('dsh-cmdline: enabling a row requires the Loader service')
|
||||
const entry = [...loader.entries()].find(candidate => candidate.options.id === id)
|
||||
if (entry === undefined) throw new Error(`dsh-cmdline: the composition has no ${JSON.stringify(id)} row to enable`)
|
||||
await entry.update({ disabled: false })
|
||||
await entry.enableRuntime()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -234,17 +234,63 @@ describe('enableRow', () => {
|
||||
await expect(enableRow(withoutLoader, 'client-hmr')).rejects.toThrow('requires the Loader service')
|
||||
|
||||
const ctx = new Context()
|
||||
let update: unknown
|
||||
let enabled = false
|
||||
ctx.provide('loader', {
|
||||
entries: () => [{
|
||||
options: { id: 'client-hmr' },
|
||||
update: async (options: unknown) => { update = options },
|
||||
enableRuntime: async () => { enabled = true },
|
||||
}],
|
||||
} as never)
|
||||
await enableRow(ctx, 'client-hmr')
|
||||
expect(update).toEqual({ disabled: false })
|
||||
expect(enabled).toBe(true)
|
||||
await expect(enableRow(ctx, 'absent')).rejects.toThrow('no "absent" row to enable')
|
||||
})
|
||||
|
||||
it('keeps invocation-only activation through config reapplication', async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'dsh-runtime-enable-'))
|
||||
const observed = { starts: 0, stops: 0 }
|
||||
;(globalThis as unknown as { __runtimeEnableObserved: typeof observed }).__runtimeEnableObserved = observed
|
||||
writeFileSync(join(dir, 'conditional.mjs'), `
|
||||
export function apply(ctx) {
|
||||
globalThis.__runtimeEnableObserved.starts += 1
|
||||
ctx.effect(() => () => { globalThis.__runtimeEnableObserved.stops += 1 })
|
||||
}
|
||||
`)
|
||||
writeFileSync(join(dir, 'cordis.yml'), [
|
||||
'- id: conditional',
|
||||
` name: ${pathToFileURL(join(dir, 'conditional.mjs')).href}`,
|
||||
' disabled: true',
|
||||
'',
|
||||
].join('\n'))
|
||||
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(Loader)
|
||||
ctx.loader.builtins.include = Include
|
||||
await ctx.loader.create({
|
||||
name: 'cordis:include',
|
||||
config: { path: pathToFileURL(join(dir, 'cordis.yml')).href },
|
||||
})
|
||||
await ctx.loader.await()
|
||||
const conditional = [...ctx.loader.entries()].find(entry => entry.options.id === 'conditional')
|
||||
const include = [...ctx.loader.entries()].find(entry => entry.options.name === 'cordis:include')
|
||||
expect(conditional).toBeDefined()
|
||||
expect(include?.fiber).toBeDefined()
|
||||
expect(conditional?.options.disabled).toBe(true)
|
||||
expect(observed).toEqual({ starts: 0, stops: 0 })
|
||||
|
||||
await enableRow(ctx, 'conditional')
|
||||
await ctx.loader.await()
|
||||
expect(conditional?.disabled).toBe(false)
|
||||
expect(conditional?.options.disabled).toBe(true)
|
||||
expect(observed).toEqual({ starts: 1, stops: 0 })
|
||||
|
||||
await include!.fiber!.update(include!.options.config, true)
|
||||
await ctx.loader.await()
|
||||
expect(conditional?.disabled).toBe(false)
|
||||
expect(conditional?.options.disabled).toBe(true)
|
||||
expect(observed).toEqual({ starts: 1, stops: 0 })
|
||||
disposers.push(async () => { await ctx.fiber.dispose() })
|
||||
})
|
||||
})
|
||||
|
||||
describe('provideCmdline', () => {
|
||||
|
||||
Reference in New Issue
Block a user