Skip to content

Commit

Permalink
feat(webui): support webui.addEntry()
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Mar 31, 2021
1 parent 2cf79b5 commit 217fbcd
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 42 deletions.
6 changes: 3 additions & 3 deletions packages/koishi-core/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@ export class App extends Context {

createServer() {
const koa: Koa = new (require('koa'))()
defineProperty(this, '_router', new (require('@koa/router'))())
this.router = new (require('@koa/router'))()
koa.use(require('koa-bodyparser')())
koa.use(this._router.routes())
koa.use(this._router.allowedMethods())
koa.use(this.router.routes())
koa.use(this.router.allowedMethods())
defineProperty(this, '_httpServer', createServer(koa.callback()))
}

Expand Down
22 changes: 10 additions & 12 deletions packages/koishi-core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,20 @@ interface Selector<T> extends PartialSeletor<T> {
}

export class Context {
static readonly middleware = Symbol('mid')
static readonly middleware = Symbol('middleware')
static readonly current = Symbol('source')

protected _bots: Bot[] & Record<string, Bot>
protected _router: Router

public database: Database
public assets: Assets
public router: Router

protected constructor(public filter: Filter, public app?: App, private _plugin: Plugin = null) {}

[inspect.custom]() {
const plugin = this._plugin
const name = !plugin ? 'root' : typeof plugin === 'object' && plugin.name || 'unknown'
const name = !plugin ? 'root' : typeof plugin === 'object' && plugin.name || 'anonymous'
return `Context <${name}>`
}

Expand Down Expand Up @@ -103,13 +104,6 @@ export class Context {
return this.unselect('groupId').user
}

get router(): Router {
if (!this.app._router) return
const router = Object.create(this.app._router)
router._koishiContext = this
return router
}

get bots() {
return this.app._bots
}
Expand Down Expand Up @@ -493,7 +487,10 @@ export class Context {
const privateKey = Symbol(key)
Object.defineProperty(Context.prototype, key, {
get() {
return this.app[privateKey]
if (!this.app[privateKey]) return
const value = Object.create(this.app[privateKey])
value[Context.current] = this
return value
},
set(value) {
this.app[privateKey] = value
Expand All @@ -504,6 +501,7 @@ export class Context {

Context.delegate('database')
Context.delegate('assets')
Context.delegate('router')

type FlattenEvents<T> = {
[K in keyof T & string]: K | `${K}/${FlattenEvents<T[K]>}`
Expand Down Expand Up @@ -552,7 +550,7 @@ export interface EventMap extends SessionEventMap {
const register = Router.prototype.register
Router.prototype.register = function (this: Router, ...args) {
const layer = register.apply(this, args)
const context: Context = this['_koishiContext']
const context: Context = this[Context.current]
context.state.disposables.push(() => {
remove(this.stack, layer)
})
Expand Down
11 changes: 3 additions & 8 deletions packages/plugin-chat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ export const name = 'chat'

export function apply(ctx: Context, options: Config = {}) {
ctx.plugin(debug, options)
ctx.with('koishi-plugin-webui', () => {
const { config, entries } = ctx.webui

const filename = resolve(__dirname, config.devMode ? '../client/index.ts' : '../dist/index.js')
entries.chat = filename
ctx.before('disconnect', () => {
delete entries.chat
})
ctx.with('koishi-plugin-webui', (ctx) => {
const filename = ctx.webui.config.devMode ? '../client/index.ts' : '../dist/index.js'
ctx.webui.addEntry(resolve(__dirname, filename))
})
}
8 changes: 2 additions & 6 deletions packages/plugin-teach/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ export function apply(ctx: Context, config: Config = {}) {
ctx.plugin(writer, config)

ctx.with('koishi-plugin-webui', (ctx) => {
const { config, entries } = ctx.webui
const { stats, meta } = ctx.webui.sources

ctx.on('dialogue/before-send', ({ session, dialogue }) => {
Expand Down Expand Up @@ -236,10 +235,7 @@ export function apply(ctx: Context, config: Config = {}) {
payload.questions = Object.values(questionMap)
})

const filename = resolve(__dirname, config.devMode ? '../client/index.ts' : '../dist/index.js')
entries.teach = filename
ctx.before('disconnect', () => {
delete entries.teach
})
const filename = ctx.webui.config.devMode ? '../client/index.ts' : '../dist/index.js'
ctx.webui.addEntry(resolve(__dirname, filename))
})
}
46 changes: 33 additions & 13 deletions packages/plugin-webui/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export class WebServer {
sources: WebServer.Sources
entries: Record<string, string> = {}

private vite: Vite.ViteDevServer
private readonly [Context.current]: Context

constructor(private ctx: Context, public config: Config) {
this.root = resolve(__dirname, '..', config.devMode ? 'client' : 'dist')
const { apiPath, uiPath, devMode, selfUrl, title } = config
Expand All @@ -54,9 +57,23 @@ export class WebServer {
ctx.on('connect', () => this.start())
}

async start() {
addEntry(filename: string) {
const ctx = this[Context.current]
let { state } = ctx
while (state && !state.name) state = state.parent
const hash = Math.floor(Math.random() * (16 ** 8)).toString(16).padStart(8, '0')
const key = `${state?.name || 'entry'}-${hash}.js`
this.entries[key] = filename
this.vite?.ws.send({ type: 'full-reload' })
ctx.before('disconnect', () => {
delete this.entries[key]
this.vite?.ws.send({ type: 'full-reload' })
})
}

private async start() {
const { uiPath } = this.config
const [vite] = await Promise.all([this.createVite(), this.createAdapter()])
await Promise.all([this.createVite(), this.createAdapter()])

this.ctx.router.get(uiPath + '(/.+)*', async (ctx) => {
// add trailing slash and redirect
Expand All @@ -68,7 +85,10 @@ export class WebServer {
ctx.type = extname(filename)
return ctx.body = createReadStream(filename)
}
if (this.entries[name]) return sendFile(this.entries[name])
if (name.startsWith('assets/')) {
const key = name.slice(7)
if (this.entries[key]) return sendFile(this.entries[key])
}
const filename = resolve(this.root, name)
if (!filename.startsWith(this.root) && !filename.includes('node_modules')) {
return ctx.status = 403
Expand All @@ -77,19 +97,21 @@ export class WebServer {
if (stats?.isFile()) return sendFile(filename)
const ext = extname(filename)
if (ext && ext !== '.html') return ctx.status = 404
let template = await fs.readFile(resolve(this.root, 'index.html'), 'utf8')
if (vite) template = await vite.transformIndexHtml(uiPath, template)
const template = await fs.readFile(resolve(this.root, 'index.html'), 'utf8')
ctx.type = 'html'
ctx.body = this.transformHtml(template)
ctx.body = await this.transformHtml(template)
})
}

private transformHtml(template: string) {
private async transformHtml(template: string) {
if (this.vite) {
template = await this.vite.transformIndexHtml(this.config.uiPath, template)
}
const headInjection = '<script>' + Object.entries(this.global).map(([key, value]) => {
return `window.KOISHI_${snakeCase(key).toUpperCase()} = ${JSON.stringify(value)};`
}).join('\n') + '</script>'
const bodyInjection = Object.entries(this.entries).map(([name, filename]) => {
const src = this.config.devMode ? '/vite/@fs' + filename : `./${name}`
const src = this.config.devMode ? '/vite/@fs' + filename : `./assets/${name}`
return `<script type="module" src="${src}"></script>`
}).join('\n')
return template
Expand Down Expand Up @@ -119,7 +141,7 @@ export class WebServer {
const { createServer } = require('vite') as typeof Vite
const pluginVue = require('@vitejs/plugin-vue').default as typeof PluginVue

const vite = await createServer({
this.vite = await createServer({
root: this.root,
base: '/vite/',
server: { middlewareMode: true },
Expand All @@ -133,11 +155,9 @@ export class WebServer {
})

this.ctx.router.all('/vite(/.+)+', (ctx) => new Promise((resolve) => {
vite.middlewares(ctx.req, ctx.res, resolve)
this.vite.middlewares(ctx.req, ctx.res, resolve)
}))

this.ctx.before('disconnect', () => vite.close())

return vite
this.ctx.before('disconnect', () => this.vite.close())
}
}

0 comments on commit 217fbcd

Please sign in to comment.