Skip to content

Commit

Permalink
feat(loader): support isolate transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed May 10, 2024
1 parent 7ac5457 commit 637d37e
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 120 deletions.
60 changes: 33 additions & 27 deletions packages/loader/src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ export namespace Entry {
config?: any
disabled?: boolean | null
intercept?: Dict | null
isolate?: Dict<true | string>
isolate?: Dict<true | string> | null
when?: any
}
}

function swap<T extends {}>(target: T, source?: T | null): T {
const result = { ...target }
for (const key in result) {
delete target[key]
function swap<T extends {}>(target: T, source?: T | null) {
for (const key of Reflect.ownKeys(target)) {
Reflect.deleteProperty(target, key)
}
for (const key of Reflect.ownKeys(source || {})) {
Reflect.defineProperty(target, key, Reflect.getOwnPropertyDescriptor(source!, key)!)
}
Object.assign(target, source)
return result
}

function takeEntries(object: {}, keys: string[]) {
Expand Down Expand Up @@ -62,30 +62,18 @@ export class Entry {
}
}

patch(ctx?: Context, legacy?: Entry.Options) {
ctx ??= this.parent.extend({
[Context.intercept]: Object.create(this.parent[Context.intercept]),
[Context.isolate]: Object.create(this.parent[Context.isolate]),
})
ctx.emit('loader/patch', this, legacy)
swap(ctx[Context.intercept], this.options.intercept)

patch(ctx: Context, ref: Context = ctx) {
// part 1: prepare isolate map
const newMap: Dict<symbol> = Object.create(Object.getPrototypeOf(ctx[Context.isolate]))
const newMap: Dict<symbol> = Object.create(Object.getPrototypeOf(ref[Context.isolate]))
for (const [key, label] of Object.entries(this.options.isolate ?? {})) {
const realm = this.resolveRealm(label)
newMap[key] = (this.loader.realms[realm] ??= Object.create(null))[key] ??= Symbol(`${key}${realm}`)
}
for (const [key, label] of Object.entries(legacy?.isolate ?? {})) {
if (this.options.isolate?.[key] === label) continue
const name = this.resolveRealm(label)
this.loader._clearRealm(key, name)
}

// part 2: generate service diff
const diff: [string, symbol, symbol, symbol, symbol][] = []
const oldMap = ctx[Context.isolate]
for (const key in { ...oldMap, ...newMap }) {
for (const key in { ...oldMap, ...newMap, ...this.loader.delims }) {
if (newMap[key] === oldMap[key]) continue
const delim = this.loader.delims[key] ??= Symbol(key)
ctx[delim] = Symbol(`${key}#${this.options.id}`)
Expand Down Expand Up @@ -114,8 +102,14 @@ export class Entry {
}

// part 3.2: update service impl, prevent double update
this.fork?.update(this.options.config)
swap(ctx[Context.isolate], newMap)
swap(ctx[Context.intercept], this.options.intercept)
if (ctx === ref) {
this.fork?.update(this.options.config)
swap(ctx[Context.isolate], newMap)
} else {
Object.setPrototypeOf(ctx, Object.getPrototypeOf(ref))
swap(ctx, ref)
}
for (const [, symbol1, symbol2, flag1, flag2] of diff) {
if (flag1 === flag2 && ctx[symbol1] && !ctx[symbol2]) {
ctx.root[symbol2] = ctx.root[symbol1]
Expand All @@ -139,7 +133,13 @@ export class Entry {
delete ctx[this.loader.delims[key]]
}
}
return ctx
}

createContext() {
return this.parent.extend({
[Context.intercept]: Object.create(this.parent[Context.intercept]),
[Context.isolate]: Object.create(this.parent[Context.isolate]),
})
}

async update(parent: Context, options: Entry.Options) {
Expand All @@ -150,12 +150,18 @@ export class Entry {
this.stop()
} else if (this.fork) {
this.isUpdate = true
this.patch(this.fork.parent, legacy)
for (const [key, label] of Object.entries(legacy.isolate ?? {})) {
if (this.options.isolate?.[key] === label) continue
const name = this.resolveRealm(label)
this.loader._clearRealm(key, name)
}
this.patch(this.fork.parent)
} else {
this.parent.emit('loader/entry', 'apply', this)
const plugin = await this.loader.resolve(this.options.name)
if (!plugin) return
const ctx = this.patch()
const ctx = this.createContext()
this.patch(ctx)
this.fork = ctx.plugin(plugin, this.options.config)
this.fork.entry = this
}
Expand Down
5 changes: 3 additions & 2 deletions packages/loader/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ export abstract class Loader<T extends Loader.Options = Loader.Options> extends
this.writeConfig()
}

teleport(id: string, target: string, index = Infinity) {
transfer(id: string, target: string, index = Infinity) {
const entry = this.entries[id]
if (!entry) throw new Error(`entry ${id} not found`)
const sourceEntry = entry.parent.scope.entry!
Expand All @@ -296,7 +296,8 @@ export abstract class Loader<T extends Loader.Options = Loader.Options> extends
if (sourceEntry === targetEntry) return
entry.parent = targetEntry.fork.ctx
if (!entry.fork) return
entry.patch()
const ctx = entry.createContext()
entry.patch(entry.fork.parent, ctx)
}

paths(scope: EffectScope): string[] {
Expand Down
Loading

0 comments on commit 637d37e

Please sign in to comment.