From 0041327fee6654e842a8097e56c76e12792e4f7f Mon Sep 17 00:00:00 2001 From: Shigma Date: Fri, 23 Feb 2024 01:12:50 +0800 Subject: [PATCH] feat(loader): support `options.intercept` --- packages/hmr/src/index.ts | 3 -- packages/loader/src/shared.ts | 72 +++++++++++++++-------------- packages/loader/tests/index.spec.ts | 26 ++++++++++- 3 files changed, 61 insertions(+), 40 deletions(-) diff --git a/packages/hmr/src/index.ts b/packages/hmr/src/index.ts index 90029dd..f1c4369 100644 --- a/packages/hmr/src/index.ts +++ b/packages/hmr/src/index.ts @@ -301,9 +301,6 @@ class Watcher extends Service { this.ctx.logger.warn(err) } - // replace loader cache for `keyFor` method - this.ctx.loader.replace(plugin, attempts[filename]) - try { for (const oldFork of children) { const fork = oldFork.parent.plugin(attempts[filename], oldFork.config) diff --git a/packages/loader/src/shared.ts b/packages/loader/src/shared.ts index 0afecd2..66cd948 100644 --- a/packages/loader/src/shared.ts +++ b/packages/loader/src/shared.ts @@ -48,6 +48,7 @@ export namespace Entry { name: string config?: any disabled?: boolean + intercept?: Dict when?: any } } @@ -58,26 +59,48 @@ export class Entry { constructor(public loader: Loader, public parent: Context, public options: Entry.Options) {} - stop() { - if (!this.fork) return - this.parent.emit('loader/entry', 'unload', this) - this.fork.dispose() - this.fork = null + amend(ctx: Context) { + for (const key of Reflect.ownKeys(ctx[Context.intercept])) { + delete ctx[Context.intercept][key] + } + Object.assign(ctx[Context.intercept], this.options.intercept) + } + + // TODO: handle parent change + update(parent: Context, options: Entry.Options) { + this.options = options + if (!this.loader.isTruthyLike(options.when) || options.disabled) { + this.stop() + } else { + this.start() + } } async start() { if (this.fork) { this.isUpdate = true + this.amend(this.fork.parent) this.fork.update(this.options.config) } else { this.parent.emit('loader/entry', 'apply', this) const plugin = await this.loader.resolve(this.options.name) if (!plugin) return - const ctx = this.parent.extend() + const intercept = Object.create(this.parent[Context.intercept]) + const ctx = this.parent.extend({ + [Context.intercept]: intercept, + }) + this.amend(ctx) this.fork = ctx.plugin(plugin, this.loader.interpolate(this.options.config)) this.fork.entry = this } } + + stop() { + if (!this.fork) return + this.parent.emit('loader/entry', 'unload', this) + this.fork.dispose() + this.fork = null + } } export namespace Loader { @@ -106,7 +129,6 @@ export abstract class Loader extends public entries: Dict = Object.create(null) private tasks = new Set>() - private store = new WeakMap() abstract import(name: string): Promise @@ -203,22 +225,7 @@ export abstract class Loader extends const task = this.import(name) this.tasks.add(task) task.finally(() => this.tasks.delete(task)) - const plugin = this.unwrapExports(await task) - if (plugin) this.store.set(this.app.registry.resolve(plugin), name) - return plugin - } - - keyFor(plugin: any) { - return this.store.get(this.app.registry.resolve(plugin)) - } - - replace(oldKey: any, newKey: any) { - oldKey = this.app.registry.resolve(oldKey) - newKey = this.app.registry.resolve(newKey) - const name = this.store.get(oldKey) - if (!name) return - this.store.set(newKey, name) - this.store.delete(oldKey) + return this.unwrapExports(await task) } isTruthyLike(expr: any) { @@ -226,7 +233,7 @@ export abstract class Loader extends return !!this.interpolate(`\${{ ${expr} }}`) } - async updateEntry(parent: Context, options: Entry.Options) { + async update(parent: Context, options: Entry.Options) { if (!options.id) { do { options.id = Math.random().toString(36).slice(2, 8) @@ -234,15 +241,10 @@ export abstract class Loader extends } const entry = this.entries[options.id] ??= new Entry(this, parent, options) - entry.options = options - if (!this.isTruthyLike(options.when) || options.disabled) { - entry.stop() - } else { - entry.start() - } + entry.update(parent, options) } - removeEntry(parent: Context, options: Entry.Options) { + remove(parent: Context, options: Entry.Options) { const entry = this.entries[options.id] if (!entry) return entry.stop() @@ -311,7 +313,7 @@ export abstract class Loader extends export function group(ctx: Context, config: Entry.Options[]) { for (const entry of config) { - ctx.loader.updateEntry(ctx, entry) + ctx.loader.update(ctx, entry) } ctx.accept((neo: Entry.Options[]) => { @@ -323,16 +325,16 @@ export function group(ctx: Context, config: Entry.Options[]) { // update inner plugins for (const id in { ...oldMap, ...neoMap }) { if (!neoMap[id]) { - ctx.loader.removeEntry(ctx, oldMap[id]) + ctx.loader.remove(ctx, oldMap[id]) } else { - ctx.loader.updateEntry(ctx, neoMap[id]) + ctx.loader.update(ctx, neoMap[id]) } } }, { passive: true }) ctx.on('dispose', () => { for (const entry of ctx.scope.config as Entry.Options[]) { - ctx.loader.removeEntry(ctx, entry) + ctx.loader.remove(ctx, entry) } }) } diff --git a/packages/loader/tests/index.spec.ts b/packages/loader/tests/index.spec.ts index c89e7b9..684ac3d 100644 --- a/packages/loader/tests/index.spec.ts +++ b/packages/loader/tests/index.spec.ts @@ -14,8 +14,12 @@ describe('@cordisjs/loader', () => { const bar = mock.fn((ctx: Context) => { ctx.accept() }) + const qux = mock.fn((ctx: Context) => { + ctx.accept() + }) root.loader.register('foo', foo) root.loader.register('bar', bar) + root.loader.register('qux', qux) test('basic support', async () => { root.loader.config = [{ @@ -30,6 +34,10 @@ describe('@cordisjs/loader', () => { config: { a: 1, }, + }, { + id: '4', + disabled: true, + name: 'qux', }], }] @@ -40,20 +48,28 @@ describe('@cordisjs/loader', () => { expect(root.registry.get(bar)).to.be.ok expect(root.registry.get(bar)?.config).to.deep.equal({ a: 1 }) expect(bar.mock.calls).to.have.length(1) + expect(root.registry.get(qux)).to.be.not.ok }) test('entry update', async () => { root.loader.config = [{ id: '1', name: 'foo', + }, { + id: '4', + name: 'qux', }] + foo.mock.resetCalls() + bar.mock.resetCalls() root.loader.entryFork.update(root.loader.config) await new Promise((resolve) => setTimeout(resolve, 0)) expect(root.registry.get(foo)).to.be.ok expect(root.registry.get(bar)).to.be.not.ok - expect(foo.mock.calls).to.have.length(1) - expect(bar.mock.calls).to.have.length(1) + expect(root.registry.get(qux)).to.be.ok + expect(foo.mock.calls).to.have.length(0) + expect(bar.mock.calls).to.have.length(0) + expect(qux.mock.calls).to.have.length(1) }) test('plugin update', async () => { @@ -64,6 +80,9 @@ describe('@cordisjs/loader', () => { id: '1', name: 'foo', config: { a: 3 }, + }, { + id: '4', + name: 'qux', }]) }) @@ -76,6 +95,9 @@ describe('@cordisjs/loader', () => { name: 'foo', disabled: true, config: { a: 3 }, + }, { + id: '4', + name: 'qux', }]) }) })