Skip to content

开发 VitePress 插件

学习如何开发 VitePress 插件来扩展站点功能。

插件类型

Vite 插件

VitePress 支持标准 Vite 插件:

ts
// .vitepress/config.mts
export default defineConfig({
  vite: {
    plugins: [
      myVitePlugin()
    ]
  }
})

Markdown 插件

通过 markdown 配置添加 Markdown-it 插件:

ts
export default defineConfig({
  markdown: {
    config(md) {
      md.use(myMarkdownPlugin)
    }
  }
})

构建钩子

使用配置中的钩子函数:

ts
export default defineConfig({
  transformHead(context) {
    // 修改 head
  },
  transformHtml(code, id, context) {
    // 修改 HTML
  },
  buildEnd(siteConfig) {
    // 构建结束
  }
})

创建 Vite 插件

基础结构

ts
// plugins/my-plugin.ts
import type { Plugin } from 'vite'

export function myPlugin(options = {}): Plugin {
  return {
    name: 'vitepress-plugin-my',
    
    // 配置解析后
    configResolved(config) {
      console.log('插件加载')
    },
    
    // 转换代码
    transform(code, id) {
      if (id.endsWith('.md')) {
        return transformMarkdown(code)
      }
      return null
    },
    
    // 构建开始
    buildStart() {
      console.log('构建开始')
    },
    
    // 构建结束
    buildEnd() {
      console.log('构建结束')
    }
  }
}

function transformMarkdown(code: string): string {
  // 转换 Markdown 内容
  return code.replace(/{{\s*(\w+)\s*}}/g, (_, key) => {
    return `<span class="var-${key}">${key}</span>`
  })
}

在 VitePress 中使用

ts
// .vitepress/config.mts
import { myPlugin } from './plugins/my-plugin'

export default defineConfig({
  vite: {
    plugins: [
      myPlugin({
        // 配置选项
      })
    ]
  }
})

创建 Markdown 插件

基础结构

ts
// plugins/markdown-plugin.ts
import type MarkdownIt from 'markdown-it'

export function markdownPlugin(md: MarkdownIt) {
  // 添加自定义块
  md.block.ruler.before('paragraph', 'myblock', (state, startLine, endLine) => {
    const pos = state.bMarks[startLine] + state.tShift[startLine]
    const max = state.eMarks[startLine]
    
    if (state.src.slice(pos, max) !== ':::myblock') {
      return false
    }
    
    // 解析自定义块
    let nextLine = startLine + 1
    
    while (nextLine < endLine) {
      const linePos = state.bMarks[nextLine] + state.tShift[nextLine]
      const lineMax = state.eMarks[nextLine]
      
      if (state.src.slice(linePos, lineMax) === ':::') {
        break
      }
      nextLine++
    }
    
    const content = state.getLines(startLine + 1, nextLine, state.blkIndent, true)
    
    const token = state.push('myblock', 'div', 0)
    token.content = content
    token.map = [startLine, nextLine]
    
    state.line = nextLine + 1
    return true
  })
  
  // 渲染规则
  md.renderer.rules.myblock = (tokens, idx) => {
    return `<div class="my-block">${tokens[idx].content}</div>`
  }
}

在 VitePress 中使用

ts
export default defineConfig({
  markdown: {
    config(md) {
      md.use(markdownPlugin)
    }
  }
})

构建钩子示例

生成 RSS

ts
// .vitepress/config.mts
import { writeFileSync } from 'fs'

export default defineConfig({
  async buildEnd({ outDir, siteConfig }) {
    // 读取所有页面
    const pages = await getPages(outDir)
    
    // 生成 RSS
    const rss = generateRSS(pages)
    
    writeFileSync(`${outDir}/rss.xml`, rss)
  }
})

注入全局数据

ts
export default defineConfig({
  transformPageData(pageData) {
    // 添加自定义字段
    pageData.customField = 'value'
    
    // 计算阅读时间
    pageData.readingTime = calculateReadingTime(pageData.content)
  }
})

完整插件示例

图片优化插件

ts
// plugins/image-optimization.ts
import type { Plugin } from 'vite'
import sharp from 'sharp'
import fs from 'fs'
import path from 'path'

interface ImageOptimizationOptions {
  quality?: number
  formats?: ('webp' | 'avif')[]
  sizes?: number[]
}

export function imageOptimization(
  options: ImageOptimizationOptions = {}
): Plugin {
  const {
    quality = 80,
    formats = ['webp'],
    sizes = [640, 1024, 1920]
  } = options

  return {
    name: 'vitepress-plugin-image-optimization',
    
    async buildEnd() {
      const publicDir = path.resolve('docs/public')
      const images = findImages(publicDir)
      
      for (const image of images) {
        const ext = path.extname(image)
        const base = path.basename(image, ext)
        const dir = path.dirname(image)
        
        for (const format of formats) {
          for (const size of sizes) {
            const outputPath = path.join(
              dir,
              `${base}-${size}w.${format}`
            )
            
            await sharp(image)
              .resize(size)
              .toFormat(format, { quality })
              .toFile(outputPath)
            
            console.log(`Generated: ${outputPath}`)
          }
        }
      }
    }
  }
}

function findImages(dir: string): string[] {
  const images: string[] = []
  const entries = fs.readdirSync(dir, { withFileTypes: true })
  
  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name)
    
    if (entry.isDirectory()) {
      images.push(...findImages(fullPath))
    } else if (/\.(png|jpe?g)$/i.test(entry.name)) {
      images.push(fullPath)
    }
  }
  
  return images
}

使用插件

ts
import { imageOptimization } from './plugins/image-optimization'

export default defineConfig({
  vite: {
    plugins: [
      imageOptimization({
        quality: 80,
        formats: ['webp'],
        sizes: [640, 1024]
      })
    ]
  }
})

发布插件

package.json

json
{
  "name": "vitepress-plugin-my",
  "version": "1.0.0",
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  },
  "files": ["dist"],
  "peerDependencies": {
    "vitepress": ">=1.0.0"
  },
  "keywords": [
    "vitepress",
    "plugin"
  ]
}

学习检查清单

  • [ ] 理解了插件类型
  • [ ] 创建了 Vite 插件
  • [ ] 创建了 Markdown 插件
  • [ ] 使用了构建钩子
  • [ ] 发布了插件到 npm

下一步

继续学习 构建组件文档站,为 Vue 组件创建文档。

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献