This commit is contained in:
chloe caruso 2025-07-07 20:58:02 -07:00
parent f1b1c650ce
commit ea5f2bc325
48 changed files with 5217 additions and 5177 deletions

View file

@ -16,7 +16,10 @@
pkgs.nodejs_24 # runtime pkgs.nodejs_24 # runtime
pkgs.deno # formatter pkgs.deno # formatter
(pkgs.ffmpeg.override { (pkgs.ffmpeg.override {
withOpus = true;
withSvtav1 = true; withSvtav1 = true;
withJxl = true;
withWebp = true;
}) })
]; ];
}; };

View file

@ -96,7 +96,9 @@ Module._resolveFilename = (...args) => {
try { try {
return require.resolve(replacedPath, { paths: [projectSrc] }); return require.resolve(replacedPath, { paths: [projectSrc] });
} catch (err: any) { } catch (err: any) {
if (err.code === "MODULE_NOT_FOUND" && (err?.requireStack?.length ?? 0) <= 1) { if (
err.code === "MODULE_NOT_FOUND" && (err?.requireStack?.length ?? 0) <= 1
) {
err.message.replace(replacedPath, args[0]); err.message.replace(replacedPath, args[0]);
} }
} }

View file

@ -107,7 +107,7 @@ export class Queue<T, R> {
try { try {
active.unshift(spinner); active.unshift(spinner);
bar.props = { active }; bar.props = { active };
console.log(this.#name + ": " + itemText); // console.log(this.#name + ": " + itemText);
const result = await this.#fn(args, spinner); const result = await this.#fn(args, spinner);
this.#done++; this.#done++;
return result; return result;
@ -116,6 +116,7 @@ export class Queue<T, R> {
(err as any).job = itemText; (err as any).job = itemText;
} }
this.#errors.push(err); this.#errors.push(err);
console.error(util.inspect(err, false, Infinity, true));
throw err; throw err;
} finally { } finally {
active.splice(active.indexOf(spinner), 1); active.splice(active.indexOf(spinner), 1);
@ -295,3 +296,4 @@ import { Progress } from "@paperclover/console/Progress";
import { Spinner } from "@paperclover/console/Spinner"; import { Spinner } from "@paperclover/console/Spinner";
import * as path from "node:path"; import * as path from "node:path";
import process from "node:process"; import process from "node:process";
import * as util from "node:util";

View file

@ -4,27 +4,27 @@ this repository contains clover's "sitegen" framework, which is a set of tools
that assist building websites. these tools power https://paperclover.net. that assist building websites. these tools power https://paperclover.net.
- **HTML "Server Side Rendering") engine written from scratch.** (~500 lines) - **HTML "Server Side Rendering") engine written from scratch.** (~500 lines)
- A more practical JSX runtime (`class` instead of `className`, built-in - A more practical JSX runtime (`class` instead of `className`, built-in
`clsx`, `html()` helper over `dangerouslySetInnerHTML` prop, etc). `clsx`, `html()` helper over `dangerouslySetInnerHTML` prop, etc).
- Integration with [Marko][1] for concisely written components. - Integration with [Marko][1] for concisely written components.
- TODO: MDX-like compiler for content-heavy pages like blogs. - TODO: MDX-like compiler for content-heavy pages like blogs.
- Different languages can be used at the same time. Supports - Different languages can be used at the same time. Supports `async function`
`async function` components, `<Suspense />`, and custom extensions. components, `<Suspense />`, and custom extensions.
- **Incremental static site generator and build system.** - **Incremental static site generator and build system.**
- Build entire production site at start, incremental updates when pages - Build entire production site at start, incremental updates when pages
change; Build system state survives coding sessions. change; Build system state survives coding sessions.
- The only difference in development and production mode is hidden - The only difference in development and production mode is hidden source-maps
source-maps and stripped `console.debug` calls. The site you and stripped `console.debug` calls. The site you see locally is the same
see locally is the same site you see deployed. site you see deployed.
- (TODO) Tests, Lints, and Type-checking is run alongside, and only re-runs - (TODO) Tests, Lints, and Type-checking is run alongside, and only re-runs
checks when the files change. For example, changing a component re-tests checks when the files change. For example, changing a component re-tests
only pages that use that component and re-lints only the changed file. only pages that use that component and re-lints only the changed file.
- **Integrated libraries for building complex, content heavy web sites.** - **Integrated libraries for building complex, content heavy web sites.**
- Static asset serving with ETag and build-time compression. - Static asset serving with ETag and build-time compression.
- Dynamicly rendered pages with static client. (`#import "#sitegen/view"`) - Dynamicly rendered pages with static client. (`#import "#sitegen/view"`)
- Databases with a typed SQLite wrapper. (`import "#sitegen/sqlite"`) - Databases with a typed SQLite wrapper. (`import "#sitegen/sqlite"`)
- TODO: Meta and Open Graph generation. (`export const meta`) - TODO: Meta and Open Graph generation. (`export const meta`)
- TODO: Font subsetting tools to reduce bytes downloaded by fonts. - TODO: Font subsetting tools to reduce bytes downloaded by fonts.
- **Built on the battle-tested Node.js runtime.** - **Built on the battle-tested Node.js runtime.**
[1]: https://next.markojs.com [1]: https://next.markojs.com
@ -42,6 +42,7 @@ Included is `src`, which contains `paperclover.net`. Website highlights:
## Development ## Development
minimum system requirements: minimum system requirements:
- a cpu with at least 1 core. - a cpu with at least 1 core.
- random access memory. - random access memory.
- windows 7 or later, macos, or other operating system. - windows 7 or later, macos, or other operating system.
@ -73,4 +74,3 @@ open a shell with all needed system dependencies.
## Contributions ## Contributions
No contributions to `src` accepted, only `framework`. No contributions to `src` accepted, only `framework`.

2
run.js
View file

@ -12,7 +12,7 @@ if (!zlib.zstdCompress) {
: null; : null;
globalThis.console.error( globalThis.console.error(
`sitegen depends on a node.js-compatibile runtime that supports zstd compression\n` + `sitegen depends on a node.js-compatibile runtime\n` +
`this is node.js version ${process.version}${ `this is node.js version ${process.version}${
brand ? ` (${brand})` : "" brand ? ` (${brand})` : ""
}\n\n` + }\n\n` +

View file

@ -252,6 +252,7 @@ interface Process {
enable?: boolean; enable?: boolean;
include: Set<string>; include: Set<string>;
depends?: string[]; depends?: string[];
version?: number;
/* Perform an action. */ /* Perform an action. */
run(args: ProcessFileArgs): Promise<void>; run(args: ProcessFileArgs): Promise<void>;
/* Should detect if `run` was never even run before before undoing state */ /* Should detect if `run` was never even run before before undoing state */
@ -384,6 +385,7 @@ const procImageSubsets: Process = {
name: "encode image subsets", name: "encode image subsets",
include: rules.extsImage, include: rules.extsImage,
depends: ["calculate dimensions"], depends: ["calculate dimensions"],
version: 2,
async run({ absPath, mediaFile, spin }) { async run({ absPath, mediaFile, spin }) {
const { width, height } = UNWRAP(mediaFile.parseDimensions()); const { width, height } = UNWRAP(mediaFile.parseDimensions());
const targetSizes = transcodeRules.imageSizes.filter((w) => w < width); const targetSizes = transcodeRules.imageSizes.filter((w) => w < width);
@ -485,6 +487,7 @@ const procVideos = transcodeRules.videoFormats.map<Process>((preset) => ({
title: fakeProgress.text, title: fakeProgress.text,
progress: fakeProgress, progress: fakeProgress,
args, args,
cwd: base,
}); });
return await collectFiles(); return await collectFiles();
} catch (err) { } catch (err) {
@ -522,7 +525,10 @@ const processors = [
// Create a unique key. // Create a unique key.
hash: new Uint16Array( hash: new Uint16Array(
crypto.createHash("sha1") crypto.createHash("sha1")
.update(process.run.toString()) .update(
process.run.toString() +
(process.version ? String(process.version) : ""),
)
.digest().buffer, .digest().buffer,
).reduce((a, b) => a ^ b), ).reduce((a, b) => a ^ b),
depends: (process.depends ?? []).map((depend) => { depends: (process.depends ?? []).map((depend) => {

View file

@ -135,10 +135,7 @@ function highlightLines({
export const getRegistry = async.once(async () => { export const getRegistry = async.once(async () => {
const wasmBin = await fs.readFile( const wasmBin = await fs.readFile(
path.join( require.resolve("vscode-oniguruma/release/onig.wasm"),
import.meta.dirname,
"../node_modules/vscode-oniguruma/release/onig.wasm",
),
); );
await oniguruma.loadWASM(wasmBin); await oniguruma.loadWASM(wasmBin);

View file

@ -10,7 +10,7 @@ db.table(
create table if not exists asset_ref_files ( create table if not exists asset_ref_files (
file text not null, file text not null,
id integer not null, id integer not null,
foreign key (id) references asset_refs(id) foreign key (id) references asset_refs(id) ON DELETE CASCADE
); );
create index asset_ref_files_id on asset_ref_files(id); create index asset_ref_files_id on asset_ref_files(id);
`, `,

View file

@ -56,11 +56,15 @@ export const extsImage = new Set([
".jpg", ".jpg",
".jpeg", ".jpeg",
".png", ".png",
".gif",
".webp", ".webp",
".avif", ".avif",
".heic", ".heic",
]);
/** These files show an image embed, but aren't optimized */
export const extsImageLike = new Set([
...extsImage,
".svg", ".svg",
".gif",
]); ]);
/** These files populate `duration` using `ffprobe` */ /** These files populate `duration` using `ffprobe` */

View file

@ -99,14 +99,38 @@ export const imagePresets = [
"6", "6",
], ],
}, },
// TODO: avif {
ext: ".avif",
args: [
"-c:v",
"libaom-av1",
"-crf",
"30",
"-pix_fmt",
"yuv420p10le",
],
},
{ {
ext: ".jxl", ext: ".jxl",
args: ["-c:v", "libjxl", "-distance", "0.8", "-effort", "9"], args: [
"-c:v",
"libjxl",
"-distance",
"0.8",
"-effort",
"9",
"-update",
"-frames:v",
"1",
],
}, },
]; ];
export function getVideoArgs(preset: VideoEncodePreset, outbase: string, input: string[]) { export function getVideoArgs(
preset: VideoEncodePreset,
outbase: string,
input: string[],
) {
const cmd = [...input]; const cmd = [...input];
if (preset.codec === "av1") { if (preset.codec === "av1") {

View file

@ -27,12 +27,12 @@ li a {
border-radius: 4px; border-radius: 4px;
} }
li a:hover { li a:hover {
background-color: rgba(255,255,255,0.2); background-color: rgba(255, 255, 255, 0.2);
font-weight: bold; font-weight: bold;
text-decoration: none!important; text-decoration: none !important;
} }
.dir a { .dir a {
color: #99eeFF color: #99eeff;
} }
.ext { .ext {
opacity: 0.5; opacity: 0.5;

View file

@ -118,4 +118,3 @@ code {
font-family: "rmo", monospace; font-family: "rmo", monospace;
font-size: inherit; font-size: inherit;
} }

View file

@ -1,4 +1,4 @@
body,html { body, html {
overflow: hidden; overflow: hidden;
} }
h1 { h1 {
@ -41,7 +41,7 @@ footer h2 {
header h2, header em, footer h2, footer em { header h2, header em, footer h2, footer em {
display: inline-block; display: inline-block;
} }
header em, footer em { header em, footer em {
margin-left: 16px!important; margin-left: 16px !important;
text-align: right; text-align: right;
} }

View file

@ -3,7 +3,9 @@
<title>paper clover</title> <title>paper clover</title>
</head> </head>
<body bgcolor="black" style="word-wrap: initial"> <body bgcolor="black" style="word-wrap: initial">
<main style="display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh"> <main
style="display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh"
>
<div> <div>
<p style="margin: 0.5rem 0"> <p style="margin: 0.5rem 0">
<a <a
@ -56,7 +58,9 @@
<font color="#FF8147">feed</font> <font color="#FF8147">feed</font>
</a> </a>
</p> </p>
<h1 style="margin: -1.5rem 0 3rem 0; font-size: 7rem; font-weight: 400; font-family: times"> <h1
style="margin: -1.5rem 0 3rem 0; font-size: 7rem; font-weight: 400; font-family: times"
>
<font color="#B8E1FF">paper</font> <font color="#B8E1FF">paper</font>
<font color="#E8F4FF">clover</font> <font color="#E8F4FF">clover</font>
</h1> </h1>

View file

@ -22,4 +22,3 @@
margin-top: -1px; margin-top: -1px;
padding-bottom: 2px; padding-bottom: 2px;
} }