Skip to content

Commit

Permalink
feat(loader): basic support for loader tree
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed May 29, 2024
1 parent 28c5bfc commit 9c95eb0
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 149 deletions.
4 changes: 2 additions & 2 deletions packages/cordis/src/group.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { group } from '@cordisjs/loader'
import { Group } from '@cordisjs/loader'

export default group
export default Group
3 changes: 1 addition & 2 deletions packages/cordis/src/worker/logger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Logger } from '@cordisjs/logger'
import { kGroup } from '@cordisjs/loader'
import { Context } from '../index.ts'

declare module '@cordisjs/loader' {
Expand Down Expand Up @@ -36,7 +35,7 @@ export function apply(ctx: Context, config: Config = {}) {
})

ctx.on('loader/entry', (type, entry) => {
if (entry.fork?.runtime.plugin?.[kGroup]) return
if (entry.options.transparent) return
ctx.logger('loader').info('%s plugin %c', type, entry.options.name)
})

Expand Down
5 changes: 3 additions & 2 deletions packages/loader/src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export namespace Entry {
intercept?: Dict | null
isolate?: Dict<true | string> | null
inject?: string[] | Inject | null
transparent?: boolean | null
when?: any
}
}
Expand Down Expand Up @@ -43,7 +44,7 @@ function sortKeys<T extends {}>(object: T, prepend = ['id', 'name'], append = ['
}

export class Entry {
static key = Symbol('cordis.entry')
static key = Symbol.for('cordis.entry')

public fork?: ForkScope
public suspend = false
Expand Down Expand Up @@ -202,7 +203,7 @@ export class Entry {

async start() {
const ctx = this.createContext()
const exports = await this.loader.import(this.options.name, this.parent.url).catch((error: any) => {
const exports = await this.loader.import(this.options.name, this.parent.tree.url).catch((error: any) => {
ctx.emit('internal/error', new Error(`Cannot find package "${this.options.name}"`))
ctx.emit('internal/error', error)
})
Expand Down
46 changes: 25 additions & 21 deletions packages/loader/src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,41 @@ import { Entry } from './entry.ts'
import { EntryGroup } from './group.ts'
import { Loader } from './shared.ts'
import { remove } from 'cosmokit'
import { EntryTree } from './tree.ts'

export class LoaderFile {
public suspend = false
public mutable = false
public readonly: boolean
public url: string
public groups: BaseLoader[] = []
public trees: ImportTree[] = []

private _writeTask?: NodeJS.Timeout

constructor(public loader: Loader, public name: string, public type?: string) {
this.url = pathToFileURL(name).href
loader.files[name] = this
loader.files[this.url] = this
this.readonly = !type
}

ref(group: BaseLoader) {
this.groups.push(group)
group.url = pathToFileURL(this.name).href
ref(tree: ImportTree) {
this.trees.push(tree)
tree.url = pathToFileURL(this.name).href
}

unref(group: BaseLoader) {
remove(this.groups, group)
if (this.groups.length) return
unref(tree: ImportTree) {
remove(this.trees, tree)
if (this.trees.length) return
clearTimeout(this._writeTask)
delete this.loader.files[this.name]
delete this.loader.files[this.url]
}

async checkAccess() {
if (!this.type) return
try {
await access(this.name, constants.W_OK)
this.mutable = true
} catch {}
} catch {
this.readonly = true
}
}

async read(): Promise<Entry.Options[]> {
Expand All @@ -55,7 +58,7 @@ export class LoaderFile {

private async _write(config: Entry.Options[]) {
this.suspend = true
if (!this.mutable) {
if (this.readonly) {
throw new Error(`cannot overwrite readonly config`)
}
if (this.type === 'application/yaml') {
Expand Down Expand Up @@ -92,14 +95,16 @@ export namespace LoaderFile {
}
}

export class BaseLoader extends EntryGroup {
export class ImportTree extends EntryTree {
static reusable = true

protected file!: LoaderFile

constructor(public ctx: Context) {
super(ctx)
super()
this.root = new EntryGroup(ctx, this)
ctx.on('ready', () => this.start())
ctx.on('dispose', () => this.stop())
}

async start() {
Expand All @@ -108,21 +113,20 @@ export class BaseLoader extends EntryGroup {
}

async refresh() {
this._update(await this.file.read())
this.root.update(await this.file.read())
}

stop() {
this.file?.unref(this)
return super.stop()
return this.root.stop()
}

write() {
return this.file!.write(this.data)
return this.file!.write(this.root.data)
}

_createFile(filename: string, type: string) {
this.file = this.ctx.loader[filename] ??= new LoaderFile(this.ctx.loader, filename, type)
this.url = this.file.url
this.file.ref(this)
}

Expand Down Expand Up @@ -179,14 +183,14 @@ export namespace Import {
}
}

export class Import extends BaseLoader {
export class Import extends ImportTree {
constructor(ctx: Context, public config: Import.Config) {
super(ctx)
}

async start() {
const { url } = this.config
const filename = fileURLToPath(new URL(url, this.ctx.scope.entry!.parent.url))
const filename = fileURLToPath(new URL(url, this.ctx.scope.entry!.parent.tree.url))
const ext = extname(filename)
if (!LoaderFile.supported.has(ext)) {
throw new Error(`extension "${ext}" not supported`)
Expand Down
67 changes: 24 additions & 43 deletions packages/loader/src/group.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,35 @@
import { Context } from '@cordisjs/core'
import { Entry } from './entry.ts'
import { EntryTree } from './tree.ts'

export abstract class EntryGroup {
export class EntryGroup {
public data: Entry.Options[] = []
public url!: string

constructor(public ctx: Context) {
ctx.on('dispose', () => this.stop())
}
constructor(public ctx: Context, public tree: EntryTree) {}

async _create(options: Omit<Entry.Options, 'id'>) {
async create(options: Omit<Entry.Options, 'id'>) {
const id = this.ctx.loader.ensureId(options)
const entry = this.ctx.loader.entries[id] ??= new Entry(this.ctx.loader, this)
entry.parent = this
await entry.update(options as Entry.Options)
return id
}

_unlink(options: Entry.Options) {
unlink(options: Entry.Options) {
const config = this.data
const index = config.indexOf(options)
if (index >= 0) config.splice(index, 1)
}

_remove(id: string) {
remove(id: string) {
const entry = this.ctx.loader.entries[id]
if (!entry) return
entry.stop()
this._unlink(entry.options)
this.unlink(entry.options)
delete this.ctx.loader.entries[id]
}

_update(config: Entry.Options[]) {
update(config: Entry.Options[]) {
const oldConfig = this.data as Entry.Options[]
this.data = config
const oldMap = Object.fromEntries(oldConfig.map(options => [options.id, options]))
Expand All @@ -40,52 +38,35 @@ export abstract class EntryGroup {
// update inner plugins
for (const id of Reflect.ownKeys({ ...oldMap, ...newMap }) as string[]) {
if (newMap[id]) {
this._create(newMap[id]).catch((error) => {
this.create(newMap[id]).catch((error) => {
this.ctx.emit('internal/error', error)
})
} else {
this._remove(id)
this.remove(id)
}
}
}

write() {
return this.ctx.scope.entry!.parent.write()
}

stop() {
for (const options of this.data) {
this._remove(options.id)
this.remove(options.id)
}
}
}

export const kGroup = Symbol.for('cordis.group')

export interface GroupOptions {
name?: string
initial?: Omit<Entry.Options, 'id'>[]
allowed?: string[]
}

export function defineGroup(config?: Entry.Options[], options: GroupOptions = {}) {
options.initial = config

class Group extends EntryGroup {
static reusable = true
static [kGroup] = options
export class Group extends EntryGroup {
static key = Symbol('cordis.group')
static reusable = true
static initial: Omit<Entry.Options, 'id'>[] = []

constructor(public ctx: Context) {
super(ctx)
this.url = ctx.scope.entry!.parent.url
ctx.scope.entry!.children = this
ctx.accept((config: Entry.Options[]) => {
this._update(config)
}, { passive: true, immediate: true })
}
// TODO support options
constructor(public ctx: Context) {
const entry = ctx.scope.entry!
super(ctx, entry.parent.tree)
entry.children = this
ctx.on('dispose', () => this.stop())
ctx.accept((config: Entry.Options[]) => {
this.update(config)
}, { passive: true, immediate: true })
}

return Group
}

export const group = defineGroup()
10 changes: 5 additions & 5 deletions packages/loader/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ class NodeLoader extends Loader {
async start() {
const originalLoad: ModuleLoad = Module['_load']
Module['_load'] = ((request, parent, isMain) => {
if (request.startsWith('node:')) return originalLoad(request, parent, isMain)
try {
// TODO support hmr for cjs-esm interop
const result = this.internal?.resolveSync(request, pathToFileURL(parent.filename).href, {})
if (result?.format === 'module' && this.internal?.loadCache.has(result.url)) {
const job = this.internal?.loadCache.get(result.url)
return job?.module?.getNamespace()
}
const job = result?.format === 'module'
? this.internal?.loadCache.get(result.url)
: undefined
if (job) return job?.module?.getNamespace()
} catch {}
return originalLoad(request, parent, isMain)
}) as ModuleLoad
Expand Down
Loading

0 comments on commit 9c95eb0

Please sign in to comment.