Skip to content

Commit

Permalink
feat(loader): support options.intercept
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Feb 22, 2024
1 parent e54e8f3 commit 0041327
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 40 deletions.
3 changes: 0 additions & 3 deletions packages/hmr/src/index.ts
Expand Up @@ -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)
Expand Down
72 changes: 37 additions & 35 deletions packages/loader/src/shared.ts
Expand Up @@ -48,6 +48,7 @@ export namespace Entry {
name: string
config?: any
disabled?: boolean
intercept?: Dict
when?: any
}
}
Expand All @@ -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 {
Expand Down Expand Up @@ -106,7 +129,6 @@ export abstract class Loader<T extends Loader.Options = Loader.Options> extends
public entries: Dict<Entry> = Object.create(null)

private tasks = new Set<Promise<any>>()
private store = new WeakMap<any, string>()

abstract import(name: string): Promise<any>

Expand Down Expand Up @@ -203,46 +225,26 @@ export abstract class Loader<T extends Loader.Options = Loader.Options> 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) {
if (isNullable(expr)) return true
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)
} while (this.entries[options.id])
}

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()
Expand Down Expand Up @@ -311,7 +313,7 @@ export abstract class Loader<T extends Loader.Options = Loader.Options> 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[]) => {
Expand All @@ -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)
}
})
}
Expand Down
26 changes: 24 additions & 2 deletions packages/loader/tests/index.spec.ts
Expand Up @@ -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 = [{
Expand All @@ -30,6 +34,10 @@ describe('@cordisjs/loader', () => {
config: {
a: 1,
},
}, {
id: '4',
disabled: true,
name: 'qux',
}],
}]

Expand All @@ -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 () => {
Expand All @@ -64,6 +80,9 @@ describe('@cordisjs/loader', () => {
id: '1',
name: 'foo',
config: { a: 3 },
}, {
id: '4',
name: 'qux',
}])
})

Expand All @@ -76,6 +95,9 @@ describe('@cordisjs/loader', () => {
name: 'foo',
disabled: true,
config: { a: 3 },
}, {
id: '4',
name: 'qux',
}])
})
})

0 comments on commit 0041327

Please sign in to comment.