diff --git a/.gitignore b/.gitignore index 91690b4d2f..bf9daab845 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +lib dist temp .koishi diff --git a/build/compile.ts b/build/compile.ts index fd234834b0..aa0a264ee5 100644 --- a/build/compile.ts +++ b/build/compile.ts @@ -47,6 +47,7 @@ const KOISHI_VERSION = JSON.stringify(version) await Promise.all(workspaces.map(async (name) => { if (name.startsWith('.')) return + let outdir = 'dist' const base = `${root}/${name}` const entryPoints = [base + '/src/index.ts'] @@ -60,6 +61,7 @@ const KOISHI_VERSION = JSON.stringify(version) } else if (name === 'koishi-test-utils') { await tasks[chai] } else if (name === 'plugin-status') { + outdir = 'lib' entryPoints.splice(0, 1, base + '/server/index.ts') } @@ -70,7 +72,7 @@ const KOISHI_VERSION = JSON.stringify(version) platform: 'node', target: 'node12.19', charset: 'utf8', - outdir: `${root}/${name}/dist`, + outdir: `${root}/${name}/${outdir}`, logLevel: 'silent', sourcemap: true, define: { diff --git a/package.json b/package.json index e5ad8bcc2c..f0b2fb4c13 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,9 @@ "addons": "yarn workspace addons", "cli": "yarn workspace test koishi", "build": "yarn compile && yarn dtsc", - "build:ci": "yarn build:reg && yarn build --listEmittedFiles", + "build:ci": "yarn build:reg && yarn build --listEmittedFiles && yarn build:web", "build:reg": "esbuild build/register.ts --outdir=build --format=cjs --log-level=error", + "build:web": "node -r ./build/register packages/plugin-status/build", "dtsc": "node -r ./build/register build/dtsc", "bump": "node -r ./build/register build/bump", "compile": "node -r ./build/register build/compile", @@ -28,7 +29,7 @@ "test:text": "c8 -r text yarn test", "test:reg": "yarn build:reg && yarn test:html", "lint": "eslint packages/*/src/**/*.ts --fix --cache", - "pub": "yarn build && node -r ./build/register build/publish", + "pub": "yarn build && yarn build:web && node -r ./build/register build/publish", "webui": "yarn workspace koishi-plugin-webui", "shiki": "yarn workspace bot-shiki", "utsuho": "yarn workspace bot-utsuho" diff --git a/packages/plugin-status/build/index.ts b/packages/plugin-status/build/index.ts new file mode 100644 index 0000000000..03c8d00995 --- /dev/null +++ b/packages/plugin-status/build/index.ts @@ -0,0 +1,22 @@ +import { build } from 'vite' +import { resolve } from 'path' +import vuePlugin from '@vitejs/plugin-vue' + +const root = resolve(__dirname, '../client') + +build({ + root, + base: './', + build: { + outDir: '../dist', + minify: 'esbuild', + emptyOutDir: true, + }, + plugins: [vuePlugin()], + resolve: { + alias: { + '~/client': root, + '~/variables': root + '/index.scss', + }, + }, +}) diff --git a/packages/plugin-status/package.json b/packages/plugin-status/package.json index 57eb1cd0fd..06af94f453 100644 --- a/packages/plugin-status/package.json +++ b/packages/plugin-status/package.json @@ -2,10 +2,10 @@ "name": "koishi-plugin-status", "description": "Show Status of Koishi", "version": "4.0.0-alpha.1", - "main": "dist/index.js", - "typings": "dist/index.d.ts", + "main": "lib/index.js", + "typings": "lib/index.d.ts", "files": [ - "dist" + "dist", "lib" ], "author": "Shigma <1700011071@pku.edu.cn>", "license": "MIT", diff --git a/packages/plugin-status/server/adapter.ts b/packages/plugin-status/server/adapter.ts index da506e6955..057e491121 100644 --- a/packages/plugin-status/server/adapter.ts +++ b/packages/plugin-status/server/adapter.ts @@ -140,6 +140,12 @@ export class WebAdapter extends Adapter<'sandbox'> { }) } + broadcast(type: string, body: any) { + if (!this?.server.clients.size) return + const data = JSON.stringify({ type, body }) + this.server.clients.forEach((socket) => socket.send(data)) + } + stop() { this.server.close() } diff --git a/packages/plugin-status/server/webui.ts b/packages/plugin-status/server/webui.ts index c24cd9e9ef..bc0fa7c66e 100644 --- a/packages/plugin-status/server/webui.ts +++ b/packages/plugin-status/server/webui.ts @@ -1,9 +1,9 @@ import { Context, Plugin } from 'koishi-core' import { assertProperty, noop } from 'koishi-utils' -import { resolve } from 'path' -import { promises as fs, Stats } from 'fs' +import { resolve, extname } from 'path' +import { promises as fs, Stats, createReadStream } from 'fs' import { WebAdapter } from './adapter' -import { createServer, ViteDevServer } from 'vite' +import { createServer } from 'vite' import vuePlugin from '@vitejs/plugin-vue' import Profile from './profile' import Statistics from './stats' @@ -13,6 +13,7 @@ export { BotData, LoadRate } from './profile' export interface Config extends WebAdapter.Config, Profile.Config { selfUrl?: string uiPath?: string + mode?: 'development' | 'production' } export interface PluginData extends Plugin.Meta { @@ -30,14 +31,20 @@ export interface Registry { export const name = 'webui' export function apply(ctx: Context, config: Config = {}) { - const root = resolve(__dirname, '../client') const koishiPort = assertProperty(ctx.app.options, 'port') - const { apiPath, uiPath, selfUrl = `http://localhost:${koishiPort}` } = config + const { apiPath, uiPath, mode, selfUrl = `http://localhost:${koishiPort}` } = config - let vite: ViteDevServer - let adapter: WebAdapter - ctx.on('connect', async () => { - vite = await createServer({ + const globalVariables = Object.entries({ + KOISHI_UI_PATH: uiPath, + KOISHI_ENDPOINT: selfUrl + apiPath, + }).map(([key, value]) => `window.${key} = ${JSON.stringify(value)};`).join('\n') + + const root = resolve(__dirname, '..', mode === 'development' ? 'client' : 'dist') + + async function createVite() { + if (mode !== 'development') return + + const vite = await createServer({ root, base: '/vite/', server: { middlewareMode: true }, @@ -48,29 +55,19 @@ export function apply(ctx: Context, config: Config = {}) { '~/variables': root + '/index.scss', }, }, - define: { - KOISHI_UI_PATH: JSON.stringify(uiPath), - KOISHI_ENDPOINT: JSON.stringify(selfUrl + apiPath), - }, - }) - - ctx.router.get(uiPath + '(/.+)*', async (koa) => { - const filename = root + koa.path.slice(uiPath.length) - const stats = await fs.stat(filename).catch(noop) - if (stats?.isFile()) { - return koa.body = await fs.readFile(filename) - } - const raw = await fs.readFile(resolve(root, 'index.html'), 'utf8') - const template = await vite.transformIndexHtml(uiPath, raw) - koa.set('content-type', 'text/html') - koa.body = template }) ctx.router.all('/vite(/.+)+', (koa) => new Promise((resolve) => { vite.middlewares(koa.req, koa.res, resolve) })) - adapter = ctx.app.adapters.sandbox = new WebAdapter(ctx, config) + ctx.before('disconnect', () => vite.close()) + + return vite + } + + async function createAdapter() { + const adapter = ctx.app.adapters.sandbox = new WebAdapter(ctx, config) adapter.server.on('connection', async (socket) => { function send(type: string, body: any) { @@ -83,6 +80,35 @@ export function apply(ctx: Context, config: Config = {}) { }) await adapter.start() + + ctx.before('disconnect', () => adapter.stop()) + + ctx.on('registry', () => { + adapter.broadcast('registry', getRegistry(true)) + }) + + ctx.on('status/tick', async () => { + adapter.broadcast('profile', await getProfile(true)) + }) + + return adapter + } + + ctx.on('connect', async () => { + const [vite] = await Promise.all([createVite(), createAdapter()]) + + ctx.router.get(uiPath + '(/.+)*', async (koa) => { + const filename = root + koa.path.slice(uiPath.length) + const stats = await fs.stat(filename).catch(noop) + if (stats?.isFile()) { + koa.type = extname(filename) + return koa.body = createReadStream(filename) + } + let template = await fs.readFile(resolve(root, 'index.html'), 'utf8') + if (vite) template = await vite.transformIndexHtml(uiPath, template) + koa.set('content-type', 'text/html') + koa.body = template.replace('', '') + }) }) function* getDeps(state: Plugin.State): Generator { @@ -100,44 +126,22 @@ export function apply(ctx: Context, config: Config = {}) { const children = state.children.flatMap(traverse, 1) const { name, sideEffect } = state if (!name) return children - internal.pluginCount += 1 + registry.pluginCount += 1 const dependencies = [...new Set(getDeps(state))] return [{ name, sideEffect, children, dependencies }] } let profile: Promise - let internal: Registry - - async function broadcast(type: string, body: any) { - if (!adapter?.server.clients.size) return - const data = JSON.stringify({ type, body }) - adapter.server.clients.forEach((socket) => socket.send(data)) - } - - function getRegistry(forced = false) { - if (internal && !forced) return internal - internal = { pluginCount: 0 } as Registry - internal.plugins = traverse(null) - return internal - } - function getProfile(forced = false) { if (profile && !forced) return profile return profile = Profile.get(ctx, config) } - ctx.on('registry', () => { - broadcast('registry', getRegistry(true)) - }) - - ctx.on('status/tick', async () => { - broadcast('profile', await getProfile(true)) - }) - - ctx.before('disconnect', async () => { - await Promise.all([ - vite?.close(), - adapter?.stop(), - ]) - }) + let registry: Registry + function getRegistry(forced = false) { + if (registry && !forced) return registry + registry = { pluginCount: 0 } as Registry + registry.plugins = traverse(null) + return registry + } } diff --git a/packages/plugin-status/tsconfig.json b/packages/plugin-status/tsconfig.json index 493a2d8fcc..715cea2081 100644 --- a/packages/plugin-status/tsconfig.json +++ b/packages/plugin-status/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base", "compilerOptions": { - "outDir": "dist", + "outDir": "lib", "rootDir": "server", }, "include": [