Quartz 4 custom extensies: components en filter plugins

Definitie

Quartz 4 is uitbreidbaar via twee mechanismes: componenten (Preact JSX bestanden die de layout van een pagina bepalen) en plugins (transformers, filters, emitters die bepalen welke content waar terechtkomt). Beide leven in quartz/ naast de out-of-the-box Quartz-code en worden via kleine registratie-stappen aangeschakeld.

Context

Tijdens de bouw van de KennisBank wiki web UI op basis van Quartz 4 bleek snel dat de standaard layout en filter-set niet genoeg waren. Nodig was een eigen language-switcher, een Wikipedia-stijl infobox, en een filter dat concept-artikelen uit de publieke build houdt. Deze drie features illustreren de twee uitbreid-routes.

Kernpunten

Component pattern

Een custom component is een TypeScript bestand in quartz/components/MyComponent.tsx dat een QuartzComponent exporteert:

import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import styles from "./styles/myComponent.scss"
 
const MyComponent: QuartzComponent = ({ fileData, cfg, displayClass }: QuartzComponentProps) => {
  return <aside class="my-component">Hallo</aside>
}
 
MyComponent.css = styles
// Optioneel:
// MyComponent.beforeDOMLoaded = preScript
// MyComponent.afterDOMLoaded = postScript
 
export default (() => MyComponent) satisfies QuartzComponentConstructor

Registreer in quartz/components/index.ts met een default import en export, en plaats vervolgens in quartz.layout.ts onder beforeBody, left, right, header, footer of afterBody.

Component eigenschappen

  • css - import van een SCSS file, wordt bij build in de page-stylesheet meegenomen
  • beforeDOMLoaded - inline script dat voor DOM-parsing wordt uitgevoerd
  • afterDOMLoaded - inline script dat na DOM-parsing draait, wordt ook opnieuw uitgevoerd bij Quartz’s SPA nav event
  • Props: fileData, cfg, displayClass, externalResources, ctx

fileData.frontmatter als API

Alle frontmatter-velden uit YAML zijn via fileData.frontmatter als Record<string, any> beschikbaar. Custom velden zoals langs: [nl, en] zijn niet getyped maar wel leesbaar met een as any cast. Zo kan een component frontmatter lezen en daar rendering op baseren zonder de Quartz core aan te raken.

Inline scripts en het nav event

SPA-navigatie in Quartz vuurt een nav event waarop componenten hun state opnieuw moeten bouwen. Voorbeeld patroon:

document.addEventListener("nav", () => {
  const el = document.querySelector(".lang-switch")
  if (!el) return
  // init logic
  el.addEventListener("click", handler)
  window.addCleanup?.(() => el.removeEventListener("click", handler))
})

De window.addCleanup helper is Quartz-specifiek en voorkomt lekken bij herhaalde navigatie.

Filter plugin pattern

Een custom filter zit in quartz/plugins/filters/myFilter.ts en exporteert een QuartzFilterPlugin:

import { QuartzFilterPlugin } from "../types"
 
export const ExcludeConcept: QuartzFilterPlugin = () => ({
  name: "ExcludeConcept",
  shouldPublish(_ctx, [_tree, vfile]) {
    const status = (vfile.data.frontmatter as any)?.status
    return status !== "concept"
  },
})

Exporteren vanuit quartz/plugins/filters/index.ts en aanroepen in quartz.config.ts onder plugins.filters: [Plugin.RemoveDrafts(), Plugin.ExcludeConcept()].

shouldPublish krijgt [tree, vfile] als tweede argument en moet een boolean teruggeven. false betekent: overslaan in de emit-fase, het bestand verschijnt niet in public/.

Build-feedback lezen

Quartz’s build-output toont direct of een filter werkt:

Found 41 input files from `content` in 10ms
Parsing input files using 1 threads
Parsed 41 Markdown files in 1s
Filtered out 2 files in 95μs
Emitting files
Emitted 271 files to `public` in 4s

Filtered out N files bevestigt de filter is actief.

Heads-up: Head component en extra fonts

Als je extra Google Fonts wil laden die niet in cfg.theme.typography passen, injecteer ze via de Head.tsx component met een <link rel="stylesheet">. Vanuit custom.scss werkt @import url(...) na @use niet (Sass ordening). Zie wiki-sass-use-import-ordening.

Verbanden

Bronnen

Sessie-herkomst