11import { Resvg } from '@resvg/resvg-js' ;
22import sharp from 'sharp' ;
3- import { writeFile , mkdir } from 'node:fs/promises' ;
3+ import { writeFile , mkdir , copyFile , stat } from 'node:fs/promises' ;
44import { existsSync , readFileSync , readdirSync } from 'node:fs' ;
5+ import { createHash } from 'node:crypto' ;
56import { dirname , join , extname } from 'node:path' ;
67import { buildRouteMetaMap , routeToOgPath , DIST , PUBLIC } from './lib/route-meta.mjs' ;
78
89const WEBP_QUALITY = 80 ;
910
11+ // Bump when the rendering algorithm changes (layout, gradient logic, font, etc.)
12+ // — invalidates every cached WebP.
13+ const CACHE_VERSION = 'v1' ;
14+ const CACHE_DIR = join ( 'node_modules' , '.cache' , 'og-images' ) ;
15+
1016const W = 1200 ;
1117const H = 630 ;
1218const STRIP_H = 200 ;
@@ -17,6 +23,10 @@ const ART_DIR = join(PUBLIC, 'images', 'og-art');
1723const MIME = { '.jpg' : 'image/jpeg' , '.jpeg' : 'image/jpeg' , '.png' : 'image/png' , '.gif' : 'image/gif' } ;
1824const UNSUPPORTED_EXT = new Set ( [ '.webp' , '.avif' , '.heic' , '.heif' , '.tiff' , '.tif' ] ) ;
1925
26+ function sha256Hex ( input ) {
27+ return createHash ( 'sha256' ) . update ( input ) . digest ( 'hex' ) ;
28+ }
29+
2030function loadArt ( ) {
2131 if ( ! existsSync ( ART_DIR ) ) return [ ] ;
2232 const out = [ ] ;
@@ -29,7 +39,11 @@ function loadArt() {
2939 }
3040 if ( ! MIME [ ext ] ) continue ;
3141 const buf = readFileSync ( join ( ART_DIR , f ) ) ;
32- out . push ( { name : f , dataUri : `data:${ MIME [ ext ] } ;base64,${ buf . toString ( 'base64' ) } ` } ) ;
42+ out . push ( {
43+ name : f ,
44+ dataUri : `data:${ MIME [ ext ] } ;base64,${ buf . toString ( 'base64' ) } ` ,
45+ hash : sha256Hex ( buf ) ,
46+ } ) ;
3347 }
3448 if ( skipped . length ) {
3549 console . warn (
@@ -39,6 +53,18 @@ function loadArt() {
3953 return out ;
4054}
4155
56+ function cacheKeyFor ( { title, slug, art } ) {
57+ return sha256Hex (
58+ JSON . stringify ( {
59+ v : CACHE_VERSION ,
60+ title,
61+ slug,
62+ artName : art ?. name ?? null ,
63+ artHash : art ?. hash ?? null ,
64+ } )
65+ ) ;
66+ }
67+
4268function hashSlug ( s ) {
4369 let h = 2166136261 >>> 0 ;
4470 for ( let i = 0 ; i < s . length ; i ++ ) {
@@ -191,22 +217,39 @@ async function main() {
191217 }
192218 console . log ( `generate-og-images: ${ targets . length } route(s) need a fallback image` ) ;
193219
194- let written = 0 ;
220+ if ( ! existsSync ( CACHE_DIR ) ) await mkdir ( CACHE_DIR , { recursive : true } ) ;
221+
222+ let rendered = 0 ;
223+ let cached = 0 ;
195224 let totalBytes = 0 ;
196225 for ( const { route, meta } of targets ) {
197226 const slug = route . split ( '/' ) . filter ( Boolean ) . pop ( ) || 'fezcodex' ;
198- const svg = buildSvg ( { title : meta . title , slug, art : pickArt ( art , slug ) } ) ;
199- const webp = await renderWebp ( svg ) ;
227+ const chosenArt = pickArt ( art , slug ) ;
228+ const key = cacheKeyFor ( { title : meta . title , slug, art : chosenArt } ) ;
229+ const cachePath = join ( CACHE_DIR , `${ key } .webp` ) ;
200230 const outAbs = join ( DIST , routeToOgPath ( route ) ) ;
201231 if ( ! existsSync ( dirname ( outAbs ) ) ) await mkdir ( dirname ( outAbs ) , { recursive : true } ) ;
232+
233+ if ( existsSync ( cachePath ) ) {
234+ await copyFile ( cachePath , outAbs ) ;
235+ const st = await stat ( cachePath ) ;
236+ cached ++ ;
237+ totalBytes += st . size ;
238+ continue ;
239+ }
240+
241+ const svg = buildSvg ( { title : meta . title , slug, art : chosenArt } ) ;
242+ const webp = await renderWebp ( svg ) ;
202243 await writeFile ( outAbs , webp ) ;
203- written ++ ;
244+ await writeFile ( cachePath , webp ) ;
245+ rendered ++ ;
204246 totalBytes += webp . length ;
205247 }
206248
249+ const written = rendered + cached ;
207250 const avgKb = written ? Math . round ( totalBytes / written / 1024 ) : 0 ;
208251 console . log (
209- `generate-og-images: wrote ${ written } WebP(s), avg ${ avgKb } KB (${ Date . now ( ) - t0 } ms)`
252+ `generate-og-images: wrote ${ written } WebP(s) ( ${ rendered } rendered, ${ cached } cached) , avg ${ avgKb } KB (${ Date . now ( ) - t0 } ms)`
210253 ) ;
211254}
212255
0 commit comments