fix: parse parallelization chunk arg, inline b64 for og image

This commit is contained in:
Jacky Zhao 2025-03-16 15:12:40 -07:00
parent a737207981
commit e86544064c
4 changed files with 49 additions and 35 deletions

View file

@ -1,7 +1,7 @@
import { QuartzEmitterPlugin } from "../types" import { QuartzEmitterPlugin } from "../types"
import { i18n } from "../../i18n" import { i18n } from "../../i18n"
import { unescapeHTML } from "../../util/escape" import { unescapeHTML } from "../../util/escape"
import { FullSlug, getFileExtension } from "../../util/path" import { FullSlug, getFileExtension, joinSegments, QUARTZ } from "../../util/path"
import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og" import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og"
import sharp from "sharp" import sharp from "sharp"
import satori, { SatoriOptions } from "satori" import satori, { SatoriOptions } from "satori"
@ -10,6 +10,8 @@ import { Readable } from "stream"
import { write } from "./helpers" import { write } from "./helpers"
import { BuildCtx } from "../../util/ctx" import { BuildCtx } from "../../util/ctx"
import { QuartzPluginData } from "../vfile" import { QuartzPluginData } from "../vfile"
import fs from "node:fs/promises"
import chalk from "chalk"
const defaultOptions: SocialImageOptions = { const defaultOptions: SocialImageOptions = {
colorScheme: "lightMode", colorScheme: "lightMode",
@ -28,7 +30,25 @@ async function generateSocialImage(
userOpts: SocialImageOptions, userOpts: SocialImageOptions,
): Promise<Readable> { ): Promise<Readable> {
const { width, height } = userOpts const { width, height } = userOpts
const imageComponent = userOpts.imageStructure(cfg, userOpts, title, description, fonts, fileData) const iconPath = joinSegments(QUARTZ, "static", "icon.png")
let iconBase64: string | undefined = undefined
try {
const iconData = await fs.readFile(iconPath)
iconBase64 = `data:image/png;base64,${iconData.toString("base64")}`
} catch (err) {
console.warn(chalk.yellow(`Warning: Could not find icon at ${iconPath}`))
}
const imageComponent = userOpts.imageStructure({
cfg,
userOpts,
title,
description,
fonts,
fileData,
iconBase64,
})
const svg = await satori(imageComponent, { const svg = await satori(imageComponent, {
width, width,
height, height,

View file

@ -172,7 +172,7 @@ export async function parseMarkdown(ctx: BuildCtx, fps: FilePath[]): Promise<Pro
workerType: "thread", workerType: "thread",
}) })
const errorHandler = (err: any) => { const errorHandler = (err: any) => {
console.error(`${err}`.replace(/^error:\s*/i, "")) console.error(err)
process.exit(1) process.exit(1)
} }
@ -201,7 +201,7 @@ export async function parseMarkdown(ctx: BuildCtx, fps: FilePath[]): Promise<Pro
const markdownToHtmlPromises: WorkerPromise<ProcessedContent[]>[] = [] const markdownToHtmlPromises: WorkerPromise<ProcessedContent[]>[] = []
processedFiles = 0 processedFiles = 0
for (const [mdChunk, _] of mdResults) { for (const mdChunk of mdResults) {
markdownToHtmlPromises.push(pool.exec("processHtml", [serializableCtx, mdChunk])) markdownToHtmlPromises.push(pool.exec("processHtml", [serializableCtx, mdChunk]))
} }
const results: ProcessedContent[][] = await Promise.all( const results: ProcessedContent[][] = await Promise.all(

View file

@ -35,7 +35,7 @@ export class QuartzLogger {
const truncated = truncate(output, columns) const truncated = truncate(output, columns)
process.stdout.write(truncated) process.stdout.write(truncated)
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerChars.length this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerChars.length
}, 20) }, 50)
} }
} }

View file

@ -13,6 +13,7 @@ import chalk from "chalk"
const defaultHeaderWeight = [700] const defaultHeaderWeight = [700]
const defaultBodyWeight = [400] const defaultBodyWeight = [400]
export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: FontSpecification) { export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: FontSpecification) {
// Get all weights for header and body fonts // Get all weights for header and body fonts
const headerWeights: FontWeight[] = ( const headerWeights: FontWeight[] = (
@ -134,21 +135,12 @@ export type SocialImageOptions = {
excludeRoot: boolean excludeRoot: boolean
/** /**
* JSX to use for generating image. See satori docs for more info (https://github.com/vercel/satori) * JSX to use for generating image. See satori docs for more info (https://github.com/vercel/satori)
* @param cfg global quartz config
* @param userOpts options that can be set by user
* @param title title of current page
* @param description description of current page
* @param fonts global font that can be used for styling
* @param fileData full fileData of current page
* @returns prepared jsx to be used for generating image
*/ */
imageStructure: ( imageStructure: (
cfg: GlobalConfiguration, options: ImageOptions & {
userOpts: UserOpts, userOpts: UserOpts
title: string, iconBase64?: string
description: string, },
fonts: SatoriOptions["fonts"],
fileData: QuartzPluginData,
) => JSXInternal.Element ) => JSXInternal.Element
} }
@ -178,17 +170,17 @@ export type ImageOptions = {
} }
// This is the default template for generated social image. // This is the default template for generated social image.
export const defaultImage: SocialImageOptions["imageStructure"] = ( export const defaultImage: SocialImageOptions["imageStructure"] = ({
cfg: GlobalConfiguration, cfg,
{ colorScheme }: UserOpts, userOpts,
title: string, title,
description: string, description,
_fonts: SatoriOptions["fonts"], fileData,
fileData: QuartzPluginData, iconBase64,
) => { }) => {
const { colorScheme } = userOpts
const fontBreakPoint = 32 const fontBreakPoint = 32
const useSmallerFont = title.length > fontBreakPoint const useSmallerFont = title.length > fontBreakPoint
const iconPath = `https://${cfg.baseUrl}/static/icon.png`
// Format date if available // Format date if available
const rawDate = getDate(cfg, fileData) const rawDate = getDate(cfg, fileData)
@ -226,14 +218,16 @@ export const defaultImage: SocialImageOptions["imageStructure"] = (
marginBottom: "0.5rem", marginBottom: "0.5rem",
}} }}
> >
{iconBase64 && (
<img <img
src={iconPath} src={iconBase64}
width={56} width={56}
height={56} height={56}
style={{ style={{
borderRadius: "50%", borderRadius: "50%",
}} }}
/> />
)}
<div <div
style={{ style={{
display: "flex", display: "flex",