// This file implements client-side bundling, mostly wrapping esbuild. const plugins: esbuild.Plugin[] = [ // There are currently no plugins needed by 'paperclover.net' ]; export async function bundleClientJavaScript( referencedScripts: string[], extraPublicScripts: string[], incr: Incremental, dev: boolean = false, ) { const entryPoints = [ ...new Set([ ...referencedScripts, ...extraPublicScripts, ]), ]; if (entryPoints.length === 0) return; const invalidFiles = entryPoints .filter((file) => !file.match(/\.client\.[tj]sx?/)); if (invalidFiles.length > 0) { const cwd = process.cwd(); throw new Error( "All client-side scripts should be named like '.client.ts'. Exceptions: " + invalidFiles.map((x) => path.join(cwd, x)).join(","), ); } const bundle = await esbuild.build({ bundle: true, chunkNames: "/js/c.[hash]", entryNames: "/js/[name]", assetNames: "/asset/[hash]", entryPoints, format: "esm", minify: !dev, outdir: "/out!", plugins, splitting: true, write: false, }); if (bundle.errors.length || bundle.warnings.length) { throw new AggregateError( bundle.errors.concat(bundle.warnings), "JS bundle failed", ); } incr.invalidate("bundle-script"); const publicScriptRoutes = extraPublicScripts.map((file) => path.basename(file).replace(/\.client\.[tj]sx?/, "") ); const promises: Promise[] = []; // TODO: add a shared build hash to entrypoints, derived from all the chunk hashes. for (const file of bundle.outputFiles) { let route = file.path.replace(/^.*!/, "").replaceAll("\\", "/"); const text = file.text; // Register non-chunks as script entries. const chunk = route.startsWith("/js/c."); if (!chunk) { route = route.replace(".client.js", ".js"); incr.put({ srcId: "bundle-script", type: "script", key: route.slice("/js/".length, -".js".length), value: text, }); } if (chunk || publicScriptRoutes.includes(route)) { promises.push(incr.putAsset({ srcId: "bundle-script", key: route, body: text, })); } } if (promises.length > 0) { await Promise.all(promises); } } import * as esbuild from "esbuild"; import * as path from "node:path"; import process from "node:process"; import { Incremental } from "./incremental.ts";