feat: bundle plugin src/ on demand via esbuild

This commit is contained in:
Linus Rath
2026-05-05 18:35:22 +02:00
parent 3e336d459c
commit 1b0ca8967e
13 changed files with 2040 additions and 173 deletions
+4 -5
View File
@@ -1,7 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { readFile } from 'node:fs/promises';
import { getPluginBundle, getPlugin } from '@/lib/admin/plugin-registry';
import { getDevPlugin } from '@/lib/admin/plugin-dev';
import { getDevPlugin, readDevBundle } from '@/lib/admin/plugin-dev';
/**
* GET /api/admin/plugins/[id]/bundle - Serve plugin JS bundle
@@ -21,11 +20,11 @@ export async function GET(
return NextResponse.json({ error: 'Invalid plugin ID' }, { status: 400 });
}
// Dev plugins are read straight from disk and served with no caching so
// every refresh picks up the latest build.
// Dev plugins are read (and optionally bundled) straight from disk and
// served with no caching so every refresh picks up the latest source.
const devEntry = await getDevPlugin(id);
if (devEntry) {
const code = await readFile(devEntry.bundlePath, 'utf-8');
const code = await readDevBundle(devEntry);
return new NextResponse(code, {
headers: {
'Content-Type': 'application/javascript; charset=utf-8',
+15 -2
View File
@@ -8,6 +8,7 @@ import {
deletePlugin as removePlugin,
type ServerPlugin,
} from '@/lib/admin/plugin-registry';
import { listDevPlugins } from '@/lib/admin/plugin-dev';
import {
sanitizeFrameOrigins,
invalidateFrameOriginsCache,
@@ -34,8 +35,20 @@ export async function GET() {
const result = await requireAdminAuth();
if ('error' in result) return result.error;
const registry = await getPluginRegistry();
return NextResponse.json(registry.plugins, {
const [registry, devEntries] = await Promise.all([
getPluginRegistry(),
listDevPlugins(),
]);
// Dev plugins win on id collision so admins see what users actually load.
const devIds = new Set(devEntries.map(e => e.plugin.id));
const merged = [
...devEntries.map(e => ({ ...e.plugin, dev: true as const })),
...registry.plugins
.filter(p => !devIds.has(p.id))
.map(p => ({ ...p, dev: false as const })),
];
return NextResponse.json(merged, {
headers: { 'Cache-Control': 'no-store' },
});
} catch (error) {