quartz/PLUGIN_MIGRATION.md
Copilot 2b63a094fe
docs: complete plugin decoupling with @plugin annotations (#7)
* 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>
2025-11-17 02:02:41 +01:00

238 lines
6.4 KiB
Markdown

# 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:
1. **Centralized VFile Schema** (`quartz/plugins/vfile-schema.ts`): Type-safe access to vfile data
2. **Plugin Utilities** (`quartz/plugins/plugin-context.ts`): Abstracted utility functions via `ctx.utils`
3. **Test Helpers** (`quartz/plugins/test-helpers.ts`): Mock utilities for testing plugins
4. **Readonly BuildCtx**: Prevents accidental mutations from plugins
## Using the New Plugin Utilities
### Before: Direct Imports
```typescript
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)
```typescript
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`)
```typescript
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`)
```typescript
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`)
```typescript
ctx.utils.escape.html(text: string) => string
```
## VFile Data Type Safety
### Before: Untyped Data Access
```typescript
const toc = file.data.toc // Hope this exists!
```
### After: Type-Safe Access
```typescript
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:
```typescript
/**
* @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:
```typescript
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
1. **Existing Plugins**: No changes required! The old import pattern still works.
2. **New Plugins**: Use `ctx.utils` for better decoupling and testability.
3. **Gradual Migration**: Update plugins incrementally as you work on them.
## Important Notes
### BuildCtx is Now Readonly
Plugins receive a readonly `BuildCtx` which prevents mutations:
```typescript
// ❌ 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:
```typescript
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 `DataMap` interface
- Your custom fields become type-safe alongside built-in fields
- The centralized `vfile-schema.ts` doesn't prevent custom extensions
**Best practices:**
1. Export your custom data type interfaces for reuse
2. Use optional fields (`?`) to indicate data may not always be present
3. Document what your plugin writes with JSDoc `@writes` annotation
4. 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 types
- `quartz/plugins/plugin-context.ts` - Plugin utilities
- `quartz/plugins/test-helpers.ts` - Testing utilities
- `DESIGN_DOCUMENT_DECOUPLING.md` - Complete strategy document