Global asset management
This commit is contained in:
parent
4b7c669ad0
commit
73849b7f04
27
lib/assets.ts
Normal file
27
lib/assets.ts
Normal 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);
|
||||
}
|
11
lib/hash.ts
11
lib/hash.ts
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 />
|
||||
</>
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
Loading…
Reference in New Issue
Block a user