手把手从零搭建一个 vue3 组件库 (三):模拟 element-plus 组件库文档

前两天有空看了一下 element-plus 的组件库文档实现部分,在这里总结一下。
上期讲过文档项目的搭建流程,对这一部分不熟悉的朋友可以先参考参考哈

手把手从零搭建一个 vue3 组件库 (二):为组件编写文档

本期任务如下:

  • 目录结构介绍
  • 自定义 md 插件
  • 封装 Demo 组件
  • 自定义样式

在线预览

github 地址

gitee 地址

前言

掘它…

目录结构

|-- docs
    |-- index.md                    // 首页展示
    |-- package.json
    |-- vite.config.ts              // vite 配置
    |-- .vitepress
    |   |-- config.ts               // vitepress 配置
    |   |-- config
    |   |   |-- nav.ts              // nav 配置
    |   |   |-- plugins.ts          // md 插件
    |   |   |-- sidebars.ts         // siderbar 配置
    |   |-- i18n                    // 国际化
    |   |   |-- pages
    |   |       |-- component.json
    |   |       |-- guide.json
    |   |       |-- sidebar.json
    |   |-- theme
    |   |   |-- index.ts
    |   |-- utils                   // 工具函数
    |   |   |-- highlight.ts        // 高亮 md
    |   |   |-- types.ts
    |   |-- vitepress
    |       |-- index.ts
    |       |-- component           // 公共组件
    |       |   |-- Demo.vue        // Demo 展示组件
    |       |-- styles              // 样式
    |           |-- app.scss
    |           |-- base.scss
    |           |-- code.scss
    |           |-- var.scss
    |-- examples                    // 组件 demo
    |   |-- button
    |       |-- basic.vue
    |-- public
    |   |-- images
    |       |-- vite.svg
    |-- zh
        |-- component               // 组件文档
        |   |-- button.md
        |-- guide                   // 指南文档
            |-- design.md

看看配置文件

// .vitepress/config.ts
import type { UserConfig } from 'vitepress'
import { mdPlugin } from './config/plugins'
import { sidebar } from './config/sidebars'
import { nav } from './config/nav'
export const config: UserConfig = {
  // base: '/vangle/', // 部署时的路径(默认 /)
  title: 'Vangle', // 网站标题
  description: 'a Vue 3 based component library for designers and developers', // 网站描述
  // 主题配置
  themeConfig: {
    logo: '/images/vite.svg',
    // 底部信息
    footer: {
      message: 'Released under the MIT License.',
      copyright: 'Copyright © 2022-PRESENT vangleer and Vangle contributors'
    },
    // algolia 配置
    algolia: {
      apiKey: 'your_api_key',
      indexName: 'index_name'
    },
    // 媒体连接
    socialLinks: [{ icon: 'github', link: 'https://github.com/vangleer' }],
    // 头部导航
    nav,
    // 侧边导航
    sidebar
  },
  // markdown 扩展
  markdown: {
    config: md => mdPlugin(md)
  }
}
export default config

nav 和 sidebar 都是对 /i18n/pages 里对应配置的解析,解析的方式也很简单,这里就不在多说了

自定义 md 插件

在上面的配置中,我们要特别关注 markdown 扩展 这块,也就是 mdPlugin 方法,关于更多的扩展可以查看 官方文档

下面我们就来解析这个方法吧,show you the code

  • Demo 组件、md、页面的对应关系

关系图

  • validate 的作用就是每次遇到 demo 块的时候都会执行 render 函数
import MarkdownIt from 'markdown-it'
import mdContainer from 'markdown-it-container'
import type Token from 'markdown-it/lib/token'
import type Renderer from 'markdown-it/lib/renderer'
import path from 'path'
import fs from 'fs'

import { highlight } from '../utils/highlight'

const localMd = MarkdownIt()

interface ContainerOpts {
  marker?: string | undefined
  validate?(params: string): boolean
  render?(
    tokens: Token[],
    index: number,
    options: any,
    env: any,
    self: Renderer
  ): string
}

export const mdPlugin = (md: MarkdownIt) => {
  md.use(mdContainer, 'div', {
    validate(params) {
      return !!params.trim().match(/^demo\s*(.*)$/)
    },
    render(tokens, idx) {
      /**
        :::demo Use `type`, `plain`, `round` and `circle` to define Button's style.
        button/basic
        :::
      */
      // console.log(tokens[idx].info, 'tokens, idx')

      const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/)
      if (tokens[idx].nesting === 1 /* means the tag is opening */) {
        // 拿到描述 Use `type`, `plain`, `round` and `circle` to define Button's style.
        const description = m && m.length > 1 ? m[1] : ''

        // 获取文件路径的 token
        const sourceFileToken = tokens[idx + 2]
        let source = ''
        // 文件路径 button/basic
        const sourceFile = sourceFileToken.children?.[0].content ?? ''

        if (sourceFileToken.type === 'inline') {
          // 根据路径读取 examples/button/basic.vue
          source = fs.readFileSync(
            path.resolve(__dirname, '../../examples', `${sourceFile}.vue`),
            'utf-8'
          )
        }
        if (!source) throw new Error(`Incorrect source file: ${sourceFile}`)

        // highlight 的作用就是将代码包裹一下,根据传递的语言加载该语言的样式
        return `<Demo source="${encodeURIComponent(
          highlight(source, 'vue')
        )}" path="${sourceFile}" raw-source="${encodeURIComponent(
          source
        )}" description="${encodeURIComponent(localMd.render(description))}">`
      } else {
        return `</Demo>`
      }
    }
  } as ContainerOpts)
}

封装 Demo 组件

  • 上面的图片对应关系中已经了解了 Demo 组件,mdPlugin 返回的是 jsx 类型的组件,因此需要在 vite.config.ts 中进行配置,先安装依赖 pnpm add @vitejs/plugin-vue-jsx -D,配置如下
// docs/vite.config.ts
import { defineConfig } from 'vite'
import vueJsx from '@vitejs/plugin-vue-jsx'
import DefineOptions from 'unplugin-vue-define-options/vite'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vueJsx(), DefineOptions()]
})
  • 还需要注册组件,这样我们就可以在 md 里使用了
import { VanButton } from '@vangle/components'
import { define } from '../utils/types'
import type { Theme as ThemeType } from 'vitepress'

import { globals } from '../vitepress'

// 使用vitepress-theme-demoblock主题,并注册组件(包含主题中默认的组件)。
import Theme from 'vitepress/dist/client/theme-default/index.js'

export default define<ThemeType>({
  ...Theme,
  enhanceApp: ({ app }) => {
    app.component('VanButton', VanButton)

    globals.forEach(([name, comp]) => app.component(name, comp))
  }
})

自定义样式

  • 样式目录:.vitepress/vitepress/styles

  • 需要 prism-theme-vars 这个包,pnpm add prism-theme-vars

// .vitepress/vitepress/styles/code.scss

// 导入 prism-theme-vars 样式
@use 'prism-theme-vars/base.css';
@use 'prism-theme-vars/marker.css';

:root {
  --border-color: #dcdfe6;
  --bg-color: #fff;
  --van-text-color-secondary: #909399;

  --prism-marker-opacity: 0.6;
  --prism-marker-color: var(--code-text-color);
  --prism-line-height: var(--code-line-height);
}

// 覆盖需要修改的样式
html:not(.dark) {
  --prism-builtin: #3182bd;
  --prism-comment: #848486;
  --prism-deleted: #3182bd;
  --prism-function: #6196cc;
  --prism-boolean: #c25205;
  --prism-number: #c25205;
  --prism-property: #717c11;
  --prism-punctuation: #a8a9cc;
  --prism-keyword: #c792ea;
  --prism-variable: #0b8235;
  --prism-url-decoration: #67cdcc;
  --prism-symbol: green;
  --prism-selector: #0b8235;

  --code-bg-color: #f5f7fa;
}

小结:

done…

结束语

到这里就是本篇文章的全部内容了,如果对大家有用,希望多多支持一下,你们的支持就是我的动力呀 (●’◡’●)

等我悄悄放几个组件,下期就写一下打包吧。。。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值