11import { readFileSync } from "node:fs" ;
22import { resolve } from "node:path" ;
3- import { renderCard , launchBrowser } from "./renderer.js" ;
3+ import type { Browser , Page } from "puppeteer-core" ;
4+ import { renderCardOnPage , launchBrowser } from "./renderer.js" ;
45import type { RenderMeta } from "./template-engine.js" ;
56import type { DeckContent , Theme , SizeName , CardContent } from "../cli/utils/validator.js" ;
67import { ThemeSchema } from "../cli/utils/validator.js" ;
@@ -22,6 +23,46 @@ function loadTheme(name: string): Theme {
2223 return ThemeSchema . parse ( JSON . parse ( raw ) ) ;
2324}
2425
26+ interface PagePool {
27+ acquire ( ) : Promise < Page > ;
28+ release ( page : Page ) : void ;
29+ drain ( ) : Promise < void > ;
30+ }
31+
32+ function createPagePool ( browser : Browser , size : number ) : PagePool {
33+ const idle : Page [ ] = [ ] ;
34+ const allPages : Page [ ] = [ ] ;
35+ const waiters : ( ( page : Page ) => void ) [ ] = [ ] ;
36+ let created = 0 ;
37+
38+ async function acquire ( ) : Promise < Page > {
39+ const reused = idle . pop ( ) ;
40+ if ( reused ) return reused ;
41+ if ( created < size ) {
42+ created ++ ;
43+ const page = await browser . newPage ( ) ;
44+ allPages . push ( page ) ;
45+ return page ;
46+ }
47+ return new Promise < Page > ( ( resolve ) => waiters . push ( resolve ) ) ;
48+ }
49+
50+ function release ( page : Page ) : void {
51+ const waiter = waiters . shift ( ) ;
52+ if ( waiter ) {
53+ waiter ( page ) ;
54+ return ;
55+ }
56+ idle . push ( page ) ;
57+ }
58+
59+ async function drain ( ) : Promise < void > {
60+ await Promise . all ( allPages . map ( ( p ) => p . close ( ) . catch ( ( ) => { } ) ) ) ;
61+ }
62+
63+ return { acquire, release, drain } ;
64+ }
65+
2566async function semaphore < T > (
2667 tasks : ( ( ) => Promise < T > ) [ ] ,
2768 limit : number ,
@@ -66,6 +107,8 @@ export async function renderDeck(
66107 : deck . slides . map ( ( slide , i ) => ( { slide, originalIndex : i } ) ) ;
67108
68109 const browser = await launchBrowser ( ) ;
110+ const poolSize = Math . min ( concurrency , slidesToRender . length ) ;
111+ const pool = createPagePool ( browser , poolSize ) ;
69112 try {
70113 const tasks = slidesToRender . map ( ( { slide, originalIndex } ) => async ( ) => {
71114 if ( ! slide ) throw new Error ( `Slide index ${ originalIndex } out of range` ) ;
@@ -98,7 +141,13 @@ export async function renderDeck(
98141 counter,
99142 } ;
100143
101- const buffer = await renderCard ( cardContent , theme , sizeName , scale , meta , browser ) ;
144+ const page = await pool . acquire ( ) ;
145+ let buffer : Buffer ;
146+ try {
147+ buffer = await renderCardOnPage ( page , cardContent , theme , sizeName , scale , meta ) ;
148+ } finally {
149+ pool . release ( page ) ;
150+ }
102151 const paddedIndex = String ( originalIndex + 1 ) . padStart ( padWidth , "0" ) ;
103152 const name = `${ deckName } -${ paddedIndex } .png` ;
104153
@@ -112,6 +161,7 @@ export async function renderDeck(
112161 names : results . map ( ( r ) => r . name ) ,
113162 } ;
114163 } finally {
164+ await pool . drain ( ) ;
115165 await browser . close ( ) ;
116166 }
117167}
0 commit comments