Vue3 开发态轻量组件文档方案:基于动态路由 + Markdown

🚩 背景

在 Vue3 业务项目中,常见做法是将复用组件集中放到 src/components 目录。但随着多人并行开发,逐渐出现以下痛点:

  • 🤷‍♂️ 不知道已有封装(重复造轮子)
  • 🧪 组件封装质量参差不齐,缺乏复用指引
  • 📄 大量组件无使用文档 / 无交互示例
  • 🔍 逐个打开文件效率低
  • 🗣️ 口头沟通成本高,给人添麻烦

引入独立组件库(例如 storybook / docs site)成本过高,不符合仅为"项目内业务组件"做快速可见化的诉求,因此需要一个"足够轻"且"低侵入"的内部文档解决方案。

🎯 目标(Design Goals)

目标 说明
低侵入 不新增独立入口,不污染生产包体
零上手成本 开发者只需新增/维护 .md 文件
自动化收集 自动扫描 components 下 Markdown 文档
支持热更新 开发态修改文档立即生效
支持组件示例 Markdown 内可内联 Vue 组件预览
平滑演进 未来可拓展"示例 + 源码复制 + 搜索"等功能

🧩 方案概述

核心思想:仅在开发环境动态注入一个内部路由 /playDoc,该页面会:

  1. 使用 import.meta.glob 递归扫描 src/components/**/*.md
  2. 借助 unplugin-vue-markdown.md 编译为 Vue 组件
  3. 将 Markdown 渲染为动态组件并支持切换
  4. 后续扩展:内联示例、源码折叠、预览/复制等

✅ 优势:无需建立二次入口、无需新开端口、无需发布,生产环境自动剔除。

最初的想法是做成多入口文件,单独启动预览,实践中发现有点复杂,除了要加一套入口文件和项目配置外,有的依赖包必须要在 vite.config.dev.ts 中导入,否则影响构建,改动较多所以放弃了。


🏗️ 实现步骤

1. 安装依赖

bash 复制代码
pnpm add -D unplugin-vue-markdown

2. Vite 插件配置

ts 复制代码
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import Markdown from "unplugin-vue-markdown/vite";

export default defineConfig(({ mode }) => {
  const isDev = mode === "development";

  return {
    plugins: [
      vueJsx(),
      vue({
        include: [/\.vue$/, /\.md$/], // 让 .md 也走 Vue 编译
      }),
      isDev &&
        Markdown({
          // 最简单就是什么都不配置,也可根据文档按需扩展 markdown-it 插件
          // headEnabled: false,
          // wrapperClasses: "md-doc-body",
          // markdownItSetup(md) {
            // 示例:支持 :::tip 容器、目录、task list 等
            // md.use(require("markdown-it-anchor")).use(require("markdown-it-task-lists"));
          },
        }),
    ]
  };
});

3. 类型声明

src/types/shims.d.ts

ts 复制代码
declare module "*.vue" {
  import type { Component } from "vue";
  const component: Component;
  export default component;
}

declare module "*.md" {
  import type { Component } from "vue";
  const component: Component;
  export default component;
}

4. 动态开发路由注入

ts 复制代码
import type { RouteRecordRaw } from "vue-router";

const baseRoutes: RouteRecordRaw[] = [
  // ...你的真实业务路由
];

const devDocRoute: RouteRecordRaw[] =
  import.meta.env.DEV
    ? [
        {
          path: "/playDoc",
          name: "PlayDoc",
          component: () => import("@/components/PlayDoc.vue"),
          meta: { hidden: true, title: "组件文档" },
        },
      ]
    : [];

export default [...baseRoutes, ...devDocRoute];

5. 文档页面组件(核心实现)

创建 src/components/PlayDoc.vue,组件内容借助 AI 实现。(简单示例)

vue 复制代码
<template>
  <div class="play-doc">
    <div class="sidebar">
      <h3>组件文档</h3>
      <ul class="doc-list">
        <li
          v-for="doc in docFiles"
          :key="doc.path"
          :class="{ active: currentDoc === doc.path }"
          @click="loadDoc(doc)"
        >
          {{ doc.name }}
        </li>
      </ul>
    </div>
    <div class="content">
      <div v-if="currentDocComponent" class="doc-content">
        <component :is="currentDocComponent" />
      </div>
      <div v-else class="empty">选择一个文档查看</div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";
import "element-plus/dist/index.css";

interface DocFile {
  name: string;
  path: string;
  module: () => Promise<any>;
}

const docFiles = ref<DocFile[]>([]);
const currentDoc = ref<string>("");
const currentDocComponent = ref<any>(null);

// 动态获取 components 目录下的 md 文件
const getDocFiles = () => {
  const modules = import.meta.glob("/src/components/**/*.md");
  console.log(modules, "modules");

  const files: DocFile[] = [];
  Object.entries(modules).forEach(([path, moduleLoader]) => {
    const name = path.split("/").pop()?.replace(".md", "") || "";
    files.push({
      name,
      path,
      module: moduleLoader as () => Promise<any>,
    });
  });

  docFiles.value = files;
  if (files.length > 0) {
    loadDoc(files[0]); // 默认加载第一个文档
  }
};

const loadDoc = async (doc: DocFile) => {
  try {
    currentDoc.value = doc.path;
    const module = await doc.module();
    currentDocComponent.value = module.default;
  } catch (error) {
    console.error("加载文档失败:", error);
  }
};

onMounted(() => {
  getDocFiles();
});
</script>

6. 示例组件文档(开发者需要编写的 .md)

md 复制代码
# FancyButton 按钮

用于演示的业务按钮组件。

## ✅ 特性
- 支持主题类型
- 支持加载状态
- 自动继承原生 button 属性

## 🔌 基础用法
.
.
.
.
.

📂 目录结构

bash 复制代码
src/
  components/
    PlayDoc.vue          # 文档入口(仅开发态路由引用)
    FancyButton/
      index.vue
      FancyButton.md     # 组件文档
    UserAvatar/
      index.vue
      UserAvatar.md
    charts/
      BarChart.vue
      BarChart.md

命名规范:

  • 每个"可复用业务组件"目录下放置同名 .md
  • 无文档的组件会在后续统计中提示(可扩展自动检测)

注意事项和拓展:

说明
生产环境剔除 路由通过 import.meta.env.DEV 条件控制
风格隔离 PlayDoc.vue 设置样式时,不要影响到引入的子组件
Markdown 能力 学习下插件用法,或集成别的插件,增强代码预览

✅ 总结

该方案通过"开发态路由 + Markdown 编译为 Vue 组件"实现了一个:

  • 不额外开启端口
  • 不改变生产构建
  • 几乎零上手成本
  • 可持续迭代增强

的内部组件文档系统。适合业务项目在"尚未抽象到组件库层级"的组件复用与提效。

🚀 先让文档"存在且可见",再逐步"结构化 + 自动化"。

后续继续补充......

相关推荐
一只小风华~3 小时前
Vue: ref、reactive、shallowRef、shallowReactive
前端·javascript·vue.js
计算机毕业设计指导4 小时前
基于Spring Boot + Vue 3的社区养老系统设计与实现
vue.js·spring boot·后端
神仙别闹5 小时前
基于 Vue+SQLite3开发吉他谱推荐网站
前端·vue.js·sqlite
方安乐5 小时前
vite+vue+js项目使用ts报错
前端·javascript·vue.js
韩立23336 小时前
Vue 3.5 升级指南
前端·vue.js
一直在学习的小白~6 小时前
node_modules 明明写进 .gitignore,却还是被 push/commit 的情况
前端·javascript·vue.js
小程序设计7 小时前
【springboot+vue】高校迎新平台管理系统(源码+文档+调试+基础修改+答疑)
vue.js·spring boot·后端
会有钱的-_-7 小时前
基于webpack的场景解决
前端·vue.js·webpack·安全性测试
Abadbeginning8 小时前
FastSoyAdmin centos7云服务器+宝塔部署
vue.js·后端·python