@@ -7,7 +7,7 @@ const { readFile, writeFile, unlink, stat } = require('fs/promises');
7
7
const PROJECT_ROOT = process . cwd ( ) ;
8
8
const TMP_DIR = join ( PROJECT_ROOT , 'tmp' ) ;
9
9
const TEMPLATE_PATH = join ( PROJECT_ROOT , 'assets' , 'og-template' , 'template.html' ) ;
10
- const EVENT_TEMPLATE_PATH = join ( PROJECT_ROOT , 'assets' , 'og-template' , 'event-template.html' ) ; // New
10
+ const EVENT_TEMPLATE_PATH = join ( PROJECT_ROOT , 'assets' , 'og-template' , 'event-template.html' ) ;
11
11
const INPUT_JSON_PATH = join ( TMP_DIR , 'ogImageData.json' ) ;
12
12
const CACHE_MANIFEST_PATH = join ( TMP_DIR , 'og-cache-manifest.json' ) ;
13
13
const JPEG_QUALITY = 90 ;
@@ -17,6 +17,7 @@ async function pathExists(path) {
17
17
}
18
18
19
19
async function generateImages ( ) {
20
+ const startTime = Date . now ( ) ;
20
21
console . log ( '🖼️ Starting OG Image Generation with Caching...' ) ;
21
22
22
23
// --- Pre-flight checks ---
@@ -31,26 +32,27 @@ async function generateImages() {
31
32
const defaultTemplateContent = await readFile ( TEMPLATE_PATH , 'utf8' ) ;
32
33
const eventTemplateContent = await readFile ( EVENT_TEMPLATE_PATH , 'utf8' ) ;
33
34
const jsonData = JSON . parse ( await readFile ( INPUT_JSON_PATH , 'utf8' ) ) ;
34
-
35
+
35
36
let oldCache = { } ;
36
37
try {
37
38
oldCache = JSON . parse ( await readFile ( CACHE_MANIFEST_PATH , 'utf8' ) ) ;
38
39
} catch ( e ) {
39
40
console . log ( 'ℹ️ No existing cache manifest found. Will generate all images.' ) ;
40
41
}
41
-
42
+
42
43
const newCache = { } ;
43
44
44
45
if ( ! jsonData || ! jsonData . pages || jsonData . pages . length === 0 ) {
45
46
console . warn ( '⚠️ No pages to process.' ) ;
46
47
return ;
47
48
}
48
49
49
- // --- Launch Puppeteer ---
50
+ // --- Launch Puppeteer and create ONE reusable page ---
50
51
const browser = await puppeteer . launch ( {
51
52
headless : true ,
52
53
args : [ '--no-sandbox' , '--disable-setuid-sandbox' ] ,
53
54
} ) ;
55
+ const page = await browser . newPage ( ) ;
54
56
55
57
let successCount = 0 ;
56
58
let errorCount = 0 ;
@@ -78,37 +80,44 @@ async function generateImages() {
78
80
newCache [ outputPath ] = finalHash ; // Keep valid entry
79
81
continue ;
80
82
}
81
-
82
- const page = await browser . newPage ( ) ;
83
+
84
+ // Reuse the single page
83
85
await page . setViewport ( { width, height } ) ;
84
86
85
87
let htmlContent ;
86
88
if ( template === 'event' ) {
87
- const speakersHtml = ( speakers || [ ] )
88
- . map ( speaker => `
89
- <div class="speaker-item">
90
- ${ speaker . imageUri ? `<img src="${ speaker . imageUri } " class="speaker-img" alt="Photo of ${ speaker . name } " />` : '' }
89
+ const speakersHtml = ( speakers || [ ] )
90
+ . map ( speaker => `
91
+ <div class="speaker-item" style="width: ${
92
+ speaker . count === 1 ? '42vmin' :
93
+ speaker . count === 2 ? '32vmin' :
94
+ speaker . count === 3 ? '27vmin' :
95
+ speaker . count >= 4 && speaker . count <= 6 ? '24vmin' :
96
+ '20vmin'
97
+ } ;">
98
+ ${ speaker . imageUri ? `<img src="${ speaker . imageUri } " class="speaker-img" alt="Photo of ${ speaker . name } " style="width: 100%; height: 100%;" />` : '' }
91
99
<div class="speaker-name">${ speaker . name } </div>
92
100
</div>
93
101
` )
94
- . join ( '' ) ;
95
-
96
- htmlContent = eventTemplateContent
97
- . replace ( 'LOGO_SRC' , jsonData . logoDataUri )
98
- . replace ( 'BACKGROUND_URL' , jsonData . backgroundDataUri || '' )
99
- . replace ( 'PAGE_TITLE' , title )
100
- . replace ( '<!-- SPEAKER_IMAGES_HTML will be injected here -->' , speakersHtml )
101
- . replace ( 'EVENT_DATE' , eventDate || '' )
102
- . replace ( 'EVENT_TIME' , eventTime || '' ) ;
102
+ . join ( '' ) ;
103
+
104
+ htmlContent = eventTemplateContent
105
+ . replace ( 'LOGO_SRC' , jsonData . logoDataUri )
106
+ . replace ( 'BACKGROUND_URL' , jsonData . backgroundDataUri || '' )
107
+ . replace ( 'PAGE_TITLE' , title )
108
+ . replace ( '<!-- SPEAKER_IMAGES_HTML will be injected here -->' , speakersHtml )
109
+ . replace ( 'EVENT_DATE' , eventDate || '' )
110
+ . replace ( 'EVENT_TIME' , eventTime || '' ) ;
103
111
} else {
104
- htmlContent = defaultTemplateContent
105
- . replace ( 'LOGO_SRC' , jsonData . logoDataUri )
106
- . replace ( 'BACKGROUND_URL' , jsonData . backgroundDataUri || '' )
107
- . replace ( 'PAGE_TITLE' , title )
108
- . replace ( 'PAGE_DESCRIPTION' , description ) ;
112
+ htmlContent = defaultTemplateContent
113
+ . replace ( 'LOGO_SRC' , jsonData . logoDataUri )
114
+ . replace ( 'BACKGROUND_URL' , jsonData . backgroundDataUri || '' )
115
+ . replace ( 'PAGE_TITLE' , title )
116
+ . replace ( 'PAGE_DESCRIPTION' , description ) ;
109
117
}
110
118
111
- await page . setContent ( htmlContent , { waitUntil : 'networkidle0' } ) ;
119
+ // Use 'domcontentloaded' for faster processing
120
+ await page . setContent ( htmlContent , { waitUntil : 'domcontentloaded' } ) ;
112
121
113
122
await page . screenshot ( {
114
123
path : outputPath ,
@@ -120,16 +129,15 @@ async function generateImages() {
120
129
console . log ( `✅ Generated: ${ outputPath . replace ( PROJECT_ROOT , '' ) } (${ width } x${ height } , ${ ( stats . size / 1024 ) . toFixed ( 1 ) } KB)` ) ;
121
130
successCount ++ ;
122
131
newCache [ outputPath ] = finalHash ; // Add new entry to cache
123
-
124
- await page . close ( ) ;
132
+
125
133
} catch ( err ) {
126
134
console . error ( `❌ Failed generating image for: ${ outputPath . replace ( PROJECT_ROOT , '' ) } ` ) ;
127
135
console . error ( err . message || err ) ;
128
136
errorCount ++ ;
129
137
}
130
138
}
131
139
}
132
-
140
+
133
141
await browser . close ( ) ;
134
142
135
143
// --- Cleanup stale images and cache entries ---
@@ -139,7 +147,7 @@ async function generateImages() {
139
147
p . outputs . forEach ( o => validOutputPaths . add ( o . path ) ) ;
140
148
}
141
149
} ) ;
142
-
150
+
143
151
let cleanedCount = 0 ;
144
152
for ( const oldPath in oldCache ) {
145
153
if ( ! validOutputPaths . has ( oldPath ) ) {
@@ -150,17 +158,18 @@ async function generateImages() {
150
158
}
151
159
}
152
160
if ( cleanedCount > 0 ) {
153
- console . log ( `🗑️ Cleaned up ${ cleanedCount } stale OG image(s).` ) ;
161
+ console . log ( `🗑️ Cleaned up ${ cleanedCount } stale OG image(s).` ) ;
154
162
}
155
163
156
164
// --- Write the new cache manifest ---
157
165
await writeFile ( CACHE_MANIFEST_PATH , JSON . stringify ( newCache , null , 2 ) ) ;
158
166
159
- console . log ( `\n✨ Generation complete! Succeeded: ${ successCount } , Skipped: ${ skippedCount } , Failed: ${ errorCount } .` ) ;
167
+ const duration = ( ( Date . now ( ) - startTime ) / 1000 ) . toFixed ( 2 ) ;
168
+ console . log ( `\n✨ Generation complete in ${ duration } s! Succeeded: ${ successCount } , Skipped: ${ skippedCount } , Failed: ${ errorCount } .` ) ;
160
169
if ( errorCount > 0 ) process . exit ( 1 ) ;
161
170
}
162
171
163
172
generateImages ( ) . catch ( err => {
164
173
console . error ( "🔥 Uncaught error during image generation:" , err ) ;
165
174
process . exit ( 1 ) ;
166
- } ) ;
175
+ } ) ;
0 commit comments