// This file implements client-side bundling, mostly wrapping esbuild. const clientPlugins: 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: clientPlugins, splitting: true, write: false, metafile: true, }); if (bundle.errors.length || bundle.warnings.length) { throw new AggregateError( bundle.errors.concat(bundle.warnings), "JS bundle failed", ); } const publicScriptRoutes = extraPublicScripts.map((file) => path.basename(file).replace(/\.client\.[tj]sx?/, "") ); const { metafile } = bundle; console.log(metafile); const promises: Promise[] = []; // TODO: add a shared build hash to entrypoints, derived from all the chunk hashes. for (const file of bundle.outputFiles) { const { text } = file; let route = file.path.replace(/^.*!/, "").replaceAll("\\", "/"); const { inputs } = UNWRAP(metafile.outputs["out!" + route]); const sources = Object.keys(inputs); // Register non-chunks as script entries. const chunk = route.startsWith("/js/c."); if (!chunk) { route = route.replace(".client.js", ".js"); incr.put({ sources, type: "script", key: route.slice("/js/".length, -".js".length), value: text, }); } // Register chunks and public scripts as assets. if (chunk || publicScriptRoutes.includes(route)) { promises.push(incr.putAsset({ sources, key: route, body: text, })); } } await Promise.all(promises); } type ServerPlatform = "node" | "passthru"; export async function bundleServerJavaScript( /** Has 'export default app;' */ backendEntryPoint: string, /** Views for dynamic loading */ viewEntryPoints: FileItem[], platform: ServerPlatform = "node", ) { const scriptMagic = "CLOVER_CLIENT_SCRIPTS_DEFINITION"; const viewSource = [ ...viewEntryPoints.map((view, i) => `import * as view${i} from ${JSON.stringify(view.file)}` ), `const scripts = ${scriptMagic}[-1]`, "export const views = {", ...viewEntryPoints.flatMap((view, i) => [ ` ${JSON.stringify(view.id)}: {`, ` component: view${i}.default,`, ` meta: view${i}.meta,`, ` layout: view${i}.layout?.default,`, ` theme: view${i}.layout?.theme ?? view${i}.theme,`, ` scripts: ${scriptMagic}[${i}]`, ` },`, ]), "}", ].join("\n"); const serverPlugins: esbuild.Plugin[] = [ virtualFiles({ "$views": viewSource, }), banFiles([ "hot.ts", "incremental.ts", "bundle.ts", "generate.ts", "css.ts", ].map((subPath) => path.join(hot.projectRoot, "framework/" + subPath))), { name: "marko", setup(b) { b.onLoad({ filter: /\.marko$/ }, async ({ path }) => { const src = await fs.readFile(path); const result = await marko.compile(src, path, { output: "html", }); return { loader: "ts", contents: result.code, }; }); }, }, ]; const bundle = await esbuild.build({ bundle: true, chunkNames: "/js/c.[hash]", entryNames: "/js/[name]", assetNames: "/asset/[hash]", entryPoints: [backendEntryPoint], platform: "node", format: "esm", minify: false, // outdir: "/out!", outdir: ".clover/wah", plugins: serverPlugins, splitting: true, write: true, external: ["@babel/preset-typescript"], }); console.log(bundle); throw new Error("wahhh"); } import * as esbuild from "esbuild"; import * as path from "node:path"; import process from "node:process"; import * as hot from "./hot.ts"; import { banFiles, virtualFiles } from "./esbuild-support.ts"; import { Incremental } from "./incremental.ts"; import type { FileItem } from "#sitegen"; import * as marko from "@marko/compiler"; import * as fs from "./lib/fs.ts";