组件库文档 Markdown解析多demo组件 (Yike-Design)

1. 前言

Yike-Design组件库项目自7月1日正式开源已近一月

在尝试和挑战中收获知识,在碰撞和交流中收获友谊,这也是参与开源项目最棒的意义~

今天开始,我会在社区中分享自己在组件库开发过程中的收获,期望这能对大家有所帮助,也希望能够通过文档的形式让大家都能够理解我的开发思路,以便后续的共同维护

当然,我目前还只是一个 积极参与者,认知有限,水平有限,我随时期待来自你的指导和建议

此前,shaka好兄弟已经在之前的文章中对文档的使用和编写规范进行了大致说明

那么这篇文章将为大家带来Yike-Design组件库关于多样例文档部分基于Markdown-It实现方法,并将逐步拆解其实现路径,让我们开始吧

2. 实现效果

大家可以先看一下最终的实现效果 ⬇⬇⬇

3.背景

技术的实现路径取决于技术的应用场景,我们的组件库文档的设计主要有以下需求

  • 文档与组件库分离
  • 单个组件需要存在多个Demo实例
  • 每个Demo之间状态隔离、数据隔离
  • 对于开发者而言,API、说明文档的编写要尽可能高效、简洁
  • 文档要尽可能美观

显然,通过Markdown能够高效地编写这类文档,VitePress和markdown-it等插件也已经完整地支持渲染组件,本文将讲述基于Markdown-it这个工具实现组件库文档的过程

4. 实现路径

Markdown-It

先来认识一下Markdown-It这个工具吧,它能够把md文件转换为html,作为一个强大的工具,VitePress底层也采用它进行渲染

我们可以逐步体验它的能力

  1. 安装依赖 bash
css 复制代码
npm i -g markdown-it
  1. 测试解析
javascript 复制代码
import md from 'markdown-it';

const parse_demo = `
## 这里是一份文档

### 这里应该变成H3`;

console.log(md().render(parse_demo));

最终输出的产物便是

  1. vite.config.ts 引入插件

至此,我们常规的markdown都可以通过markdown-it进行解析了,下面我们尝试在vite项目中使用插件解析md文件,支持直接添加到路由中

javascript 复制代码
const developRoutes: Array<RouteRecordRaw> = [
  {
    path: '/develop',
    name: 'develop',
    component: () =>  import('../CONTRIBUTING.md'),
  },
];
  1. 自定义插件

新建一个vite-plugin-md.mjs文件用来配置我们的自定义插件,我们使用最基本的transform解析能力

javascript 复制代码
import MarkdownIt from 'markdown-it'

export default () => ({
  name: 'vitePluginMarkdown',
  // src为文件内容,id为当前文件的路径
  transform(src, id) {
    // 匹配.md后缀的文件进行解析
    if (id.endsWith('.md')) {
      const markdownIt = MarkdownIt({
        html: true,
        xhtmlOut: false,
      })
      // 解析之后的html文档需要在外层包裹<template>根结点
      return {
        code: `<template>${markdownIt.render(src)}</template>`,
        map: null,
      }
    }
  },
})
  1. 修改vite的配置文件

vite.config.ts中引入这个插件

javascript 复制代码
import vitePluginMarkdown from './plugins/vite-plugin-md.mjs';

const vuePlugin = createVuePlugin({ include: [/\.vue$/, /\.md$/] }); 
// 配置可编译 .vue 与 .md 文件

export default defineConfig({
  plugins: [
    vitePluginMarkdown(),
    vuePlugin,
  ],
});

💐💐💐 此时,你就可以直接通过对应的路由访问到md文档了

添加并解析Vue组件

现在可以开始加入我们的Demo组件解析了,我们先准备一下需要解析的文档和对应的组件

yk-button是我们已经全局注册的组件,他能够很轻松地被渲染出来

bash 复制代码
### 这里是文件解析

<yk-button>测试</yk-button>

解析自定义组件(.vue)

但是 ,我们期望的是将的多个包含demo样例的自定义模版添加进md文档中进行解析,全都在一个doc.md文件里维护demo样例显然是不合适的

因此,我们需要在md文档中引入编写好的demo文件进行渲染,如下

而自定义组件想要被正常解析需要提前引入,让我们去自定义插件里实现一下~

vite-plugin-md.mjs

javascript 复制代码
export default () => ({
  name: 'vitePluginMarkdown',
  transform(src, id) {
      ...
      ...
      return {
        code: `
        <script setup>import ParsePrimary from './parse-primary.vue';</script>
        <template>${markdownIt.render(src)}</template>`,
        map: null,
      }
    }
  },
})
      

此时,我们的Demo样例组件就可以被渲染出来了

走到这一步,我们的组件库文档已经初见端倪,我们已经可以通过不断地编写demo文件,并在md文件中引入即可,当然,我们需要在插件中把对应的组件引入进来,下一步,便是实现每个Demo的源码复制使用说明

通过(Snippet.vue)组件实现单个Demo的规范化样式

Snippet 组件是我们的Demo容器,主要包含了四个部分的内容

  • Title demo的标题
  • Desc demo的说明
  • Demo demo的渲染
  • Code demo的示例源代码

其中,title和code部分通过props传入,desc和demo部分则提供插槽直接由Markdown-It进行渲染,当然,具体这个容器需要长啥样,大家根据需要自己编写代码即可

html 复制代码
<template>
  <div class="case-card">
    <!-- id 用于锚点定位 -->
    <yk-title :id="title" :level="3">{{ title }}</yk-title>
    <slot name="desc"></slot>
    <div class="container">
      <slot name="demo"></slot>
    </div>
    <yk-space class="space" :size="8">
      <div v-show="showCode" class="icons" @click="onCopy">
        <yk-icon name="yk-kaobei"></yk-icon>
      </div>
      <div class="icons" :class="{ select: showCode }" @click="clickShow">
        <yk-icon name="yk-daima"></yk-icon>
      </div>
    </yk-space>
    <div v-show="showCode" ref="codes" class="codes">
      <pre class="hljs"><code v-html="html"></code></pre>
    </div>
  </div>
</template>
<script setup lang="ts">
const props = defineProps({
  title: {
    type: String,
    default: '标题',
  },
  code: {
    type: String,
    default: '',
  },
})

const html = hljs.highlightAuto(decodeURIComponent(props.code)).value
</script>

至此,我们只需要在插件中渲染yk-snippet这个组件然后把四个组成部分丢给他,便可完成整个文档库的渲染

代码拆解

约定Demo文件格式

对于一个Demo而言,我们只关心那四个部分,我们需要在编写的时候进行规范化以便在脚本侧提取想要的内容,第一行为Title,第二行为Desc,第三行则是期望引入的Demo文件,源码部分我们将直接根据这个路径去读取文件内容

markdown 复制代码
:::snippet
按钮类型 type
按钮有三种类型:`主按钮` 、`次按钮` 、`线框按钮` 。主按钮在同一个操作区域建议最多出现一次。
<ButtonPrimary/>
:::

匹配Demo块并解析对应内容

javascript 复制代码
  const snippetPattern = /:::snippet\s+(.*?)\s+:::/gs
  //匹配md文件中所有的snippet块
  const matches = src.matchAll(snippetPattern) 
  for (const match of matches) {
    // parse three lines in snippet block
    // 提取三行内容
    const [title, desc, demoName] = match[1].split('\n')
    // match demo Vue components
    // 去除括号和斜杠,直接得到Demo组件名称,如ButtonPrimary
    const tagPattern = /<(\w+)\/>/
    const demoTagName = demoName.match(tagPattern)[1] // <ButtonPrimary/> -> ButtonPrimary
    
    const demoComponentName = camelToDashCase(demoTagName).replace(
      /([a-zA-Z])([A-Z])/g,
      '$1-$2',
    ) // ButtonPrimary -> button-primary

    // 获取源码
    const demoCode = fetchDemoCode(demoComponentName, id)
    
    // 根据组件和md文件的相对路径添加依赖
    const importLine = `import ${demoTagName} from './${demoComponentName}.vue';\n`
    importContent += importLine
    
    // 调用demo容器组件,将demoCode和title作为props传入
    // Demo 将直接渲染组件实例,作为插槽传入
    // Desc 用MrkdownIt解析为html,作为插槽传入
    const caseCardContent = `<yk-snippet title="${title}" code="${encodeURIComponent(
      demoCode,
    )}" >
      <template v-slot:demo>${demoName}</template>
      <template v-slot:desc>${markdownIt.render(desc)}</template>
    </yk-snippet>
    `
    // 替换原来待渲染的内容
    src = src.replace(match[0], caseCardContent) // html render
    

工具方法

需要两个工具方法

javascript 复制代码
// 驼峰命名转短横线
export function camelToDashCase(str) {
  return str.replace(/([a-zA-Z])([A-Z])/g, '$1-$2').toLowerCase()
}
// fetch demo source code by relative path
// 根据Demo组件文件名和当前md文档的绝对路径,读取demo组件源代码
export function fetchDemoCode(componentName, id) {
  const targetFile = `${componentName}.vue`
  const absolutePath = path.resolve(path.dirname(id), targetFile)
  try {
    const content = fs.readFileSync(absolutePath, 'utf-8')
    return content
  } catch (error) {
    return ''
  }
}

返回渲染内容

将所有的snippet块均处理为yk-snippet容器进行渲染后,我们返回对应的script和template部分即可,这边外层包了一个带类名的div,主要是为了添加额外的自定义样式

javascript 复制代码
  return {
    code: `
    <script setup>
      ${importContent}
    </script>
    <template>
      <div class='yk-demo-doc'>
        ${markdownIt.render(src)}
      </div>
    </template>`,
    map: null,
  }

额外工作~

当然,如果不想每次都引入yk-snippet这个容器组件的话,可以外main.ts里注册一下

javascript 复制代码
import Snippet from './components/Snippet.vue';
app.component('YkSnippet', Snippet);

5. 自定义代码块

当然,为了应对Icon这种需要直接渲染demo的场景,我们也提供了pure代码块

markdown 复制代码
:::pure
<IconPlanarity/>
:::

6.结束

其实我的实现类似于markdown-it-container的对于的处理,有需要的话也完全可以通过定义其他标志符号采取不同的渲染逻辑,目前看,可能替换并渲染的方案不是最优雅的,如果有意见和建议也欢迎随时提出~

后续可能会针对项目中npm run new脚本和目前我着手实现的MessageUpload组件输出文档,对于阅读体验和行文逻辑有建议的小伙伴可以提出意见,期待和你们共同进步!!!

相关推荐
2401_8827275733 分钟前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
NoneCoder36 分钟前
CSS系列(36)-- Containment详解
前端·css
anyup_前端梦工厂1 小时前
初始 ShellJS:一个 Node.js 命令行工具集合
前端·javascript·node.js
5hand1 小时前
Element-ui的使用教程 基于HBuilder X
前端·javascript·vue.js·elementui
GDAL1 小时前
vue3入门教程:ref能否完全替代reactive?
前端·javascript·vue.js
六卿1 小时前
react防止页面崩溃
前端·react.js·前端框架
z千鑫2 小时前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js
m0_748256142 小时前
前端 MYTED单篇TED词汇学习功能优化
前端·学习
小白学前端6663 小时前
React Router 深入指南:从入门到进阶
前端·react.js·react
web130933203984 小时前
前端下载后端文件流,文件可以下载,但是打不开,显示“文件已损坏”的问题分析与解决方案
前端