Making your own plugins
译-制作自己的插件
原文: https://quartz.jzhao.xyz/advanced/making-plugins
本文档的这一部分假设您具备 TypeScript 的基本知识,并将包含描述 Quartz 插件接口的代码片段。
处理流程
Quartz 的插件是对内容进行一系列转换。下图展示了处理流程:

即从上到下依次转换:
- Markdown Files | Markdown 文件
- Transformer | 转换器。可转换 text->text, markdown->markdown
- Modified files | 转换后的文件。HTML语法树 + 转换器的数据
- Filters | 过滤器。根据特定规则过滤一些文件
- Filtered files | 过滤后的文件
- Emitters | 发射器。根据已解析的文件生成新的文件 (ContentPage/FolderPage/ContentIndex)
- Generated Site | 生成的网站。一组包含 html、js、css 和 json 文件的集合
总api
所有插件都被定义为一个函数,该函数接受一个选项参数type OptionType = object | undefined,并返回一个与插件类型相对应的对象。
type OptionType = object | undefined
type QuartzPlugin<Options extends OptionType = undefined> = (opts?: Options) => QuartzPluginInstance
type QuartzPluginInstance =
| QuartzTransformerPluginInstance
| QuartzFilterPluginInstance
| QuartzEmitterPluginInstance以下各节将详细介绍每种插件类型可以实现的方法。在此之前,让我们先澄清一些容易产生歧义的类型:
BuildCtx定义于quartz/ctx.ts。它由以下部分组成:argv传递给 Quartz构建命令的命令行参数cfg:完整的石英配置allSlugs:所有有效内容别名的列表(有关别名的更多信息,请参阅路径)
StaticResources定义于quartz/resources.tsx。它由以下部分组成:css:一个待加载的 CSS 样式定义列表。CSS 样式由类型描述,CSSResource该类型也在 中定义quartz/resources.tsx。它接受源 URL 或样式表的内联内容。js:待加载脚本列表。脚本由类型描述,JSResource该类型也在 中定义quartz/resources.tsx。它允许您定义加载时间(DOM 加载之前或之后)、是否应为模块,以及脚本的源 URL 或内联内容。additionalHead:一个包含 JSX 元素或函数的列表,这些函数返回要添加到<head>页面标签中的 JSX 元素。函数接收页面数据作为参数,并可以根据条件渲染元素。
1. 转换器 (Transformers)
(变形金刚 的英语是 Transformers,所以有时这里也会被翻译成变形金刚。如果你在文档中看到这个词,需要知道他指的是 Transformers)
转换器会对内容进行映射,接收 Markdown 文件并输出修改后的内容或向文件本身添加元数据。
export type QuartzTransformerPluginInstance = {
name: string
textTransform?: (ctx: BuildCtx, src: string) => string
markdownPlugins?: (ctx: BuildCtx) => PluggableList
htmlPlugins?: (ctx: BuildCtx) => PluggableList
externalResources?: (ctx: BuildCtx) => Partial<StaticResources>
}所有转换器插件都必须至少定义一个name字段来注册插件,以及一些可选函数,允许您接入单个 Markdown 文件转换的各个部分。
1.1. textTransform (str -> str)
textTransform 在将文件解析为 Markdown AST 之前,执行文本到文本的转换。
1.2. markdownPlugins (remark plugin, mdast -> mdast)
markdownPlugins 定义了 remark 插件 的列表。remark 是一个将 Markdown 转换为结构化 Markdown 的工具。
api 如:
markdownPlugins() {
return [
() => {
return (tree, file) => { // tree 是一个 `mdast` 根元素, file 是一个 `vfile`
const text = file.value
const words = text.split(" ").length
file.data.wordcount = words // 往文件数据中添加单词计数
}
},
]
},1.3. htmlPlugins (rehype plugin, hast -> hast)
htmlPlugins 定义了 rehype 插件 列表。与 的工作方式类似 remark,rehype 它是一个以结构化方式将 HTML 转换为 HTML 的工具。
1.4. externalResource (on client, assets)
externalResources 定义插件正常工作可能需要在客户端加载的任何外部资源。
示例 - LaTeX 插件
通常情况下,对于 remark 和 rehype 这两种,您都可以找到现有的插件来使用。如果您想创建自己的 remark 或 rehype 插件,请查看使用 unified (底层 AST 解析器和转换器库)创建插件的指南。
LaTeX 插件是一个很好的Transformer插件示例,他借鉴了 remark 和 rehype 这两个系统的功能:
在阅读后文之前,你可先阅读 Quartz Plugin Demo 加深领悟
2. 过滤器
过滤器会过滤内容,获取所有转换器的输出,并确定哪些文件要保留,哪些文件要丢弃。
export type QuartzFilterPlugin<Options extends OptionType = undefined> = (
opts?: Options,
) => QuartzFilterPluginInstance
export type QuartzFilterPluginInstance = {
name: string
shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean
}过滤器插件必须定义一个name字段和一个shouldPublish函数,该函数接收一段经过所有转换器处理的内容,并根据是否应将其传递给发射器插件而返回一个true值。false
例如,以下是用于删除草稿的内置插件:
quartz/plugins/filters/draft.ts
import { QuartzFilterPlugin } from "../types"
export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({
name: "RemoveDrafts",
shouldPublish(_ctx, [_tree, vfile]) {
// uses frontmatter parsed from transformers
const draftFlag: boolean = vfile.data?.frontmatter?.draft ?? false
return !draftFlag
},
})3. 发射器
发射器会减少内容,接收所有已转换和已过滤的内容列表,并创建输出文件。
export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (
opts?: Options,
) => QuartzEmitterPluginInstance
export type QuartzEmitterPluginInstance = {
name: string
emit(
ctx: BuildCtx,
content: ProcessedContent[],
resources: StaticResources,
): Promise<FilePath[]> | AsyncGenerator<FilePath>
partialEmit?(
ctx: BuildCtx,
content: ProcessedContent[],
resources: StaticResources,
changeEvents: ChangeEvent[],
): Promise<FilePath[]> | AsyncGenerator<FilePath> | null
getQuartzComponents(ctx: BuildCtx): QuartzComponent[]
}发射器插件必须定义一个name字段、一个emit函数和一个getQuartzComponents函数。它还可以选择性地实现一个partialEmit用于增量构建的函数。
emit负责查看所有已解析和已过滤的内容,然后适当地创建文件并返回插件创建的文件路径列表。partialEmit是一个可选函数,用于启用增量构建。它接收有关哪些文件已更改的信息(changeEvents),并可以选择性地仅重新构建必要的文件。这对于优化开发模式下的构建时间非常有用。如果partialEmit未定义,则默认使用emit函数。getQuartzComponents声明发射器使用哪些石英组件来构建其页面。
创建新文件可以通过常规的 Node.js fs 模块(例如fs.files``fs.cp或 fs.files)完成fs.writeFile,也可以通过 writefs.files 函数(quartz/plugins/emitters/helpers.ts如果创建的文件包含文本)完成。fs.files``write函数的签名如下:
export type WriteOptions = (data: {
// the build context
ctx: BuildCtx
// the name of the file to emit (not including the file extension)
slug: FullSlug
// the file extension
ext: `.${string}` | ""
// the file content to add
content: string
}) => Promise<FilePath>这是一个简单的封装,用于将数据写入相应的输出文件夹并确保中间目录存在。如果您选择使用原生 Node API,请确保也fs将其输出到该文件夹。argv.output
如果您正在创建一个需要渲染组件的发射器插件,那么还有三点需要注意:
- 您的组件应该使用
getQuartzComponents<Component>声明一个用于构建页面的列表。有关更多信息,请参阅“创建组件”QuartzComponents页面。 - 您可以使用
renderPage定义的函数quartz/components/renderPage.tsx将 Quartz 组件渲染成 HTML。 - 如果您需要将 HTML AST 渲染为 JSX,可以使用该
htmlToJsx函数quartz/util/jsx.ts。示例可以在中找到quartz/components/pages/Content.tsx。
例如,以下是内容页面插件的简化版本,该插件会渲染每个页面。
quartz/plugins/emitters/contentPage.tsx
export const ContentPage: QuartzEmitterPlugin = () => {
// construct the layout
const layout: FullPageLayout = {
...sharedPageComponents,
...defaultContentPageLayout,
pageBody: Content(),
}
const { head, header, beforeBody, pageBody, afterBody, left, right, footer } = layout
return {
name: "ContentPage",
getQuartzComponents() {
return [head, ...header, ...beforeBody, pageBody, ...afterBody, ...left, ...right, footer]
},
async emit(ctx, content, resources, emit): Promise<FilePath[]> {
const cfg = ctx.cfg.configuration
const fps: FilePath[] = []
const allFiles = content.map((c) => c[1].data)
for (const [tree, file] of content) {
const slug = canonicalizeServer(file.data.slug!)
const externalResources = pageResources(slug, file.data, resources)
const componentData: QuartzComponentProps = {
fileData: file.data,
externalResources,
cfg,
children: [],
tree,
allFiles,
}
const content = renderPage(cfg, slug, componentData, opts, externalResources)
const fp = await emit({
content,
slug: file.data.slug!,
ext: ".html",
})
fps.push(fp)
}
return fps
},
}
}请注意,它接受一个FullPageLayout选项。它是通过组合一个参数SharedLayout和一个选项生成的PageLayout,这两个参数都通过文件提供quartz.layout.ts。
您可以在 Quartz 中查找quartz/plugins更多插件示例,作为您自己开发插件的参考!