import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; import * as sass from "sass"; import * as esbuild from "esbuild"; import { patchBundledJS } from "../client/plugos/plug_compile.ts"; // This builds the client and puts it into client_bundle/client export async function buildClient(): Promise { await mkdir("client_bundle/client", { recursive: true }); await mkdir("client_bundle/base_fs", { recursive: true }); console.log("Now ESBuilding the client and service workers..."); const result = await esbuild.build({ entryPoints: [ { in: "client/boot.ts", out: ".client/client", }, { in: "client/service_worker.ts", out: "service_worker", }, ], outdir: "client_bundle/client", absWorkingDir: process.cwd(), bundle: true, treeShaking: true, sourcemap: "linked", minify: true, jsxFactory: "h", // metafile: true, splitting: true, format: "esm", chunkNames: ".client/[name]-[hash]", jsx: "automatic", jsxFragment: "Fragment", jsxImportSource: "preact", }); if (result.metafile) { const text = await esbuild.analyzeMetafile(result.metafile!); console.log("Bundle info", text); } await copyAssets("client_bundle/client/.client"); await patchServiceWorker(); console.log("Built!"); } async function copyAssets(dist: string) { await mkdir(dist, { recursive: true }); await cp("client/fonts", dist, { recursive: true }); await cp("client/html", dist, { recursive: true }); await cp("client/images/favicon.png", `${dist}/favicon.png`); await cp("client/images/logo.png", `${dist}/logo.png`); await cp("client/images/logo-dock.png", `${dist}/logo-dock.png`); const scssContent = await readFile("client/styles/main.scss", "utf-8"); const result = sass.compileString(scssContent, { loadPaths: ["client/styles"], style: "compressed", }); await writeFile(`${dist}/main.css`, result.css, "utf-8"); // HACK: Patch the JS by removing an invalid regex let bundleJs = await readFile(`${dist}/client.js`, "utf-8"); bundleJs = patchBundledJS(bundleJs); await writeFile(`${dist}/client.js`, bundleJs, "utf-8"); } async function patchServiceWorker() { // Scan .client/ directory to build the full precache file list const clientDir = "client_bundle/client/.client"; const allFiles = await readdir(clientDir); const precacheFiles = [ "/", // The index page "/.client/manifest.json", // Dynamically generated by the server, but needed for PWA ...allFiles .filter( (f) => !f.endsWith(".map") && f !== "auth.html" && f !== "index.html" && f !== "LICENSE.md", ) .map((f) => `/.client/${f}`), ]; const precacheFilesStr = precacheFiles.join(","); // Patch the service_worker {{CACHE_NAME}} and {{PRECACHE_FILES}} let swCode = await readFile( "client_bundle/client/service_worker.js", "utf-8", ); swCode = swCode.replaceAll("{{CACHE_NAME}}", `cache-${Date.now()}`); swCode = swCode.replaceAll("{{PRECACHE_FILES}}", precacheFilesStr); await writeFile("client_bundle/client/service_worker.js", swCode, "utf-8"); } const isMain = process.argv[1] === fileURLToPath(import.meta.url); if (isMain) { await buildClient(); await esbuild.stop(); }