mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-19 10:54:06 -06:00
* Initial plan * Initial analysis and plan for decoupling completion Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * docs: add @plugin annotations to transformers missing documentation Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * docs: mark decoupling phases and success criteria as complete Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * fix: move @plugin annotation in roam.ts to correct location Move the @plugin documentation block to immediately precede the RoamFlavoredMarkdown export, consistent with other transformer files (gfm.ts, syntax.ts, linebreaks.ts). Previously it was placed before the regex constant declarations. Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * Changes before error encountered Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * refactor: move documentation files from docs/ to project root Move IMPLEMENTATION_SUMMARY.md, PLUGIN_MIGRATION.md, and SECURITY_SUMMARY.md from docs/ directory to project root to keep them separate from user-facing documentation. Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * config: add implementation docs to ignore patterns Add IMPLEMENTATION_SUMMARY.md, PLUGIN_MIGRATION.md, and SECURITY_SUMMARY.md to ignorePatterns in quartz.config.ts to exclude them from the documentation build. These files are implementation documentation for the project itself, not user-facing documentation. Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * chore: remove build output directories from git tracking Remove public-current and public-v4 directories that were accidentally committed during build testing. These directories are already covered by .gitignore and should not be tracked in the repository. Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com>
6.4 KiB
6.4 KiB
Plugin Decoupling Migration Guide
This guide helps plugin authors migrate to the new decoupled plugin architecture introduced by the plugin decoupling strategy.
Overview
The plugin system has been enhanced with:
- Centralized VFile Schema (
quartz/plugins/vfile-schema.ts): Type-safe access to vfile data - Plugin Utilities (
quartz/plugins/plugin-context.ts): Abstracted utility functions viactx.utils - Test Helpers (
quartz/plugins/test-helpers.ts): Mock utilities for testing plugins - Readonly BuildCtx: Prevents accidental mutations from plugins
Using the New Plugin Utilities
Before: Direct Imports
import { QuartzTransformerPlugin } from "../types"
import { simplifySlug, transformLink, pathToRoot } from "../../util/path"
export const MyPlugin: QuartzTransformerPlugin = () => ({
name: "MyPlugin",
htmlPlugins(ctx) {
// Direct utility imports
const slug = simplifySlug(someSlug)
const link = transformLink(from, to, opts)
const root = pathToRoot(slug)
// ...
},
})
After: Using ctx.utils (Optional, Recommended for New Plugins)
import { QuartzTransformerPlugin } from "../types"
export const MyPlugin: QuartzTransformerPlugin = () => ({
name: "MyPlugin",
htmlPlugins(ctx) {
// Use utilities from context (no imports needed)
const slug = ctx.utils!.path.simplify(someSlug)
const link = ctx.utils!.path.transform(from, to, opts)
const root = ctx.utils!.path.toRoot(slug)
// ...
},
})
Available Utilities
Path Utilities (ctx.utils.path)
ctx.utils.path.slugify(path: FilePath) => FullSlug
ctx.utils.path.simplify(slug: FullSlug) => SimpleSlug
ctx.utils.path.transform(from: FullSlug, to: string, opts: TransformOptions) => RelativeURL
ctx.utils.path.toRoot(slug: FullSlug) => RelativeURL
ctx.utils.path.split(slug: FullSlug) => [FullSlug, string]
ctx.utils.path.join(...segments: string[]) => FilePath
Resource Utilities (ctx.utils.resources)
ctx.utils.resources.createExternalJS(src: string, loadTime?: "beforeDOMReady" | "afterDOMReady") => JSResource
ctx.utils.resources.createInlineJS(script: string, loadTime?: "beforeDOMReady" | "afterDOMReady") => JSResource
ctx.utils.resources.createCSS(resource: CSSResource) => CSSResource
Escape Utilities (ctx.utils.escape)
ctx.utils.escape.html(text: string) => string
VFile Data Type Safety
Before: Untyped Data Access
const toc = file.data.toc // Hope this exists!
After: Type-Safe Access
import { QuartzVFileData } from "../vfile-schema"
// TypeScript knows what's available
const toc = file.data.toc // TocEntry[] | undefined (typed!)
Documenting Plugin Data Dependencies
Add JSDoc comments to document what your plugin reads and writes:
/**
* @plugin TableOfContents
* @category Transformer
*
* @reads vfile.data.frontmatter.enableToc
* @writes vfile.data.toc
* @writes vfile.data.collapseToc
*
* @dependencies None
*/
export const TableOfContents: QuartzTransformerPlugin = () => ({
// ...
})
Testing Plugins
Use the new test helpers:
import { describe, it } from "node:test"
import { createMockPluginContext, createMockVFile } from "../test-helpers"
import { TableOfContents } from "./toc"
describe("TableOfContents", () => {
it("should generate TOC from headings", async () => {
const ctx = createMockPluginContext()
const file = createMockVFile({
frontmatter: { title: "Test", enableToc: "true" },
})
const plugin = TableOfContents()
const [markdownPlugin] = plugin.markdownPlugins!(ctx)
// Test the plugin...
})
})
Migration Strategy
- Existing Plugins: No changes required! The old import pattern still works.
- New Plugins: Use
ctx.utilsfor better decoupling and testability. - Gradual Migration: Update plugins incrementally as you work on them.
Important Notes
BuildCtx is Now Readonly
Plugins receive a readonly BuildCtx which prevents mutations:
// ❌ This will cause a TypeScript error
ctx.allSlugs.push(newSlug)
// ✅ Instead, write to vfile.data
file.data.aliases = [newSlug]
Backward Compatibility
All existing plugins continue to work without changes. The new utilities are optional and additive.
Benefits
- Better Testability: Mock utilities easily in tests
- Type Safety: Centralized vfile schema with autocomplete
- Reduced Coupling: Plugins don't import utilities directly
- Clearer Contracts: Document what plugins read/write
- Future-Proof: Easier to version and update utilities
Adding Custom VFile Fields
Custom plugins can add their own fields to the vfile data using TypeScript module augmentation:
import { QuartzTransformerPlugin } from "../types"
export interface MyCustomData {
customField: string
anotherField: number[]
}
/**
* @plugin MyCustomPlugin
* @category Transformer
*
* @writes vfile.data.myCustomData
*/
export const MyCustomPlugin: QuartzTransformerPlugin = () => ({
name: "MyCustomPlugin",
markdownPlugins() {
return [
() => {
return (tree, file) => {
// Add your custom data
file.data.myCustomData = {
customField: "value",
anotherField: [1, 2, 3],
}
}
},
]
},
})
// Extend the VFile DataMap with your custom fields
declare module "vfile" {
interface DataMap {
myCustomData?: MyCustomData
}
}
How it works:
- TypeScript's module augmentation allows multiple
declare module "vfile"statements - Each declaration merges into the same
DataMapinterface - Your custom fields become type-safe alongside built-in fields
- The centralized
vfile-schema.tsdoesn't prevent custom extensions
Best practices:
- Export your custom data type interfaces for reuse
- Use optional fields (
?) to indicate data may not always be present - Document what your plugin writes with JSDoc
@writesannotation - Add the module augmentation at the bottom of your plugin file
This allows third-party and custom plugins to extend the vfile data structure without modifying core files.
Next Steps
For more details, see:
quartz/plugins/vfile-schema.ts- VFile data typesquartz/plugins/plugin-context.ts- Plugin utilitiesquartz/plugins/test-helpers.ts- Testing utilitiesDESIGN_DOCUMENT_DECOUPLING.md- Complete strategy document