it was weird. i pressed delete on a subfolder, i think one of the pages.off folders that i was using. and then, suddenly, nvim on windows 7 decided to delete every file in the directory. they weren't shred off the space time continuum, but just marked deleted. i had to pay $80 to get access to a software that could see them. bleh! just seeing all my work, a little over a week, was pretty heart shattering. but i remembered that long ago, a close friend said i could call them whenever i was feeling sad. i finally took them up on that offer. the first time i've ever called someone for emotional support. but it's ok. i got it back. and the site framework is better than ever. i'm gonna commit and push more often. the repo is private anyways.
240 lines
7.4 KiB
TypeScript
240 lines
7.4 KiB
TypeScript
// This implements the ability to use TS, TSX, and more plugins
|
|
// in Node.js. It cannot be built on the ES module loader,
|
|
// because there is no exposed way to replace modules when
|
|
// needed (see nodejs#49442).
|
|
//
|
|
// It also allows using a simple compile cache, which is used by
|
|
// the site generator to determine when code changes.
|
|
export const projectRoot = path.resolve(import.meta.dirname, "../");
|
|
export const projectSrc = path.resolve(projectRoot, "src");
|
|
|
|
// Create a project-relative require. For convenience, it is generic-typed.
|
|
export const load = createRequire(
|
|
pathToFileURL(path.join(projectRoot, "run.js")).toString(),
|
|
) as {
|
|
<T = unknown>(id: string): T;
|
|
extensions: NodeJS.Dict<(mod: NodeJS.Module, file: string) => unknown>;
|
|
cache: NodeJS.Dict<NodeJS.Module>;
|
|
resolve: (id: string, o?: { paths: string[] }) => string;
|
|
};
|
|
export const { cache } = load;
|
|
|
|
// Register extensions by overwriting `require.extensions`
|
|
const require = load;
|
|
const exts = require.extensions;
|
|
exts[".ts"] = loadEsbuild;
|
|
exts[".tsx"] = loadEsbuild;
|
|
exts[".jsx"] = loadEsbuild;
|
|
exts[".marko"] = loadMarko;
|
|
exts[".mdx"] = loadMdx;
|
|
exts[".css"] = loadCss;
|
|
|
|
// Intercept all module load calls to track CSS imports + file times.
|
|
export interface FileStat {
|
|
cssImportsRecursive: string[] | null;
|
|
lastModified: number;
|
|
imports: string[];
|
|
/* Used by 'incremental.ts' */
|
|
srcIds: string[];
|
|
}
|
|
let fsGraph = new Map<string, FileStat>();
|
|
export function setFsGraph(g: Map<string, FileStat>) {
|
|
if (fsGraph.size > 0) {
|
|
throw new Error("Cannot restore fsGraph when it has been written into");
|
|
}
|
|
fsGraph = g;
|
|
}
|
|
export function getFsGraph() {
|
|
return fsGraph;
|
|
}
|
|
|
|
function shouldTrackPath(filename: string) {
|
|
return !filename.includes("node_modules") &&
|
|
!filename.includes(import.meta.dirname);
|
|
}
|
|
|
|
const Module = load<typeof import("node:module")>("node:module");
|
|
const ModulePrototypeUnderscoreCompile = Module.prototype._compile;
|
|
Module.prototype._compile = function (
|
|
content: string,
|
|
filename: string,
|
|
format: "module" | "commonjs",
|
|
) {
|
|
fs.writeMkdirSync(
|
|
".clover/debug-transpilation/" +
|
|
path.relative(projectRoot, filename).replaceAll("\\", "/").replaceAll(
|
|
"../",
|
|
"_/",
|
|
).replaceAll("/", "."),
|
|
content,
|
|
);
|
|
const result = ModulePrototypeUnderscoreCompile.call(
|
|
this,
|
|
content,
|
|
filename,
|
|
format,
|
|
);
|
|
const stat = fs.statSync(filename);
|
|
if (shouldTrackPath(filename)) {
|
|
const cssImportsMaybe: string[] = [];
|
|
const imports: string[] = [];
|
|
for (const { filename: file } of this.children) {
|
|
const relative = path.relative(projectRoot, file);
|
|
if (file.endsWith(".css")) cssImportsMaybe.push(relative);
|
|
else {
|
|
const child = fsGraph.get(relative);
|
|
if (!child) continue;
|
|
const { cssImportsRecursive } = child;
|
|
if (cssImportsRecursive) cssImportsMaybe.push(...cssImportsRecursive);
|
|
imports.push(relative);
|
|
}
|
|
}
|
|
const relative = path.relative(projectRoot, filename);
|
|
fsGraph.set(relative, {
|
|
cssImportsRecursive: cssImportsMaybe.length > 0
|
|
? Array.from(new Set(cssImportsMaybe))
|
|
: null,
|
|
imports,
|
|
lastModified: stat.mtimeMs,
|
|
srcIds: [],
|
|
});
|
|
}
|
|
return result;
|
|
};
|
|
|
|
// Implement @/ prefix
|
|
const ModuleUnderscoreResolveFilename = Module._resolveFilename;
|
|
Module._resolveFilename = (...args) => {
|
|
if (args[0].startsWith("@/")) {
|
|
const replacedPath = "." + args[0].slice(1);
|
|
try {
|
|
return require.resolve(replacedPath, { paths: [projectSrc] });
|
|
} catch (err: any) {
|
|
if (err.code === "MODULE_NOT_FOUND" && err.requireStack.length <= 1) {
|
|
err.message.replace(replacedPath, args[0]);
|
|
}
|
|
}
|
|
}
|
|
return ModuleUnderscoreResolveFilename(...args);
|
|
};
|
|
|
|
function loadEsbuild(module: NodeJS.Module, filepath: string) {
|
|
let src = fs.readFileSync(filepath, "utf8");
|
|
return loadEsbuildCode(module, filepath, src);
|
|
}
|
|
|
|
function loadEsbuildCode(module: NodeJS.Module, filepath: string, src: string) {
|
|
if (filepath === import.meta.filename) {
|
|
module.exports = self;
|
|
return;
|
|
}
|
|
|
|
let loader: any = "tsx";
|
|
if (filepath.endsWith(".ts")) loader = "ts";
|
|
else if (filepath.endsWith(".jsx")) loader = "jsx";
|
|
else if (filepath.endsWith(".js")) loader = "js";
|
|
if (src.includes("import.meta")) {
|
|
src = `
|
|
import.meta.url = ${JSON.stringify(pathToFileURL(filepath).toString())};
|
|
import.meta.dirname = ${JSON.stringify(path.dirname(filepath))};
|
|
import.meta.filename = ${JSON.stringify(filepath)};
|
|
` + src;
|
|
}
|
|
src = esbuild.transformSync(src, {
|
|
loader,
|
|
format: "cjs",
|
|
target: "esnext",
|
|
jsx: "automatic",
|
|
jsxImportSource: "#ssr",
|
|
}).code;
|
|
return module._compile(src, filepath, "commonjs");
|
|
}
|
|
|
|
function loadMarko(module: NodeJS.Module, filepath: string) {
|
|
let src = fs.readFileSync(filepath, "utf8");
|
|
// A non-standard thing here is Clover Sitegen implements
|
|
// its own client side scripting stuff, so it overrides
|
|
// bare client import statements to it's own usage.
|
|
if (src.match(/^\s*client\s+import\s+["']/m)) {
|
|
src = src.replace(
|
|
/^\s*client\s+import\s+("[^"]+|'[^']+)[^\n]+/m,
|
|
"<CloverScriptInclude src=$1 />",
|
|
) + '\nimport { Script as CloverScriptInclude } from "#sitegen";';
|
|
}
|
|
|
|
src = marko.compileSync(filepath, {}).code;
|
|
src = src.replace("marko/debug/html", "#ssr/marko");
|
|
return loadEsbuildCode(module, filepath, src);
|
|
}
|
|
|
|
function loadMdx(module: NodeJS.Module, filepath: string) {
|
|
const input = fs.readFileSync(filepath);
|
|
const out = mdx.compileSync(input, { jsxImportSource: "#ssr" }).value;
|
|
const src = typeof out === "string" ? out : Buffer.from(out).toString("utf8");
|
|
return loadEsbuildCode(module, filepath, src);
|
|
}
|
|
|
|
function loadCss(module: NodeJS.Module, filepath: string) {
|
|
module.exports = {};
|
|
}
|
|
|
|
export function reloadRecursive(filepath: string) {
|
|
filepath = path.resolve(filepath);
|
|
const existing = cache[filepath];
|
|
if (existing) deleteRecursive(filepath, existing);
|
|
fsGraph.clear();
|
|
return require(filepath);
|
|
}
|
|
|
|
function deleteRecursive(id: string, module: any) {
|
|
if (id.includes(path.sep + "node_modules" + path.sep)) {
|
|
return;
|
|
}
|
|
delete cache[id];
|
|
for (const child of module.children) {
|
|
if (child.filename.includes("/engine/")) return;
|
|
const existing = cache[child.filename];
|
|
if (existing === child) deleteRecursive(child.filename, existing);
|
|
}
|
|
}
|
|
|
|
export function getCssImports(filepath: string) {
|
|
filepath = path.resolve(filepath);
|
|
if (!require.cache[filepath]) throw new Error(filepath + " was never loaded");
|
|
return fsGraph.get(path.relative(projectRoot, filepath))
|
|
?.cssImportsRecursive ?? [];
|
|
}
|
|
|
|
export function resolveFrom(src: string, dest: string) {
|
|
try {
|
|
return createRequire(src).resolve(dest);
|
|
} catch (err: any) {
|
|
if (err.code === "MODULE_NOT_FOUND" && err.requireStack.length <= 1) {
|
|
err.message = err.message.split("\n")[0] + " from '" + src + "'";
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
namespace NodeJS {
|
|
interface Module {
|
|
_compile(
|
|
this: NodeJS.Module,
|
|
content: string,
|
|
filepath: string,
|
|
format: "module" | "commonjs",
|
|
): unknown;
|
|
}
|
|
}
|
|
}
|
|
|
|
import * as fs from "./fs.ts";
|
|
import * as path from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import * as esbuild from "esbuild";
|
|
import * as marko from "@marko/compiler";
|
|
import { createRequire } from "node:module";
|
|
import * as mdx from "@mdx-js/mdx";
|
|
import * as self from "./hot.ts";
|
|
import { Buffer } from "node:buffer";
|