166 lines
4.7 KiB
TypeScript
166 lines
4.7 KiB
TypeScript
// 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<void>[] = [];
|
|
// 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";
|