Strangler-fig migration setup for the frontend. Legacy public/js/*.js keeps working unchanged; new TypeScript code lives under client/ and gets bundled into public/dist/ at image-build time. Adds: - esbuild@0.28 as a regular dep (so docker build can run the bundler without --include-dev). It's small; tsx already pulled in a pinned version transitively. - scripts/build-client.mjs: bundles every top-level client/*.ts file into public/dist/<name>.js as ESM with sourcemap. --watch mode for dev. Sub-directories under client/ are bundled by their parent entry, not separately. - tsconfig.client.json: browser target (lib: DOM), bundler module resolution, isolatedModules:true so esbuild + tsc agree. - Two npm scripts: build:client, typecheck:client. - Dockerfile: 'RUN node scripts/build-client.mjs' between COPY and CMD, so the prod image ships with the built bundle. - .gitignore: public/dist/ — build output, never committed. - public/index.html: <script type="module" src="/dist/main.js"> appended after legacy script tags. - client/main.ts: minimal entry point with a console.log marker (window.__clientBundle = true) so we can verify the bundle loaded. Sacred files (per project memory) kept off the migration path: - public/js/encounters.js (save/idempotency) - voiceDictation.js, sickVisit.js recording paths (STT plumbing) These need explicit per-file approval from Daniel before migration. Verification: - npm run typecheck → 0 errors (backend) - npm run typecheck:client → 0 errors (frontend) - npm run build:client → public/dist/main.js + .map in 3 ms - 46/46 unit tests pass Next session: migrate the first real frontend module (small + isolated, likely extensions.js or memories.js) into client/ as ES module + TS.
65 lines
2.2 KiB
JavaScript
65 lines
2.2 KiB
JavaScript
#!/usr/bin/env node
|
|
// ============================================================
|
|
// build-client.mjs — Compiles client/**/*.ts to public/dist/.
|
|
//
|
|
// Strangler-fig pattern: TS modules live under client/, legacy
|
|
// IIFE files stay under public/js/ until each is migrated.
|
|
// Each top-level .ts file in client/ becomes one ES-module bundle
|
|
// in public/dist/ that index.html can load via:
|
|
// <script type="module" src="/dist/<name>.js"></script>
|
|
//
|
|
// Run:
|
|
// npm run build:client # one-shot
|
|
// npm run build:client -- --watch # rebuild on change
|
|
// ============================================================
|
|
|
|
import { build, context } from 'esbuild';
|
|
import { readdirSync, mkdirSync, existsSync } from 'node:fs';
|
|
import { resolve, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const PROJECT_ROOT = resolve(__dirname, '..');
|
|
const SRC = resolve(PROJECT_ROOT, 'client');
|
|
const OUT = resolve(PROJECT_ROOT, 'public/dist');
|
|
|
|
const watch = process.argv.includes('--watch');
|
|
|
|
if (!existsSync(SRC)) {
|
|
console.error(`[build-client] Source dir does not exist: ${SRC}`);
|
|
process.exit(1);
|
|
}
|
|
mkdirSync(OUT, { recursive: true });
|
|
|
|
// Each top-level .ts/.tsx file in client/ is a separate entry point.
|
|
// Sub-directories are imported by the entries via TS imports — they
|
|
// are NOT separate entries, they get bundled into their parent.
|
|
const entryFiles = readdirSync(SRC, { withFileTypes: true })
|
|
.filter(d => d.isFile() && /\.tsx?$/.test(d.name))
|
|
.map(d => resolve(SRC, d.name));
|
|
|
|
if (entryFiles.length === 0) {
|
|
console.error(`[build-client] No .ts entry points found in ${SRC}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const buildOpts = {
|
|
entryPoints: entryFiles,
|
|
outdir: OUT,
|
|
bundle: true,
|
|
format: 'esm',
|
|
target: 'es2022',
|
|
platform: 'browser',
|
|
sourcemap: true,
|
|
minify: !watch, // unminified in watch mode for easier debugging
|
|
logLevel: 'info',
|
|
};
|
|
|
|
if (watch) {
|
|
const ctx = await context(buildOpts);
|
|
await ctx.watch();
|
|
console.log('[build-client] Watching for changes…');
|
|
} else {
|
|
await build(buildOpts);
|
|
console.log(`[build-client] Built ${entryFiles.length} entry point(s) → ${OUT}`);
|
|
}
|