fix: rebuild the plugin sandbox while npm run dev runs, and build it for the integration image

This commit is contained in:
Linus Rath
2026-10-25 02:26:18 +01:00
parent f20892ccec
commit a7cfa5b160
3 changed files with 47 additions and 7 deletions
+3
View File
@@ -22,6 +22,9 @@ RUN npm ci
# App source (data-testid hooks included).
COPY . .
# The plugin sandbox document's script and stylesheet (see the script).
RUN node scripts/build-plugin-sandbox.mjs
ENV NODE_ENV=development
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
+1 -1
View File
@@ -22,7 +22,7 @@
"typescript"
],
"scripts": {
"dev": "node scripts/build-plugin-sandbox.mjs && next dev --turbopack",
"dev": "node scripts/build-plugin-sandbox.mjs --watch -- next dev --turbopack",
"build": "node scripts/build-plugin-sandbox.mjs && next build --turbopack",
"build:lite": "node scripts/lite/build.mjs",
"lite:serve": "node scripts/lite/serve.mjs out 4173",
+43 -6
View File
@@ -8,12 +8,16 @@
// origins, so the browser's code cache never helps: what a frame loads, it
// parses again, on the host page's main thread.
//
// node scripts/build-plugin-sandbox.mjs (npm run build and dev run it)
// node scripts/build-plugin-sandbox.mjs (npm run build)
// node scripts/build-plugin-sandbox.mjs --watch -- <cmd> (npm run dev: rebuild
// when one of the sandbox's sources changes, while <cmd> runs)
import { build } from 'esbuild';
import postcss from 'postcss';
import tailwind from '@tailwindcss/postcss';
import { createHash } from 'node:crypto';
import { spawn } from 'node:child_process';
import { watch } from 'node:fs';
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -72,8 +76,10 @@ const tablerPerIcon = {
* Build into `outdir` (emptied first). Resolves with the file names and
* esbuild's metafile outputs.
*/
export async function buildPluginSandbox({ outdir = DEFAULT_OUTDIR } = {}) {
await rm(outdir, { recursive: true, force: true });
export async function buildPluginSandbox({ outdir = DEFAULT_OUTDIR, clean = true } = {}) {
// A rebuild under `next dev` keeps the old files: the manifest, written
// last, switches frames over, and one loading meanwhile still finds its files.
if (clean) await rm(outdir, { recursive: true, force: true });
await mkdir(outdir, { recursive: true });
// ES modules so the rarely used parts (every Tabler icon, for an icon name
@@ -123,11 +129,42 @@ export async function buildPluginSandbox({ outdir = DEFAULT_OUTDIR } = {}) {
const manifest = { script: path.basename(script), style, preload };
await writeFile(path.join(outdir, 'manifest.json'), `${JSON.stringify(manifest)}\n`);
return { ...manifest, outputs: js.metafile.outputs, css: css.css };
const inputs = [...Object.keys(js.metafile.inputs).map((f) => path.resolve(root, f)), cssPath, path.join(root, 'app/theme-inline.css')];
return { ...manifest, outputs: js.metafile.outputs, css: css.css, inputs };
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const { outputs, style, css } = await buildPluginSandbox();
function report({ outputs, style, css }) {
const sizes = Object.entries(outputs).filter(([f]) => f.endsWith('.js')).map(([f, o]) => `${path.basename(f)} ${o.bytes}`);
console.log(`[build-plugin-sandbox] ${sizes.join(', ')}, ${style} ${css.length}`);
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const args = process.argv.slice(2);
let built = await buildPluginSandbox();
report(built);
if (args.includes('--watch')) {
let timer = null;
let running = false;
const rebuild = async () => {
if (running) { timer = setTimeout(rebuild, 200); return; }
running = true;
try { built = await buildPluginSandbox({ clean: false }); report(built); } catch (err) { console.error('[build-plugin-sandbox]', err.message); }
running = false;
};
for (const dir of ['app', 'components', 'lib', 'i18n']) {
watch(path.join(root, dir), { recursive: true }, (_event, file) => {
if (!file || !built.inputs.includes(path.join(root, dir, file))) return;
clearTimeout(timer);
timer = setTimeout(rebuild, 200);
});
}
const dash = args.indexOf('--');
if (dash >= 0 && args.length > dash + 1) {
const [cmd, ...cmdArgs] = args.slice(dash + 1);
// npm puts node_modules/.bin on PATH; on Windows those are .cmd shims.
const child = spawn(cmd, cmdArgs, { stdio: 'inherit', shell: process.platform === 'win32' });
child.on('exit', (code) => process.exit(code ?? 0));
for (const sig of ['SIGINT', 'SIGTERM']) process.on(sig, () => child.kill(sig));
}
}
}