the asset system is reworked to support "dynamic" entries, where each entry is a separate file on disk containing the latest generation's headers+raw+gzip+zstd. when calling view.regenerate, it will look for pages that had "export const regenerate" during generation, and render those pages using the view system, but then store the results as assets instead of sending as a response. pages configured as regenerable are also bundled as views, using the non-aliasing key "page:${page.id}". this cannot alias because file paths may not contain a colon.
122 lines
3 KiB
TypeScript
122 lines
3 KiB
TypeScript
type Awaitable<T> = T | Promise<T>;
|
|
|
|
export function virtualFiles(
|
|
map: Record<
|
|
string,
|
|
| string
|
|
| esbuild.OnLoadResult
|
|
| (() => Awaitable<string | esbuild.OnLoadResult>)
|
|
>,
|
|
) {
|
|
return {
|
|
name: "clover vfs",
|
|
setup(b) {
|
|
b.onResolve(
|
|
{
|
|
filter: new RegExp(
|
|
`^(?:${
|
|
Object.keys(map).map((file) => string.escapeRegExp(file)).join(
|
|
"|",
|
|
)
|
|
})\$`,
|
|
),
|
|
},
|
|
({ path }) => ({ path, namespace: "vfs" }),
|
|
);
|
|
b.onLoad(
|
|
{ filter: /./, namespace: "vfs" },
|
|
async ({ path }) => {
|
|
let entry = map[path];
|
|
if (typeof entry === "function") entry = await entry();
|
|
return ({
|
|
resolveDir: ".",
|
|
loader: "ts",
|
|
...typeof entry === "string" ? { contents: entry } : entry,
|
|
});
|
|
},
|
|
);
|
|
},
|
|
} satisfies esbuild.Plugin;
|
|
}
|
|
|
|
export function banFiles(
|
|
files: string[],
|
|
) {
|
|
return {
|
|
name: "clover vfs",
|
|
setup(b) {
|
|
b.onResolve(
|
|
{
|
|
filter: new RegExp(
|
|
`^(?:${
|
|
files.map((file) => string.escapeRegExp(file)).join("|")
|
|
})\$`,
|
|
),
|
|
},
|
|
({ path, importer }) => {
|
|
throw new Error(
|
|
`Loading ${path} (from ${importer}) is banned!`,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
} satisfies esbuild.Plugin;
|
|
}
|
|
|
|
export function projectRelativeResolution(root = process.cwd() + "/src") {
|
|
return {
|
|
name: "project relative resolution ('@/' prefix)",
|
|
setup(b) {
|
|
b.onResolve({ filter: /^@\// }, ({ path: id }) => {
|
|
return {
|
|
path: path.resolve(root, id.slice(2)),
|
|
};
|
|
});
|
|
b.onResolve({ filter: /^#/ }, ({ path: id, importer }) => {
|
|
return {
|
|
path: hot.resolveFrom(importer, id),
|
|
};
|
|
});
|
|
},
|
|
} satisfies esbuild.Plugin;
|
|
}
|
|
|
|
export function markoViaBuildCache(): esbuild.Plugin {
|
|
return {
|
|
name: "marko via build cache",
|
|
setup(b) {
|
|
b.onLoad(
|
|
{ filter: /\.marko$/ },
|
|
async ({ path: file }) => {
|
|
const cacheEntry = markoCache.get(file);
|
|
if (!cacheEntry) {
|
|
if (!fs.existsSync(file)) {
|
|
console.warn(`File does not exist: ${file}`);
|
|
}
|
|
throw new Error("Marko file not in cache: " + file);
|
|
}
|
|
return ({
|
|
loader: "ts",
|
|
contents: cacheEntry.src,
|
|
resolveDir: path.dirname(file),
|
|
});
|
|
},
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
export function isIgnoredSource(source: string) {
|
|
return source.includes("<define:") ||
|
|
source.startsWith("vfs:") ||
|
|
source.startsWith("dropped:") ||
|
|
source.includes("node_modules");
|
|
}
|
|
|
|
import * as esbuild from "esbuild";
|
|
import * as string from "#sitegen/string";
|
|
import * as path from "node:path";
|
|
import * as fs from "#sitegen/fs";
|
|
import * as incr from "./incremental.ts";
|
|
import * as hot from "./hot.ts";
|
|
import { markoCache } from "./marko.ts";
|