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 { Transform } from "node:stream";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import File from "vinyl";
|
import File from "vinyl";
|
||||||
import { globSync } from "glob";
|
|
||||||
import { readFileSync } from "node:fs";
|
|
||||||
import { PRODUCTION } from "./environment.js";
|
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) {
|
function fileHash(buffer: Buffer) {
|
||||||
const hash = createHash("sha256");
|
const hash = createHash("sha256");
|
||||||
hash.update(buffer.toString());
|
hash.update(buffer.toString());
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import Postcss from "postcss";
|
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 {
|
return {
|
||||||
postcssPlugin: "postcss-hashes",
|
postcssPlugin: "postcss-hashes",
|
||||||
Once() {
|
Once() {
|
||||||
opts._mappings = readAssetManifest();
|
reloadAssets();
|
||||||
},
|
},
|
||||||
Declaration(decl: Postcss.Declaration) {
|
Declaration(decl: Postcss.Declaration) {
|
||||||
decl.value = decl.value.replace(/url\("([^"]+)"\)/, (v, url) => {
|
decl.value = decl.value.replace(/url\("([^"]+)"\)/, (v, url) => {
|
||||||
if (opts._mappings.hasOwnProperty(url)) {
|
if (assetMap.has(url)) {
|
||||||
return v.replace(url, opts._mappings[url]);
|
return v.replace(url, assetMap.get(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { env } from "node:process";
|
|
||||||
import { join, basename, dirname } from "node:path";
|
import { join, basename, dirname } from "node:path";
|
||||||
import { Transform } from "node:stream";
|
import { Transform } from "node:stream";
|
||||||
import asciidoctor, { Document } from "asciidoctor";
|
import asciidoctor, { Document } from "asciidoctor";
|
||||||
|
@ -14,13 +13,13 @@ import { PRODUCTION, DEFAULT_DATE } from "../environment.js";
|
||||||
import rehypeExternalLinks from "rehype-external-links";
|
import rehypeExternalLinks from "rehype-external-links";
|
||||||
import visit from "unist-util-visit";
|
import visit from "unist-util-visit";
|
||||||
import { src, dest } from "gulp";
|
import { src, dest } from "gulp";
|
||||||
import { readAssetManifest } from "../hash.js";
|
|
||||||
import renderLayout from "../../src/layout.js";
|
import renderLayout from "../../src/layout.js";
|
||||||
import renderArticleLayout from "../../src/article.js";
|
import renderArticleLayout from "../../src/article.js";
|
||||||
import renderIndex from "../../src/index.js";
|
import renderIndex from "../../src/index.js";
|
||||||
import { renderToStaticMarkup } from "preact-render-to-string";
|
import { renderToStaticMarkup } from "preact-render-to-string";
|
||||||
import { SITE_TITLE, SITE_DESCRIPTION } from "../../src/constants.js";
|
import { SITE_TITLE, SITE_DESCRIPTION } from "../../src/constants.js";
|
||||||
import { JSX } from "preact/jsx-runtime";
|
import { JSX } from "preact/jsx-runtime";
|
||||||
|
import { reloadAssets } from "../assets.js";
|
||||||
|
|
||||||
const Asciidoctor = asciidoctor();
|
const Asciidoctor = asciidoctor();
|
||||||
const EXTENSION_REGISTRY = Asciidoctor.Extensions.create();
|
const EXTENSION_REGISTRY = Asciidoctor.Extensions.create();
|
||||||
|
@ -60,17 +59,7 @@ function renderDocument(root: JSX.Element): Buffer {
|
||||||
|
|
||||||
function renderArticle() {
|
function renderArticle() {
|
||||||
const allArticles: [string, DateTime, Document][] = [];
|
const allArticles: [string, DateTime, Document][] = [];
|
||||||
const assetManifest = readAssetManifest();
|
reloadAssets();
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Transform({
|
return new Transform({
|
||||||
readableObjectMode: true,
|
readableObjectMode: true,
|
||||||
|
@ -98,7 +87,6 @@ function renderArticle() {
|
||||||
.use(rehypeStringify)
|
.use(rehypeStringify)
|
||||||
.process(article.convert());
|
.process(article.convert());
|
||||||
const content = renderLayout({
|
const content = renderLayout({
|
||||||
asset,
|
|
||||||
title: article.getDoctitle({}) as string,
|
title: article.getDoctitle({}) as string,
|
||||||
meta: {
|
meta: {
|
||||||
description: article.getAttribute("description"),
|
description: article.getAttribute("description"),
|
||||||
|
@ -112,7 +100,6 @@ function renderArticle() {
|
||||||
},
|
},
|
||||||
Content: () =>
|
Content: () =>
|
||||||
renderArticleLayout({
|
renderArticleLayout({
|
||||||
asset,
|
|
||||||
article,
|
article,
|
||||||
date,
|
date,
|
||||||
body: vfile.toString(),
|
body: vfile.toString(),
|
||||||
|
@ -133,14 +120,13 @@ function renderArticle() {
|
||||||
try {
|
try {
|
||||||
allArticles.sort(([, a], [, b]) => b.diff(a).toMillis());
|
allArticles.sort(([, a], [, b]) => b.diff(a).toMillis());
|
||||||
const contents = renderLayout({
|
const contents = renderLayout({
|
||||||
asset,
|
|
||||||
meta: {
|
meta: {
|
||||||
description: SITE_DESCRIPTION,
|
description: SITE_DESCRIPTION,
|
||||||
"og:title": SITE_TITLE,
|
"og:title": SITE_TITLE,
|
||||||
"og:type": "website",
|
"og:type": "website",
|
||||||
"og:url": `https://adaedra.eu`,
|
"og:url": `https://adaedra.eu`,
|
||||||
},
|
},
|
||||||
Content: () => renderIndex({ articles: allArticles, asset }),
|
Content: () => renderIndex({ articles: allArticles }),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.push(
|
this.push(
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { Document } from "asciidoctor";
|
import { Document } from "asciidoctor";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
import { asset } from "../lib/assets.js";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
article: Document;
|
article: Document;
|
||||||
date: DateTime;
|
date: DateTime;
|
||||||
body: string;
|
body: string;
|
||||||
asset(path: string): string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ({ asset, article, date, body }: Props) => (
|
export default ({ article, date, body }: Props) => (
|
||||||
<>
|
<>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/" title="Back to home page">
|
<a href="/" title="Back to home page">
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import { Document } from "asciidoctor";
|
import { Document } from "asciidoctor";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
import { asset } from "../lib/assets.js";
|
||||||
|
|
||||||
type HeaderProps = {
|
const Header = () => (
|
||||||
asset(path: string): string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Header = ({ asset }: HeaderProps) => (
|
|
||||||
<header class="index-header">
|
<header class="index-header">
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<h1>Adædra</h1>
|
<h1>Adædra</h1>
|
||||||
|
@ -19,11 +16,11 @@ const Header = ({ asset }: HeaderProps) => (
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|
||||||
type ArticleProps = {
|
type Props = {
|
||||||
articles: [string, DateTime, Document][];
|
articles: [string, DateTime, Document][];
|
||||||
};
|
};
|
||||||
|
|
||||||
const Articles = ({ articles }: ArticleProps) => (
|
const Articles = ({ articles }: Props) => (
|
||||||
<>
|
<>
|
||||||
<h2>Latest articles</h2>
|
<h2>Latest articles</h2>
|
||||||
<ul class="index-list">
|
<ul class="index-list">
|
||||||
|
@ -42,8 +39,6 @@ const Articles = ({ articles }: ArticleProps) => (
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
type Props = HeaderProps & ArticleProps;
|
|
||||||
|
|
||||||
const SOCIALS: [string, string, string][] = [
|
const SOCIALS: [string, string, string][] = [
|
||||||
["Gitea", "https://gitea.adaedra.eu", "git"],
|
["Gitea", "https://gitea.adaedra.eu", "git"],
|
||||||
["Mastodon", "https://nerdculture.de/@adaedra", "mastodon"],
|
["Mastodon", "https://nerdculture.de/@adaedra", "mastodon"],
|
||||||
|
@ -74,10 +69,10 @@ const Socials = () => (
|
||||||
|
|
||||||
const Footer = () => <footer class="index-footer"></footer>;
|
const Footer = () => <footer class="index-footer"></footer>;
|
||||||
|
|
||||||
export default ({ asset, articles }: Props) => (
|
export default (props: Props) => (
|
||||||
<>
|
<>
|
||||||
<Header asset={asset} />
|
<Header />
|
||||||
<Articles articles={articles} />
|
<Articles {...props} />
|
||||||
<Socials />
|
<Socials />
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { SITE_DEFAULT_META, SITE_TITLE } from "./constants.js";
|
import { SITE_DEFAULT_META, SITE_TITLE } from "./constants.js";
|
||||||
import { JSX } from "preact/jsx-runtime";
|
import { JSX } from "preact/jsx-runtime";
|
||||||
|
import { asset } from "../lib/assets.js";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title?: string;
|
title?: string;
|
||||||
meta?: { [name: string]: string };
|
meta?: { [name: string]: string };
|
||||||
asset(path: string): string;
|
|
||||||
} & ({ content: string } | { Content(): JSX.Element });
|
} & ({ content: string } | { Content(): JSX.Element });
|
||||||
|
|
||||||
export default (props: Props) => {
|
export default (props: Props) => {
|
||||||
|
@ -22,7 +22,7 @@ export default (props: Props) => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{metaTags}
|
{metaTags}
|
||||||
<link rel="stylesheet" type="text/css" href={props.asset("index.css")} />
|
<link rel="stylesheet" type="text/css" href={asset("index.css")} />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{"Content" in props ? (
|
{"Content" in props ? (
|
||||||
|
|
Loading…
Reference in New Issue
Block a user