Code前端首页关于Code前端联系我们

使用npm创建项目

terry 5天前 阅读数 995 #Vue
文章标签 npm项目创建

Vue3项目里要加Markdown编辑/预览功能,选哪个开源库?怎么快速搭建?

前阵子帮朋友做一个轻量级技术博客的后台,他指定用Vue3+Vite,核心需求就是一个带实时预览、一键导出图片/HTML、代码高亮、支持数学公式渲染的编辑器,一开始我以为随便找个热门的Vue2兼容库改改就行,结果踩了不少坑:要么是Vue3版本还在早期测试不稳定,要么是数学公式、图片上传这些插件生态崩得离谱,要么是文档全是英文且更新慢,折腾了三天,最终选了一套组合拳,不仅完美覆盖所有需求,还能自由定制界面和功能,现在后台上线快一个月了,朋友连说好用,今天就把选库的逻辑、踩过的雷、快速搭建的步骤,还有进阶自定义的小技巧全讲清楚,保证不管是刚入门Vue3的小白,还是要快速迭代功能的开发者都能用得上。

先搞懂:你的项目到底需要什么样的Vue3 Markdown编辑器?

别上来就搜“Vue3 Markdown编辑器排行榜”然后选第一,每个开源库的定位不一样,盲目跟风只会浪费时间,先花5分钟列个需求清单,

  1. 核心能力边界:是只需要预览渲染静态Markdown就行,还是要带可拖拽编辑、大纲导航、撤销重做的编辑器?
  2. 技术栈适配度:项目是用Vue3 Options API还是Composition API?有没有搭配Tailwind CSS、Element Plus这些UI库?要不要支持TypeScript?
  3. 插件生态完整性:要不要数学公式(KaTeX/MathJax)、流程图(Mermaid)、代码高亮(Prism/Shiki)、图片上传(本地/OSS)、一键导出这些附加功能?
  4. 可定制性要求:是直接拿UI来用就好,还是要完全重写工具栏、编辑器布局,甚至修改Markdown解析规则?
  5. 性能与体积:是追求极致轻量(比如只渲染核心语法,体积几KB),还是功能全一点、体积稍大也没关系?

根据这几个维度,我把目前常用且稳定的Vue3 Markdown开源库分成了三类,大家可以对号入座:

第一类:轻量无UI,专注解析与渲染

适合只需要展示Markdown内容,或者想要100%自定义编辑器UI的开发者,核心就是用底层的Markdown解析库,搭配Vue3的组件化能力自己拼。

常用库有哪些?

  1. Marked:解析速度最快的库之一,体积小(压缩后不到20KB),支持HTML标签嵌入,但代码高亮、数学公式这些都需要自己加插件。
  2. Remark + Rehype:属于Unified生态的工具链,完全基于插件体系,功能可扩展性极强,但学习曲线稍微陡一点,体积比Marked大一些(核心库加基础插件大概30-50KB)。

第二类:带基础UI,开箱即用但可扩展

适合大多数普通开发者,核心需求是“快速上线+按需加小功能”,这些库已经封装好了基础的编辑器界面、实时预览、撤销重做,只需要引入自己需要的插件就行。

常用库有哪些?

  1. Vditor:这个是我朋友那个项目最终用的解析渲染底层的核心备选之一?不,备选是Editor.md,不过Vditor更棒!它是专门为中文场景优化的,文档全中文,功能超级全:自带大纲导航、任务列表、表情选择、代码高亮(支持Prism和Shiki)、数学公式(KaTeX)、流程图(Mermaid)、本地图片拖拽上传、一键导出Markdown/HTML/PDF,而且支持Vue2/Vue3、React、原生JS,甚至小程序!可定制性也不错,可以通过配置项隐藏不需要的工具栏按钮,或者自己重写某个工具栏组件,体积的话,完整版压缩后大概1.2MB,如果只加载核心语法和基础插件,大概300KB,完全可以接受。
  2. Vue-Markdown-It-Vite:基于Markdown-It封装的Vue3组件,带一个极简的实时预览编辑器(分屏式),支持TypeScript,可通过Markdown-It的插件生态扩展功能,比如代码高亮、数学公式、流程图,文档全英文,但不算复杂,适合习惯Markdown-It的开发者,体积比Vditor小很多,完整版大概200KB左右。
  3. Milkdown:这个库很有意思,采用了“无头UI”+“编辑器框架”的思路,核心库不带任何UI组件,所有界面元素(包括工具栏、编辑器、预览区)都需要自己写或者用官方提供的UI插件包,可定制性几乎是无限的,适合要做和现有项目UI风格完全统一的编辑器,它基于ProseMirror和Remark,插件生态也比较丰富,支持TypeScript,体积大概500KB左右(核心库加基础UI插件),不过学习曲线稍微比Vditor和Vue-Markdown-It-Vite陡一点,因为要理解它的“Node/Schema/Mark”模型。

第三类:功能极全,类似Typora所见即所得(WYSIWYG)

适合对编辑体验要求极高的项目,比如笔记类应用、协作文档类应用,这些库已经完全封装了WYSIWYG的编辑逻辑,用户可以像编辑Word一样编辑Markdown,不需要切换预览区。

常用库有哪些?

  1. Bytemd:字节跳动开源的WYSIWYG Markdown编辑器,支持Vue2/Vue3、React、原生JS,文档全中文,功能和Typora很像:实时预览、大纲导航、代码高亮(支持Prism和Shiki)、数学公式(KaTeX)、流程图(Mermaid)、本地图片拖拽上传、一键导出,还支持Vue3的Composition API和TypeScript,可定制性也不错,可以通过配置项调整UI,或者自己写插件,体积大概1MB左右。
  2. Quill + Markdown Shortcuts:Quill本身是一个富文本编辑器,不是专门的Markdown编辑器,但可以通过Markdown Shortcuts插件实现Markdown语法的快捷输入,然后通过Quill的Delta格式转成Markdown输出,适合已经在用Quill做富文本编辑,又想加一点Markdown功能的项目。

选对了库,怎么快速搭建一个Vue3+Vite+Vditor的Markdown编辑器?

刚才说了,朋友的项目最终用的是Vditor,因为它的中文文档、中文社区支持(虽然不算特别大,但有问题基本能在GitHub Issues或者Gitee Issues里找到答案)、功能完整性、开箱即用的体验都是最好的,接下来就以Vditor为例,给大家演示从0到1快速搭建一个带实时预览、代码高亮、数学公式、本地图片上传、一键导出的Markdown编辑器,代码量非常少,大概10分钟就能搞定。

第一步:创建Vue3+Vite项目

如果已经有项目了,这一步可以跳过,打开终端,输入以下命令:

# 或者使用yarn
yarn create vite vue3-vditor-demo --template vue-ts
# 或者使用pnpm
pnpm create vite vue3-vditor-demo --template vue-ts
# 进入项目目录
cd vue3-vditor-demo
# 安装依赖
npm install
# 或者yarn
yarn install
# 或者pnpm
pnpm install

这里我选的是TypeScript模板,因为TypeScript能提供更好的类型提示和代码维护性,不过用JavaScript模板也是一样的,只是不需要处理类型定义的问题。

第二步:安装Vditor依赖

打开终端,输入以下命令:

npm install vditor
# 或者yarn
yarn add vditor
# 或者pnpm
pnpm add vditor

Vditor的依赖非常少,只有几个核心的工具库,安装起来很快。

第三步:在Vue3组件中引入Vditor

打开src/components目录,创建一个新的组件文件VditorEditor.vue,然后输入以下代码:

<template>
  <div class="vditor-container">
    <!-- Vditor会自动挂载到这个div上 -->
    <div ref="vditorRef"></div>
    <!-- 一键导出按钮 -->
    <div class="export-buttons">
      <button @click="exportMarkdown">导出Markdown</button>
      <button @click="exportHtml">导出HTML</button>
    </div>
  </div>
</template>
<script setup lang="ts">
import { onMounted, ref, onBeforeUnmount } from 'vue'
import Vditor from 'vditor'
import 'vditor/dist/index.css'
// 定义Vditor实例的引用
const vditorRef = ref<HTMLDivElement>()
// 定义Vditor实例
let vditor: Vditor | null = null
onMounted(() => {
  if (!vditorRef.value) return
  // 初始化Vditor
  vditor = new Vditor(vditorRef.value, {
    // 配置Vditor的高度
    height: 600,
    // 配置Vditor的模式,这里用的是分屏模式(wysiwyg是所见即所得,ir是即时渲染)
    mode: 'sv',
    // 配置Vditor的缓存,这里关闭了本地缓存,避免刷新页面后内容残留
    cache: {
      enable: false
    },
    // 配置Vditor的预览,开启了数学公式、流程图、代码高亮
    preview: {
      math: {
        enable: true,
        engine: 'KaTeX'
      },
      mermaid: {
        enable: true
      },
      hljs: {
        enable: true,
        style: 'github'
      }
    },
    // 配置Vditor的工具栏,这里只保留了常用的按钮
    toolbar: [
      'emoji',
      'headings',
      'bold',
      'italic',
      'strike',
      'link',
      '|',
      'list',
      'ordered-list',
      'check',
      'outdent',
      'indent',
      '|',
      'quote',
      'line',
      'code',
      'inline-code',
      'insert-before',
      'insert-after',
      '|',
      'upload',
      'record',
      'table',
      'math',
      'mermaid',
      '|',
      'undo',
      'redo',
      '|',
      'fullscreen',
      'edit-mode',
      'content-theme',
      'code-theme',
      'preview',
      'info',
      'help'
    ],
    // 配置Vditor的图片上传,这里用的是本地临时上传,实际项目中要换成OSS上传
    upload: {
      accept: 'image/*',
      multiple: true,
      fieldName: 'file',
      max: 10 * 1024 * 1024,
      url: '/api/upload',
      // 本地临时上传的模拟函数
      handler(files: File[]) {
        return new Promise((resolve) => {
          const urls: string[] = []
          files.forEach((file) => {
            const reader = new FileReader()
            reader.onload = (e) => {
              if (e.target?.result) {
                urls.push(e.target.result as string)
                if (urls.length === files.length) {
                  // 这里需要注意,Vditor的upload handler返回的Promise的resolve参数格式是{ data: { errFiles: [], succMap: { [filename: string]: url } } }
                  const succMap: Record<string, string> = {}
                  files.forEach((file, index) => {
                    succMap[file.name] = urls[index]
                  })
                  resolve({
                    data: {
                      errFiles: [],
                      succMap
                    }
                  })
                }
              }
            }
            reader.readAsDataURL(file)
          })
        })
      }
    }
  })
})
// 导出Markdown
const exportMarkdown = () => {
  if (!vditor) return
  // 获取编辑器的Markdown内容
  const content = vditor.getValue()
  // 创建一个Blob对象
  const blob = new Blob([content], { type: 'text/markdown' })
  // 创建一个a标签
  const a = document.createElement('a')
  // 设置a标签的href
  a.href = URL.createObjectURL(blob)
  // 设置a标签的下载文件名
  a.download = 'document.md'
  // 点击a标签
  a.click()
  // 释放URL对象
  URL.revokeObjectURL(a.href)
}
// 导出HTML
const exportHtml = () => {
  if (!vditor) return
  // 获取编辑器的HTML内容
  const content = vditor.getHTML()
  // 生成一个完整的HTML文档
  const fullHtml = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">Document</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vditor/dist/index.css">
</head>
<body>
  <div class="vditor-reset">${content}</div>
</body>
</html>
  `
  // 创建一个Blob对象
  const blob = new Blob([fullHtml], { type: 'text/html' })
  // 创建一个a标签
  const a = document.createElement('a')
  // 设置a标签的href
  a.href = URL.createObjectURL(blob)
  // 设置a标签的下载文件名
  a.download = 'document.html'
  // 点击a标签
  a.click()
  // 释放URL对象
  URL.revokeObjectURL(a.href)
}
onBeforeUnmount(() => {
  // 组件卸载前销毁Vditor实例,避免内存泄漏
  vditor?.destroy()
})
</script>
<style scoped>
.vditor-container {
  width: 100%;
  max-width: 1200px;
  margin: 20px auto;
}
.export-buttons {
  margin-top: 20px;
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}
.export-buttons button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  background-color: #409eff;
  color: white;
  cursor: pointer;
  font-size: 14px;
}
.export-buttons button:hover {
  background-color: #66b1ff;
}
</style>

这段代码虽然看起来有点长,但大部分都是Vditor的配置项,非常容易理解,我已经加了详细的注释,大家可以根据自己的需求修改配置项,比如调整高度、模式、缓存、预览、工具栏、图片上传等等。

第四步:在App.vue中引入VditorEditor组件

打开src/App.vue,替换成以下代码:

<template>
  <div class="app-container">
    <h1>Vue3+Vite+Vditor Markdown编辑器演示</h1>
    <VditorEditor />
  </div>
</template>
<script setup lang="ts">
import VditorEditor from './components/VditorEditor.vue'
</script>
<style scoped>
.app-container {
  width: 100%;
  min-height: 100vh;
  background-color: #f5f7fa;
  padding-bottom: 40px;
}
h1 {
  text-align: center;
  padding-top: 40px;
  color: #333;
  font-size: 28px;
}
</style>

第五步:启动项目测试

打开终端,输入以下命令:

npm run dev
# 或者yarn
yarn dev
# 或者pnpm
pnpm dev

然后在浏览器中打开终端输出的地址(一般是http://localhost:5173/),你就会看到一个功能完整的Markdown编辑器了!可以试试输入一些Markdown语法,比如标题、列表、代码块、数学公式、流程图,也可以试试拖拽本地图片上传,或者点击一键导出按钮导出Markdown/HTML文件。

进阶:怎么自定义Vditor的界面和功能?

刚才的演示只是用了Vditor的基础配置,如果你想要更符合自己项目UI风格的编辑器,或者想要加一些特殊功能,比如支持自定义表情、支持协作编辑、支持保存到数据库等等,Vditor也完全能满足你的需求,这里给大家讲几个常用的进阶技巧:

完全重写Vditor的工具栏

Vditor允许你通过配置项toolbar传入一个自定义的数组,数组中的元素可以是字符串(对应Vditor内置的工具栏按钮),也可以是一个对象(对应自定义的工具栏按钮),你想要加一个“保存到数据库”的按钮,可以这样修改配置项:

toolbar: [
  // 内置按钮
  'emoji',
  'headings',
  'bold',
  // 自定义按钮
  {
    name: 'save',
    tipPosition: 'n',
    tip: '保存到数据库',
    className: 'vditor-toolbar-save',
    icon: '<svg t="1699999999999" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12345"><path d="M512 64a448 448 0 1 0 448 448A448 448 0 0 0 512 64z m0 832a384 384 0 1 1 384-384 384 384 0 0 1-384 384z" fill="#ffffff"></path><path d="M512 448a64 64 0 0 0-64 64v128a64 64 0 0 0 128 0V512a64 64 0 0 0-64-64z" fill="#ffffff"></path><path d="M512 320a32 32 0 1 0 32 32 32 32 0 0 0-32-32z" fill="#ffffff"></path></svg>',
    click() {
      if (!vditor) return
      const content = vditor.getValue()
      // 这里写保存到数据库的逻辑
      console.log('保存到数据库的内容:', content)
      alert('保存成功!')
    }
  },
  // 其他内置按钮
  '|',
  'undo',
  'redo'
]

然后在style标签中加一点自定义按钮的样式:

.vditor-toolbar-save {
  background-color: #67c23a !important;
}
.vditor-toolbar-save:hover {
  background-color: #85ce61 !important;
}

修改Vditor的主题

Vditor自带了几个编辑器主题和代码高亮主题,你可以通过配置项themepreview.hljs.style来修改,编辑器主题可以选classicdarkant-design,代码高亮主题可以选githubatom-one-darkdraculamonokai等等,如果你想要完全自定义主题,可以参考Vditor的CSS源码,自己写一套CSS文件,然后引入到项目中。

监听Vditor的事件

Vditor提供了很多事件,比如input时触发)、focus(编辑器获得焦点时触发)、blur(编辑器失去焦点时触发)、keydown(按下键盘时触发)等等,你可以通过配置项afterinputfocusblurkeydown来监听这些事件,你想要在输入内容时自动保存草稿到本地存储,可以这样修改配置项:

input() {
  if (!vditor) return
  const content = vditor.getValue()
  localStorage.setItem('vditor-demo-draft', content)
}

然后在初始化Vditor的时候,从本地存储中读取草稿:

onMounted(() => {
  if (!vditorRef.value) return
  // 从本地存储中读取草稿
  const draft = localStorage.getItem('vditor-demo-draft') || ''
  vditor = new Vditor(vditorRef.value, {
    // 其他配置项
    value: draft,
    // 其他配置项
  })
})

踩过的雷,帮你避坑

刚才也说了,一开始我踩了不少坑,现在把这些雷总结出来,帮大家少走弯路:

  1. 不要用Vue2的兼容库强行改Vue3:比如Editor.md,虽然有Vue2的封装,但Vue3的版本非常老,而且数学公式、图片上传这些插件都不兼容,最后我花了两天时间才放弃。
  2. 注意Vditor的版本号:Vditor的更新速度比较快,有时候新版本会有一些小bug,建议大家用稳定版本,比如目前(2024年11月)的稳定版本是10.9
  3. 本地临时上传的图片刷新页面后会消失:这个是正常的,因为本地临时上传的图片是用Base64编码的,没有保存到服务器,实际项目中一定要换成OSS上传,比如阿里云OSS、腾讯云COS、七牛云OSS等等。
  4. 导出PDF功能需要额外的依赖:Vditor的导出PDF功能是基于jsPDF和html2canvas实现的,如果你需要这个功能,要额外安装这两个依赖:
    npm install jspdf html2canvas

    然后在组件中引入:

    import jsPDF from 'jspdf'
    import html2canvas from 'html2canvas'

    最后写导出PDF的函数。

  5. Milkdown的学习曲线稍微陡一点:如果你是第一次接触无头UI和ProseMirror,可能需要花一点时间学习它的“Node/Schema/Mark”模型,不过学会之后,可定制性真的是无限的。

今天给大家讲了Vue3 Markdown编辑器的选库逻辑、踩过的雷、快速搭建Vditor的步骤,还有几个常用的进阶技巧,如果你只是需要快速上线一个带基础功能的Markdown编辑器,选Vditor肯定没错;如果你想要100%自定义编辑器UI,选Remark+Rehype或者Milkdown;如果你想要类似Typora的WYSIWYG体验,选Bytemd,希望这篇文章能帮到大家,如果有什么问题,欢迎在评论区留言讨论!

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门