sitegen/framework/bundle.ts

85 lines
2.4 KiB
TypeScript

// 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<unknown>[] = [];
// 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";