mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-20 03:14:06 -06:00
* Initial plan * docs: format design document for decoupling strategy Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * feat: implement Phase 1-4 of plugin decoupling strategy - Created centralized vfile-schema.ts with all VFile data type definitions - Created plugin-context.ts with PluginUtilities abstraction layer - Created test-helpers.ts for plugin testing utilities - Made BuildCtx readonly to prevent plugin mutations - Added MutableBuildCtx for build orchestration layer - Updated components to not mutate ctx.trie - Updated FrontMatter plugin with temporary cast for backward compatibility - Updated TransformOptions to accept ReadonlyArray Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * feat: add plugin utilities to BuildCtx and document plugin decoupling - Added utils property to BuildCtx with createPluginUtilities() - Documented plugin data dependencies with JSDoc comments on transformers and filters - Created comprehensive plugin migration guide in docs/PLUGIN_MIGRATION.md - Updated plugin types.ts with guidance on using PluginContext - All plugins now have access to ctx.utils for path, resource, and escape utilities Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * docs: add implementation and security summaries - Created IMPLEMENTATION_SUMMARY.md documenting all phases completed - Created SECURITY_SUMMARY.md with CodeQL scan results - All security checks passed with 0 vulnerabilities - Complete documentation of changes, decisions, and impact Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * docs: add guidance for custom plugins extending vfile data - Added section "Adding Custom VFile Fields" to PLUGIN_MIGRATION.md - Demonstrates TypeScript module augmentation for custom fields - Shows best practices for extending vfile data in third-party plugins - Clarifies that centralized schema doesn't prevent custom extensions Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * refactor: address code review feedback - Remove `any` type annotations from component map callbacks (Breadcrumbs, FolderContent) - Fix test-helpers: change `config` to `cfg` to match BuildCtx interface - Add `trie: undefined` to mock context for completeness - Add missing vfile fields to schema: text, blocks, htmlAst, hasMermaidDiagram - Make Argv properties truly readonly with ReadonlyArgv type - Add documentation explaining module augmentation pattern is intentional - Document FrontMatter mutation as known technical debt with timeline 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>
238 lines
6.4 KiB
Markdown
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
|