diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md index fcc88a7b..65209a2c 100644 --- a/docs/advanced/making plugins.md +++ b/docs/advanced/making plugins.md @@ -216,22 +216,19 @@ export type QuartzEmitterPlugin = ( export type QuartzEmitterPluginInstance = { name: string - emit( - ctx: BuildCtx, - content: ProcessedContent[], - resources: StaticResources, - emitCallback: EmitCallback, - ): Promise + emit(ctx: BuildCtx, content: ProcessedContent[], resources: StaticResources): Promise getQuartzComponents(ctx: BuildCtx): QuartzComponent[] } ``` -An emitter plugin must define a `name` field an `emit` function and a `getQuartzComponents` function. `emit` is responsible for looking at all the parsed and filtered content and then appropriately creating files and returning a list of paths to files the plugin created. +An emitter plugin must define a `name` field, an `emit` function, and a `getQuartzComponents` function. `emit` is responsible for looking at all the parsed and filtered content and then appropriately creating files and returning a list of paths to files the plugin created. -Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `emitCallback` if you are creating files that contain text. The `emitCallback` function is the 4th argument of the emit function. Its interface looks something like this: +Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `write` function in `quartz/plugins/emitters/helpers.ts` if you are creating files that contain text. `write` has the following signature: ```ts -export type EmitCallback = (data: { +export type WriteOptions = (data: { + // the build context + ctx: BuildCtx // the name of the file to emit (not including the file extension) slug: ServerSlug // the file extension diff --git a/quartz/plugins/emitters/404.tsx b/quartz/plugins/emitters/404.tsx index cd079a06..58ae59a4 100644 --- a/quartz/plugins/emitters/404.tsx +++ b/quartz/plugins/emitters/404.tsx @@ -7,6 +7,7 @@ import { FilePath, FullSlug } from "../../util/path" import { sharedPageComponents } from "../../../quartz.layout" import { NotFound } from "../../components" import { defaultProcessedContent } from "../vfile" +import { write } from "./helpers" export const NotFoundPage: QuartzEmitterPlugin = () => { const opts: FullPageLayout = { @@ -25,7 +26,7 @@ export const NotFoundPage: QuartzEmitterPlugin = () => { getQuartzComponents() { return [Head, Body, pageBody, Footer] }, - async emit(ctx, _content, resources, emit): Promise { + async emit(ctx, _content, resources): Promise { const cfg = ctx.cfg.configuration const slug = "404" as FullSlug @@ -48,7 +49,8 @@ export const NotFoundPage: QuartzEmitterPlugin = () => { } return [ - await emit({ + await write({ + ctx, content: renderPage(slug, componentData, opts, externalResources), slug, ext: ".html", diff --git a/quartz/plugins/emitters/aliases.ts b/quartz/plugins/emitters/aliases.ts index 210715eb..118c3926 100644 --- a/quartz/plugins/emitters/aliases.ts +++ b/quartz/plugins/emitters/aliases.ts @@ -1,13 +1,15 @@ import { FilePath, FullSlug, joinSegments, resolveRelative, simplifySlug } from "../../util/path" import { QuartzEmitterPlugin } from "../types" import path from "path" +import { write } from "./helpers" export const AliasRedirects: QuartzEmitterPlugin = () => ({ name: "AliasRedirects", getQuartzComponents() { return [] }, - async emit({ argv }, content, _resources, emit): Promise { + async emit(ctx, content, _resources): Promise { + const { argv } = ctx const fps: FilePath[] = [] for (const [_tree, file] of content) { @@ -32,7 +34,8 @@ export const AliasRedirects: QuartzEmitterPlugin = () => ({ } const redirUrl = resolveRelative(slug, file.data.slug!) - const fp = await emit({ + const fp = await write({ + ctx, content: ` diff --git a/quartz/plugins/emitters/assets.ts b/quartz/plugins/emitters/assets.ts index edc22d9e..cc97b2e3 100644 --- a/quartz/plugins/emitters/assets.ts +++ b/quartz/plugins/emitters/assets.ts @@ -10,7 +10,7 @@ export const Assets: QuartzEmitterPlugin = () => { getQuartzComponents() { return [] }, - async emit({ argv, cfg }, _content, _resources, _emit): Promise { + async emit({ argv, cfg }, _content, _resources): Promise { // glob all non MD/MDX/HTML files in content folder and copy it over const assetsPath = argv.output const fps = await glob("**", argv.directory, ["**/*.md", ...cfg.configuration.ignorePatterns]) diff --git a/quartz/plugins/emitters/cname.ts b/quartz/plugins/emitters/cname.ts index ffe2c6d1..3e17fea2 100644 --- a/quartz/plugins/emitters/cname.ts +++ b/quartz/plugins/emitters/cname.ts @@ -13,7 +13,7 @@ export const CNAME: QuartzEmitterPlugin = () => ({ getQuartzComponents() { return [] }, - async emit({ argv, cfg }, _content, _resources, _emit): Promise { + async emit({ argv, cfg }, _content, _resources): Promise { if (!cfg.configuration.baseUrl) { console.warn(chalk.yellow("CNAME emitter requires `baseUrl` to be set in your configuration")) return [] diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts index e8a81bc0..f92c0a9b 100644 --- a/quartz/plugins/emitters/componentResources.ts +++ b/quartz/plugins/emitters/componentResources.ts @@ -13,6 +13,7 @@ import { QuartzComponent } from "../../components/types" import { googleFontHref, joinStyles } from "../../util/theme" import { Features, transform } from "lightningcss" import { transform as transpile } from "esbuild" +import { write } from "./helpers" type ComponentResources = { css: string[] @@ -93,7 +94,7 @@ function addGlobalPageResources( function gtag() { dataLayer.push(arguments); } gtag("js", new Date()); gtag("config", "${tagId}", { send_page_view: false }); - + document.addEventListener("nav", () => { gtag("event", "page_view", { page_title: document.title, @@ -121,7 +122,7 @@ function addGlobalPageResources( umamiScript.src = "https://analytics.umami.is/script.js" umamiScript.setAttribute("data-website-id", "${cfg.analytics.websiteId}") umamiScript.async = true - + document.head.appendChild(umamiScript) `) } @@ -168,7 +169,7 @@ export const ComponentResources: QuartzEmitterPlugin = (opts?: Partial< getQuartzComponents() { return [] }, - async emit(ctx, _content, resources, emit): Promise { + async emit(ctx, _content, resources): Promise { // component specific scripts and styles const componentResources = getComponentResources(ctx) // important that this goes *after* component scripts @@ -190,7 +191,8 @@ export const ComponentResources: QuartzEmitterPlugin = (opts?: Partial< ]) const fps = await Promise.all([ - emit({ + write({ + ctx, slug: "index" as FullSlug, ext: ".css", content: transform({ @@ -207,12 +209,14 @@ export const ComponentResources: QuartzEmitterPlugin = (opts?: Partial< include: Features.MediaQueries, }).code.toString(), }), - emit({ + write({ + ctx, slug: "prescript" as FullSlug, ext: ".js", content: prescript, }), - emit({ + write({ + ctx, slug: "postscript" as FullSlug, ext: ".js", content: postscript, diff --git a/quartz/plugins/emitters/contentIndex.ts b/quartz/plugins/emitters/contentIndex.ts index fa8c2c9d..31e1d3e2 100644 --- a/quartz/plugins/emitters/contentIndex.ts +++ b/quartz/plugins/emitters/contentIndex.ts @@ -6,6 +6,7 @@ import { FilePath, FullSlug, SimpleSlug, joinSegments, simplifySlug } from "../. import { QuartzEmitterPlugin } from "../types" import { toHtml } from "hast-util-to-html" import path from "path" +import { write } from "./helpers" export type ContentIndex = Map export type ContentDetails = { @@ -91,7 +92,7 @@ export const ContentIndex: QuartzEmitterPlugin> = (opts) => { opts = { ...defaultOptions, ...opts } return { name: "ContentIndex", - async emit(ctx, content, _resources, emit) { + async emit(ctx, content, _resources) { const cfg = ctx.cfg.configuration const emitted: FilePath[] = [] const linkIndex: ContentIndex = new Map() @@ -115,7 +116,8 @@ export const ContentIndex: QuartzEmitterPlugin> = (opts) => { if (opts?.enableSiteMap) { emitted.push( - await emit({ + await write({ + ctx, content: generateSiteMap(cfg, linkIndex), slug: "sitemap" as FullSlug, ext: ".xml", @@ -125,7 +127,8 @@ export const ContentIndex: QuartzEmitterPlugin> = (opts) => { if (opts?.enableRSS) { emitted.push( - await emit({ + await write({ + ctx, content: generateRSSFeed(cfg, linkIndex, opts.rssLimit), slug: "index" as FullSlug, ext: ".xml", @@ -146,7 +149,8 @@ export const ContentIndex: QuartzEmitterPlugin> = (opts) => { ) emitted.push( - await emit({ + await write({ + ctx, content: JSON.stringify(simplifiedIndex), slug: fp, ext: ".json", diff --git a/quartz/plugins/emitters/contentPage.tsx b/quartz/plugins/emitters/contentPage.tsx index 338bfae4..f8e64047 100644 --- a/quartz/plugins/emitters/contentPage.tsx +++ b/quartz/plugins/emitters/contentPage.tsx @@ -8,6 +8,7 @@ import { FilePath, pathToRoot } from "../../util/path" import { defaultContentPageLayout, sharedPageComponents } from "../../../quartz.layout" import { Content } from "../../components" import chalk from "chalk" +import { write } from "./helpers" export const ContentPage: QuartzEmitterPlugin> = (userOpts) => { const opts: FullPageLayout = { @@ -26,7 +27,7 @@ export const ContentPage: QuartzEmitterPlugin> = (userOp getQuartzComponents() { return [Head, Header, Body, ...header, ...beforeBody, pageBody, ...left, ...right, Footer] }, - async emit(ctx, content, resources, emit): Promise { + async emit(ctx, content, resources): Promise { const cfg = ctx.cfg.configuration const fps: FilePath[] = [] const allFiles = content.map((c) => c[1].data) @@ -49,7 +50,8 @@ export const ContentPage: QuartzEmitterPlugin> = (userOp } const content = renderPage(slug, componentData, opts, externalResources) - const fp = await emit({ + const fp = await write({ + ctx, content, slug, ext: ".html", diff --git a/quartz/plugins/emitters/folderPage.tsx b/quartz/plugins/emitters/folderPage.tsx index 8632eceb..a4bd1aed 100644 --- a/quartz/plugins/emitters/folderPage.tsx +++ b/quartz/plugins/emitters/folderPage.tsx @@ -17,6 +17,7 @@ import { } from "../../util/path" import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout" import { FolderContent } from "../../components" +import { write } from "./helpers" export const FolderPage: QuartzEmitterPlugin = (userOpts) => { const opts: FullPageLayout = { @@ -35,7 +36,7 @@ export const FolderPage: QuartzEmitterPlugin = (userOpts) => { getQuartzComponents() { return [Head, Header, Body, ...header, ...beforeBody, pageBody, ...left, ...right, Footer] }, - async emit(ctx, content, resources, emit): Promise { + async emit(ctx, content, resources): Promise { const fps: FilePath[] = [] const allFiles = content.map((c) => c[1].data) const cfg = ctx.cfg.configuration @@ -82,7 +83,8 @@ export const FolderPage: QuartzEmitterPlugin = (userOpts) => { } const content = renderPage(slug, componentData, opts, externalResources) - const fp = await emit({ + const fp = await write({ + ctx, content, slug, ext: ".html", diff --git a/quartz/plugins/emitters/helpers.ts b/quartz/plugins/emitters/helpers.ts new file mode 100644 index 00000000..ef1d1c35 --- /dev/null +++ b/quartz/plugins/emitters/helpers.ts @@ -0,0 +1,19 @@ +import path from "path" +import fs from "fs" +import { BuildCtx } from "../../util/ctx" +import { FilePath, FullSlug, joinSegments } from "../../util/path" + +type WriteOptions = { + ctx: BuildCtx + slug: FullSlug + ext: `.${string}` | "" + content: string +} + +export const write = async ({ ctx, slug, ext, content }: WriteOptions): Promise => { + const pathToPage = joinSegments(ctx.argv.output, slug + ext) as FilePath + const dir = path.dirname(pathToPage) + await fs.promises.mkdir(dir, { recursive: true }) + await fs.promises.writeFile(pathToPage, content) + return pathToPage +} diff --git a/quartz/plugins/emitters/static.ts b/quartz/plugins/emitters/static.ts index f0118e2e..9f93d9b0 100644 --- a/quartz/plugins/emitters/static.ts +++ b/quartz/plugins/emitters/static.ts @@ -8,7 +8,7 @@ export const Static: QuartzEmitterPlugin = () => ({ getQuartzComponents() { return [] }, - async emit({ argv, cfg }, _content, _resources, _emit): Promise { + async emit({ argv, cfg }, _content, _resources): Promise { const staticPath = joinSegments(QUARTZ, "static") const fps = await glob("**", staticPath, cfg.configuration.ignorePatterns) await fs.promises.cp(staticPath, joinSegments(argv.output, "static"), { diff --git a/quartz/plugins/emitters/tagPage.tsx b/quartz/plugins/emitters/tagPage.tsx index 56691198..56a552c6 100644 --- a/quartz/plugins/emitters/tagPage.tsx +++ b/quartz/plugins/emitters/tagPage.tsx @@ -14,6 +14,7 @@ import { } from "../../util/path" import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout" import { TagContent } from "../../components" +import { write } from "./helpers" export const TagPage: QuartzEmitterPlugin = (userOpts) => { const opts: FullPageLayout = { @@ -32,7 +33,7 @@ export const TagPage: QuartzEmitterPlugin = (userOpts) => { getQuartzComponents() { return [Head, Header, Body, ...header, ...beforeBody, pageBody, ...left, ...right, Footer] }, - async emit(ctx, content, resources, emit): Promise { + async emit(ctx, content, resources): Promise { const fps: FilePath[] = [] const allFiles = content.map((c) => c[1].data) const cfg = ctx.cfg.configuration @@ -81,7 +82,8 @@ export const TagPage: QuartzEmitterPlugin = (userOpts) => { } const content = renderPage(slug, componentData, opts, externalResources) - const fp = await emit({ + const fp = await write({ + ctx, content, slug: file.data.slug!, ext: ".html", diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index eaeb12ae..bf1c0db0 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -36,19 +36,6 @@ export type QuartzEmitterPlugin = ( ) => QuartzEmitterPluginInstance export type QuartzEmitterPluginInstance = { name: string - emit( - ctx: BuildCtx, - content: ProcessedContent[], - resources: StaticResources, - emitCallback: EmitCallback, - ): Promise + emit(ctx: BuildCtx, content: ProcessedContent[], resources: StaticResources): Promise getQuartzComponents(ctx: BuildCtx): QuartzComponent[] } - -export interface EmitOptions { - slug: FullSlug - ext: `.${string}` | "" - content: string -} - -export type EmitCallback = (data: EmitOptions) => Promise diff --git a/quartz/processors/emit.ts b/quartz/processors/emit.ts index 3b357aa9..c68e0ede 100644 --- a/quartz/processors/emit.ts +++ b/quartz/processors/emit.ts @@ -1,10 +1,6 @@ -import path from "path" -import fs from "fs" import { PerfTimer } from "../util/perf" import { getStaticResourcesFromPlugins } from "../plugins" -import { EmitCallback } from "../plugins/types" import { ProcessedContent } from "../plugins/vfile" -import { FilePath, joinSegments } from "../util/path" import { QuartzLogger } from "../util/log" import { trace } from "../util/trace" import { BuildCtx } from "../util/ctx" @@ -15,19 +11,12 @@ export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) { const log = new QuartzLogger(ctx.argv.verbose) log.start(`Emitting output files`) - const emit: EmitCallback = async ({ slug, ext, content }) => { - const pathToPage = joinSegments(argv.output, slug + ext) as FilePath - const dir = path.dirname(pathToPage) - await fs.promises.mkdir(dir, { recursive: true }) - await fs.promises.writeFile(pathToPage, content) - return pathToPage - } let emittedFiles = 0 const staticResources = getStaticResourcesFromPlugins(ctx) for (const emitter of cfg.plugins.emitters) { try { - const emitted = await emitter.emit(ctx, content, staticResources, emit) + const emitted = await emitter.emit(ctx, content, staticResources) emittedFiles += emitted.length if (ctx.argv.verbose) {