fridgemagnets/build.mjs

134 lines
4.0 KiB
JavaScript

import { execFileSync } from "child_process";
import * as chokidar from "chokidar";
import esbuild from "esbuild";
import fs from "fs";
import { globSync } from "glob";
import path from "path";
import { cwd } from "process";
import process from "process";
import { fileURLToPath } from "url";
// Hack replaces what was lost when using Bun / later Node versions
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const isProdBuild = process.env.NODE_ENV === "production";
const definitions = {
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
"process.env.CWD": JSON.stringify(cwd()),
};
// If you have assets in your src folder that won't be built/bundled, put them into "otherFiles" to
// copy them. All this is a replacement for rollup-copy-plugin, which I used to use.
const otherFiles = [
["./src/*.css", "."],
["./src/*.png", "."]
];
const isFile = (filePath) => fs.statSync(filePath).isFile();
function nameCopyTarget(src, dest, strip) {
const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base);
return [src, target];
}
function copyOthers() {
for (const [source, rawdest, strip] of otherFiles) {
const matchedPaths = globSync(source);
const dest = path.join("dist", rawdest);
const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip));
for (const [src, dest] of copyTargets) {
if (isFile(src)) {
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.copyFileSync(src, dest);
}
}
}
}
// This starts the definitions used for esbuild: Targets, arguments, the function for running a
// build, and the options for building: building or watching. If you're building more than one app,
// order them by the approximately largest project to smallest to build even faster.
const apps = [
["index.ts", "."]
];
const baseArgs = {
bundle: true,
write: true,
sourcemap: true,
minify: isProdBuild,
splitting: true,
treeShaking: true,
external: ["*.woff", "*.woff2"],
tsconfig: "./tsconfig.json",
loader: { ".css": "text", ".md": "text" },
define: definitions,
format: "esm",
};
async function buildOneSource(source, dest) {
const DIST = path.join(__dirname, "./dist", dest);
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
try {
const start = Date.now();
copyOthers();
await esbuild.build({
...baseArgs,
entryPoints: [`./src/${source}`],
entryNames: '[dir]/[name]',
outdir: DIST,
});
const end = Date.now();
console.log(
`[${new Date(end).toISOString()}] Finished build for target ${source} in ${
Date.now() - start
}ms`,
);
} catch (exc) {
console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`);
}
}
async function buildAll(apps) {
await Promise.allSettled(apps.map(([source, dest]) => buildOneSource(source, dest)));
}
let timeoutId = null;
function debouncedBuild() {
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
console.log("\x1bc");
buildAll(apps);
}, 250);
}
if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
console.log(`Build:
options:
-w, --watch: Build all ${apps.length} applications
-h, --help: This help message
`);
process.exit(0);
}
if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === "--watch")) {
console.log("Watching ./src for changes");
chokidar.watch("./src").on("all", (event, path) => {
if (!["add", "change", "unlink"].includes(event)) {
return;
}
if (!/(\.css|\.ts|\.js)$/.test(path)) {
return;
}
debouncedBuild();
});
} else {
await buildAll(apps);
}