From feb5a5c74db2b8c1c011a41724d69b8e8f58ecc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=B5=A6=20=E7=9F=A5=E4=B9=9F=20Matsuura=20Tomoy?= =?UTF-8?q?a?= Date: Wed, 29 Oct 2025 20:56:55 -0400 Subject: [PATCH 1/4] made ogimage generation concurrent --- quartz/plugins/emitters/ogImage.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/quartz/plugins/emitters/ogImage.tsx b/quartz/plugins/emitters/ogImage.tsx index 813d9348c..af2aa5d44 100644 --- a/quartz/plugins/emitters/ogImage.tsx +++ b/quartz/plugins/emitters/ogImage.tsx @@ -114,11 +114,13 @@ export const CustomOgImages: QuartzEmitterPlugin> = const headerFont = cfg.theme.typography.header const bodyFont = cfg.theme.typography.body const fonts = await getSatoriFonts(headerFont, bodyFont) - - for (const [_tree, vfile] of content) { - if (vfile.data.frontmatter?.socialImage !== undefined) continue - yield processOgImage(ctx, vfile.data, fonts, fullOptions) - } + Promise.all( + content.map(([_tree, vfile]) => { + if (vfile.data.frontmatter?.socialImage !== undefined) { + processOgImage(ctx, vfile.data, fonts, fullOptions) + } + }), + ) }, async *partialEmit(ctx, _content, _resources, changeEvents) { const cfg = ctx.cfg.configuration From 2a64a3103d8f6317fc99214fa9b6beeeca9c289d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=B5=A6=20=E7=9F=A5=E4=B9=9F=20Matsuura=20Tomoy?= =?UTF-8?q?a?= Date: Sat, 1 Nov 2025 12:25:58 -0400 Subject: [PATCH 2/4] ogimage plugin returns array of promises instead async iterator --- quartz/plugins/emitters/ogImage.tsx | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/quartz/plugins/emitters/ogImage.tsx b/quartz/plugins/emitters/ogImage.tsx index af2aa5d44..48ca16972 100644 --- a/quartz/plugins/emitters/ogImage.tsx +++ b/quartz/plugins/emitters/ogImage.tsx @@ -1,7 +1,14 @@ import { QuartzEmitterPlugin } from "../types" import { i18n } from "../../i18n" import { unescapeHTML } from "../../util/escape" -import { FullSlug, getFileExtension, isAbsoluteURL, joinSegments, QUARTZ } from "../../util/path" +import { + FilePath, + FullSlug, + getFileExtension, + isAbsoluteURL, + joinSegments, + QUARTZ, +} from "../../util/path" import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og" import sharp from "sharp" import satori, { SatoriOptions } from "satori" @@ -109,17 +116,19 @@ export const CustomOgImages: QuartzEmitterPlugin> = getQuartzComponents() { return [] }, - async *emit(ctx, content, _resources) { + async emit(ctx, content, _resources): Promise { const cfg = ctx.cfg.configuration const headerFont = cfg.theme.typography.header const bodyFont = cfg.theme.typography.body const fonts = await getSatoriFonts(headerFont, bodyFont) - Promise.all( - content.map(([_tree, vfile]) => { - if (vfile.data.frontmatter?.socialImage !== undefined) { - processOgImage(ctx, vfile.data, fonts, fullOptions) - } - }), + return Promise.all( + content + .filter( + ([_tree, vfile]) => + vfile.data.frontmatter?.socialImage !== undefined && + vfile.data.filePath !== undefined, + ) + .map(([_tree, vfile]) => processOgImage(ctx, vfile.data, fonts, fullOptions)), ) }, async *partialEmit(ctx, _content, _resources, changeEvents) { From ef25f903cac0e661f0f1d8d004953342f4fd09f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=B5=A6=20=E7=9F=A5=E4=B9=9F=20Matsuura=20Tomoy?= =?UTF-8?q?a?= Date: Sat, 1 Nov 2025 15:21:33 -0400 Subject: [PATCH 3/4] fixed wrong logics --- quartz/plugins/emitters/ogImage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/plugins/emitters/ogImage.tsx b/quartz/plugins/emitters/ogImage.tsx index 48ca16972..dfb3da22e 100644 --- a/quartz/plugins/emitters/ogImage.tsx +++ b/quartz/plugins/emitters/ogImage.tsx @@ -125,7 +125,7 @@ export const CustomOgImages: QuartzEmitterPlugin> = content .filter( ([_tree, vfile]) => - vfile.data.frontmatter?.socialImage !== undefined && + vfile.data.frontmatter?.socialImage === undefined && vfile.data.filePath !== undefined, ) .map(([_tree, vfile]) => processOgImage(ctx, vfile.data, fonts, fullOptions)), From 59b836bd7954eabf7f5a93adc20848e9f46354d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=B5=A6=20=E7=9F=A5=E4=B9=9F=20Matsuura=20Tomoy?= =?UTF-8?q?a?= Date: Wed, 5 Nov 2025 11:36:11 -0500 Subject: [PATCH 4/4] made entire array emitter process async --- quartz/build.ts | 52 +++++++++++++++++++-------------------- quartz/processors/emit.ts | 31 +++++++++++++---------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/quartz/build.ts b/quartz/build.ts index f3adfe250..d16fce8bf 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -261,35 +261,35 @@ async function rebuild(changes: ChangeEvent[], clientRefresh: () => void, buildD .map((file) => file.content), ) - let emittedFiles = 0 - for (const emitter of cfg.plugins.emitters) { - // Try to use partialEmit if available, otherwise assume the output is static - const emitFn = emitter.partialEmit ?? emitter.emit - const emitted = await emitFn(ctx, processedFiles, staticResources, changeEvents) - if (emitted === null) { - continue - } - - if (Symbol.asyncIterator in emitted) { - // Async generator case - for await (const file of emitted) { - emittedFiles++ - if (ctx.argv.verbose) { - console.log(`[emit:${emitter.name}] ${file}`) - } + const emittedFiles = await Promise.all( + cfg.plugins.emitters.map(async (emitter) => { + const emitFn = emitter.partialEmit ?? emitter.emit + const emitted = emitFn(ctx, processedFiles, staticResources, changeEvents) + if (emitted === null) { + return 0 } - } else { - // Array case - emittedFiles += emitted.length - if (ctx.argv.verbose) { - for (const file of emitted) { - console.log(`[emit:${emitter.name}] ${file}`) + if (Symbol.asyncIterator in emitted) { + let emittedFiles = 0 + // Async generator case + for await (const file of emitted) { + emittedFiles++ + if (ctx.argv.verbose) { + console.log(`[emit:${emitter.name}] ${file}`) + } } + return emittedFiles + } else { + // Array case + return await Promise.all( + (await emitted).map((file) => { + console.log(`[emit:${emitter.name}] ${file}`) + }), + ).then((files) => files.length) } - } - } - - console.log(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`) + }), + ) + const sumFiles = emittedFiles.reduce((a, b) => a + b) + console.log(`Emitted ${sumFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`) console.log(styleText("green", `Done rebuilding in ${perf.timeSince()}`)) changes.splice(0, numChangesInBuild) clientRefresh() diff --git a/quartz/processors/emit.ts b/quartz/processors/emit.ts index 78702959c..ad6748530 100644 --- a/quartz/processors/emit.ts +++ b/quartz/processors/emit.ts @@ -13,14 +13,14 @@ export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) { log.start(`Emitting files`) - let emittedFiles = 0 const staticResources = getStaticResourcesFromPlugins(ctx) - await Promise.all( + const emittedFiles = await Promise.all( cfg.plugins.emitters.map(async (emitter) => { try { - const emitted = await emitter.emit(ctx, content, staticResources) + const emitted = emitter.emit(ctx, content, staticResources) if (Symbol.asyncIterator in emitted) { // Async generator case + let emittedFiles = 0 for await (const file of emitted) { emittedFiles++ if (ctx.argv.verbose) { @@ -29,22 +29,27 @@ export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) { log.updateText(`${emitter.name} -> ${styleText("gray", file)}`) } } + return emittedFiles } else { // Array case - emittedFiles += emitted.length - for (const file of emitted) { - if (ctx.argv.verbose) { - console.log(`[emit:${emitter.name}] ${file}`) - } else { - log.updateText(`${emitter.name} -> ${styleText("gray", file)}`) - } - } + return ( + await Promise.all( + (await emitted).map((file) => { + if (ctx.argv.verbose) { + console.log(`[emit:${emitter.name}] ${file}`) + } else { + log.updateText(`${emitter.name} -> ${styleText("gray", file)}`) + } + }), + ) + ).length } } catch (err) { trace(`Failed to emit from plugin \`${emitter.name}\``, err as Error) + return 0 } }), ) - - log.end(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince()}`) + const sumFiles = emittedFiles.reduce((a, b) => a + b) + log.end(`Emitted ${sumFiles} files to \`${argv.output}\` in ${perf.timeSince()}`) }