Global asset management

This commit is contained in:
Thibault “Adædra” Hamel 2024-06-24 19:31:11 +02:00
parent 4b7c669ad0
commit 73849b7f04
7 changed files with 46 additions and 49 deletions

27
lib/assets.ts Normal file
View File

@ -0,0 +1,27 @@
import { globSync } from "glob";
import { readFileSync } from "node:fs";
import { env } from "node:process";
import { join } from "node:path";
export const assetMap: Map<string, string> = new Map();
export function reloadAssets() {
assetMap.clear();
globSync("dist/_assets/*.manifest")
.map(
(manifest) =>
JSON.parse(readFileSync(manifest).toString()) as { [orig: string]: string },
)
.forEach((mapping) =>
Object.entries(mapping).forEach(([orig, hashed]) => assetMap.set(orig, hashed)),
);
}
export function asset(path: string): string {
const realPath = assetMap.has(path) ? assetMap.get(path) : path;
return env.ASSETS_HOSTS
? new URL(realPath, env.ASSETS_HOSTS).toString()
: join("/_assets/", realPath);
}

View File

@ -1,19 +1,8 @@
import { Transform } from "node:stream";
import { createHash } from "node:crypto";
import File from "vinyl";
import { globSync } from "glob";
import { readFileSync } from "node:fs";
import { PRODUCTION } from "./environment.js";
export function readAssetManifest(): { [entry: string]: string } {
return Object.assign(
{},
...globSync("dist/_assets/*.manifest").map((path) =>
JSON.parse(readFileSync(path).toString()),
),
);
}
function fileHash(buffer: Buffer) {
const hash = createHash("sha256");
hash.update(buffer.toString());

View File

@ -1,16 +1,16 @@
import Postcss from "postcss";
import { readAssetManifest } from "../hash.js";
import { reloadAssets, assetMap } from "../assets.js";
export default (opts: any = {}): Postcss.Plugin => {
export default (): Postcss.Plugin => {
return {
postcssPlugin: "postcss-hashes",
Once() {
opts._mappings = readAssetManifest();
reloadAssets();
},
Declaration(decl: Postcss.Declaration) {
decl.value = decl.value.replace(/url\("([^"]+)"\)/, (v, url) => {
if (opts._mappings.hasOwnProperty(url)) {
return v.replace(url, opts._mappings[url]);
if (assetMap.has(url)) {
return v.replace(url, assetMap.get(url));
}
return v;

View File

@ -1,5 +1,4 @@
import { readFileSync } from "node:fs";
import { env } from "node:process";
import { join, basename, dirname } from "node:path";
import { Transform } from "node:stream";
import asciidoctor, { Document } from "asciidoctor";
@ -14,13 +13,13 @@ import { PRODUCTION, DEFAULT_DATE } from "../environment.js";
import rehypeExternalLinks from "rehype-external-links";
import visit from "unist-util-visit";
import { src, dest } from "gulp";
import { readAssetManifest } from "../hash.js";
import renderLayout from "../../src/layout.js";
import renderArticleLayout from "../../src/article.js";
import renderIndex from "../../src/index.js";
import { renderToStaticMarkup } from "preact-render-to-string";
import { SITE_TITLE, SITE_DESCRIPTION } from "../../src/constants.js";
import { JSX } from "preact/jsx-runtime";
import { reloadAssets } from "../assets.js";
const Asciidoctor = asciidoctor();
const EXTENSION_REGISTRY = Asciidoctor.Extensions.create();
@ -60,17 +59,7 @@ function renderDocument(root: JSX.Element): Buffer {
function renderArticle() {
const allArticles: [string, DateTime, Document][] = [];
const assetManifest = readAssetManifest();
const asset = (path: string) => {
if (assetManifest.hasOwnProperty(path)) {
path = assetManifest[path];
}
return env.ASSETS_HOSTS
? new URL(path, env.ASSETS_HOSTS).toString()
: join("/_assets/", path);
};
reloadAssets();
return new Transform({
readableObjectMode: true,
@ -98,7 +87,6 @@ function renderArticle() {
.use(rehypeStringify)
.process(article.convert());
const content = renderLayout({
asset,
title: article.getDoctitle({}) as string,
meta: {
description: article.getAttribute("description"),
@ -112,7 +100,6 @@ function renderArticle() {
},
Content: () =>
renderArticleLayout({
asset,
article,
date,
body: vfile.toString(),
@ -133,14 +120,13 @@ function renderArticle() {
try {
allArticles.sort(([, a], [, b]) => b.diff(a).toMillis());
const contents = renderLayout({
asset,
meta: {
description: SITE_DESCRIPTION,
"og:title": SITE_TITLE,
"og:type": "website",
"og:url": `https://adaedra.eu`,
},
Content: () => renderIndex({ articles: allArticles, asset }),
Content: () => renderIndex({ articles: allArticles }),
});
this.push(

View File

@ -1,14 +1,14 @@
import { Document } from "asciidoctor";
import { DateTime } from "luxon";
import { asset } from "../lib/assets.js";
type Props = {
article: Document;
date: DateTime;
body: string;
asset(path: string): string;
};
export default ({ asset, article, date, body }: Props) => (
export default ({ article, date, body }: Props) => (
<>
<nav>
<a href="/" title="Back to home page">

View File

@ -1,11 +1,8 @@
import { Document } from "asciidoctor";
import { DateTime } from "luxon";
import { asset } from "../lib/assets.js";
type HeaderProps = {
asset(path: string): string;
};
const Header = ({ asset }: HeaderProps) => (
const Header = () => (
<header class="index-header">
<div style={{ flex: 1 }}>
<h1>Adædra</h1>
@ -19,11 +16,11 @@ const Header = ({ asset }: HeaderProps) => (
</header>
);
type ArticleProps = {
type Props = {
articles: [string, DateTime, Document][];
};
const Articles = ({ articles }: ArticleProps) => (
const Articles = ({ articles }: Props) => (
<>
<h2>Latest articles</h2>
<ul class="index-list">
@ -42,8 +39,6 @@ const Articles = ({ articles }: ArticleProps) => (
</>
);
type Props = HeaderProps & ArticleProps;
const SOCIALS: [string, string, string][] = [
["Gitea", "https://gitea.adaedra.eu", "git"],
["Mastodon", "https://nerdculture.de/@adaedra", "mastodon"],
@ -74,10 +69,10 @@ const Socials = () => (
const Footer = () => <footer class="index-footer"></footer>;
export default ({ asset, articles }: Props) => (
export default (props: Props) => (
<>
<Header asset={asset} />
<Articles articles={articles} />
<Header />
<Articles {...props} />
<Socials />
<Footer />
</>

View File

@ -1,10 +1,10 @@
import { SITE_DEFAULT_META, SITE_TITLE } from "./constants.js";
import { JSX } from "preact/jsx-runtime";
import { asset } from "../lib/assets.js";
type Props = {
title?: string;
meta?: { [name: string]: string };
asset(path: string): string;
} & ({ content: string } | { Content(): JSX.Element });
export default (props: Props) => {
@ -22,7 +22,7 @@ export default (props: Props) => {
}}
/>
{metaTags}
<link rel="stylesheet" type="text/css" href={props.asset("index.css")} />
<link rel="stylesheet" type="text/css" href={asset("index.css")} />
</head>
<body>
{"Content" in props ? (