diff --git a/package.json b/package.json index 8b7ce1e..e561ba0 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "build:js": "vite build", "build": "bun run build:js && zola build", - "dev:js": "vite build --watch" + "dev:js": "vite build --watch", + "scan:size": "bun scripts/scan-output-size.ts" }, "dependencies": { "katex": "^0.16.47", diff --git a/scripts/scan-output-size.ts b/scripts/scan-output-size.ts new file mode 100644 index 0000000..a704b49 --- /dev/null +++ b/scripts/scan-output-size.ts @@ -0,0 +1,102 @@ +import { readdir, stat } from "node:fs/promises"; +import { join, relative } from "node:path"; + +const OUTPUT_DIRS = [ + { label: "Zola (deploy)", path: "public" }, + { label: "Vite JS/CSS", path: "themes/openfreebooks/static/js" }, +] as const; + +const TOP_FILES = 15; + +type FileEntry = { path: string; bytes: number }; + +async function collectFiles( + dir: string, + root: string, + entries: FileEntry[], +): Promise { + let items; + try { + items = await readdir(dir, { withFileTypes: true }); + } catch { + return; + } + + for (const item of items) { + const fullPath = join(dir, item.name); + if (item.isDirectory()) { + await collectFiles(fullPath, root, entries); + continue; + } + if (!item.isFile()) continue; + + const { size } = await stat(fullPath); + entries.push({ path: relative(root, fullPath), bytes: size }); + } +} + +function formatBytes(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; +} + +function padEnd(value: string, width: number): string { + return value.length >= width ? value : value + " ".repeat(width - value.length); +} + +async function scanDir(label: string, root: string) { + const entries: FileEntry[] = []; + await collectFiles(root, root, entries); + + if (entries.length === 0) { + console.log(`\n${label} (${root}/)`); + console.log(" (missing or empty — run `bun run build` first)\n"); + return null; + } + + const total = entries.reduce((sum, e) => sum + e.bytes, 0); + entries.sort((a, b) => b.bytes - a.bytes); + + console.log(`\n${label} (${root}/)`); + console.log(` files: ${entries.length}`); + console.log(` total: ${formatBytes(total)} (${total.toLocaleString()} bytes)`); + + const labelWidth = Math.min( + 52, + Math.max(...entries.slice(0, TOP_FILES).map((e) => e.path.length), 4), + ); + + console.log(`\n largest files:`); + for (const entry of entries.slice(0, TOP_FILES)) { + console.log( + ` ${padEnd(entry.path, labelWidth)} ${padEnd(formatBytes(entry.bytes), 10)} ${entry.bytes.toLocaleString()} B`, + ); + } + + return { label, root, total, fileCount: entries.length, entries }; +} + +async function main() { + const cwd = process.cwd(); + console.log(`Output size scan — ${cwd}`); + + const results = []; + for (const { label, path } of OUTPUT_DIRS) { + const result = await scanDir(label, join(cwd, path)); + if (result) results.push(result); + } + + if (results.length > 0) { + const grandTotal = results.reduce((sum, r) => sum + r.total, 0); + console.log("\n---"); + console.log( + `Combined tracked output: ${formatBytes(grandTotal)} (${grandTotal.toLocaleString()} bytes)`, + ); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/themes/openfreebooks/templates/index.html b/themes/openfreebooks/templates/index.html index 184b29d..3eb99dc 100644 --- a/themes/openfreebooks/templates/index.html +++ b/themes/openfreebooks/templates/index.html @@ -10,7 +10,14 @@
-

Open textbooks. Free to read. Open to improve.

+

+ Future of textbooks. + Free + to read. + Open + to improve. + +

High-quality learning materials for elementary school through university — on the web, at no cost.