Implement hashing of assets from scratch

This is a great idea... for sure
This commit is contained in:
Sofía Aritz 2025-04-26 14:43:07 +02:00
parent c5e968ce0d
commit a60159ad68
Signed by: sofia
GPG key ID: 5A1485B4CCCDDB4A
2 changed files with 93 additions and 9 deletions

View file

@ -1,7 +1,9 @@
import { createRequire } from "module"
const require = createRequire(import.meta.url)
import { join } from "path";
import { basename, dirname, extname, join, relative } from "path";
import { copyFile, mkdir, readdir, readFile, stat, unlink } from "fs/promises";
import { createHash } from "crypto";
import { DateTime } from "luxon";
@ -14,6 +16,70 @@ import markdownItAnchor from "markdown-it-anchor";
const markdownItEmoji = require("markdown-it-emoji");
import markdownItFootnote from "markdown-it-footnote";
const HASHED_ASSETS = [".css", ".js"]
const assetsManifest = {}
async function hashFile(filePath) {
const content = await readFile(filePath)
return createHash("sha1").update(content).digest("hex").slice(0, 12)
}
async function walk(dir) {
let files = [];
for (const item of await readdir(dir, { withFileTypes: true })) {
const fullPath = join(dir, item.name);
if (item.isDirectory()) {
files = files.concat(await walk(fullPath));
} else if (item.isFile()) {
files.push(fullPath);
}
}
return files;
}
async function prepareManifest(assetsPath, log = console.log) {
const files = await walk(assetsPath);
for (const absPath of files) {
if (!(await stat(absPath)).isFile()) continue;
const ext = extname(absPath)
if (!HASHED_ASSETS.includes(ext)) {
const rel = relative(assetsPath, absPath)
assetsManifest[rel] = rel
continue;
}
const base = basename(absPath, ext)
if (base.endsWith("__h")) continue;
const hash = await hashFile(absPath)
const newBase = `${base}-${hash}__h${ext}`
const relDir = dirname(relative(assetsPath, absPath))
const newRelPath = relDir === "."
? newBase
: join(relDir, newBase)
assetsManifest[relative(assetsPath, absPath)] = newRelPath
log(`[sofi-assets] Manifest: '${absPath}' to '${newRelPath}'`)
}
}
async function processAssets(assetsPath, outputPath, log = console.log) {
for (const [origRel, newRel] of Object.entries(assetsManifest)) {
const src = join(assetsPath, origRel);
const dest = join(outputPath, newRel);
await mkdir(dirname(dest), { recursive: true })
await copyFile(src, dest);
log(`[sofi-assets] Copied: '${src}' to '${dest}'`);
}
}
const markdownItOptions = {
html: true,
linkify: true,
@ -47,8 +113,26 @@ function dateDesc(a, b) {
return DateTime.fromJSDate(b.data.date) - DateTime.fromJSDate(a.data.date);
}
export default function (eleventyConfig) {
eleventyConfig.addPassthroughCopy({ "assets": "/" })
export default async function (eleventyConfig) {
const log = eleventyConfig.logger.log.bind(eleventyConfig.logger)
eleventyConfig.addWatchTarget("./assets/")
eleventyConfig.on("eleventy.before", async () => {
prepareManifest("assets", log)
})
eleventyConfig.on("eleventy.after", async () => {
log("[sofi-assets] Build done, performing asset processing")
await processAssets("assets", join(dirOptions.output, "/assets/"), log)
})
eleventyConfig.addShortcode("assetPath", (asset) => {
if (assetsManifest[asset] != null) {
return `/assets/${assetsManifest[asset]}`
} else {
throw `Asset '${asset}' is not in asset manifest`
}
})
eleventyConfig.addCollection("weblog", (collection) => {
return collection.getFilteredByGlob(join(dirOptions.input, "/weblog/*"))

View file

@ -7,12 +7,12 @@
<meta name="description" content="{{ description | default: site.description }}">
<meta name="fediverse:creator" content="@sofiaritz@hachyderm.io">
<link href="https://cdn.sofiaritz.com" rel="preconnect" crossorigin>
<link rel="stylesheet" href="/styles/root.css">
<link rel="stylesheet" href="/styles/fonts.css">
<link rel="stylesheet" href="/styles/layout.css">
<link rel="stylesheet" href="/styles/text.css">
<link rel="stylesheet" href="/styles/components.css">
<link href="/styles/prism-coldark-cold.css" rel="stylesheet">
<link rel="stylesheet" href="{% assetPath 'styles/root.css' %}">
<link rel="stylesheet" href="{% assetPath 'styles/fonts.css' %}">
<link rel="stylesheet" href="{% assetPath 'styles/layout.css' %}">
<link rel="stylesheet" href="{% assetPath 'styles/text.css' %}">
<link rel="stylesheet" href="{% assetPath 'styles/components.css' %}">
<link rel="stylesheet" href="{% assetPath 'styles/prism-coldark-cold.css' %}">
<title>{% if title %}{{ title }} - {% endif %}{{ site.title }}</title>
<link rel="alternate" href="{{ site.url }}/feed.xml" type="application/atom+xml" title="{{ site.title }} Feed">
</head>