自制处理markdown的vite4插件

275 阅读4分钟

背景:

在编写UI库(vue3+ts+vite4)时需要展示安装指引的相关内容,打算使用markdown来编写,这时候就需要处理markdown文件。vite并不提供这一功能。借助这个机会去了解vite,翻阅了相关文档打算自己实现一个vite插件。

介绍:

「一个基于浏览器原生 ES imports 的开发服务器。 利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念, 服务器随起随用。 同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。 针对生产环境则可以把同一份代码用 rollup 打包。」

相关概念:

浏览器可以处理 ES模块导入,无法处理其他类型的文件。当需要处理像vue、css这种文件类型该如何处理

  • 开发服务器

在开发环境下: 每次浏览器在代码中发现一个导入,会先通过vite的开发服务器,她会处理任何文件类型再提供给浏览器,解决了浏览器只能处理 ES模块导入的限制。

  • rollup 在生产环境下:对于静态内容,不使用 vite 开发服务器。vite 使用rollup来打包生产代码。

思路:

以开发环境为例:

第一步:在开发服务器处理.md文件,文件内容由markdown转化为html格式的字符串并导出

第二步:创建一个vue文件,在第一步拿到的字符串传入vue文件中通过v-html来渲染即可

步骤

前期准备

创建 src/markdown/intro.md

# 介绍

Wheel UI 是一款基于 Vue 3 和 TypeScript 的 UI 组件库。

这款组件库其实是我为了总结自己在学习 Vue 3 和 TypeScript 的过程中获得的学习经验而写的,全程亲手编写,尽量不采用第三方库,包括你现在看到的这个官网也几乎都是我自己写的。

创建插件:

为了更好的提示,最好标注类型

// plugins/md.ts 
import { Plugin } from 'vite';
export function md():Plugin {
	return {}
}

注册插件

// vite.config.ts
import { defineConfig } from 'vite'
import { md } from './plugins/md'
export default defineConfig({
  plugins: [
    md(),
  ],
  assetsInclude: ['**/*.md'],
})

将markdown格式转化为html格式的字符串并导出

  1. 安装marked
pnpm i marked
pnpm i @types/marked
  1. 使用
// plugins/md.ts  
import { marked } from 'marked'
import { Plugin } from 'vite';

function mdToJs<T extends string>(str:T):T{
  const content = JSON.stringify(marked(str))
  return `export default ${content}`
}

通过翻阅文档,发现configureServer这个钩子适合处理请求的文件

自定义中间件

import path from 'path'
import fs from 'fs'
import { marked } from 'marked'
import { Plugin } from 'vite';

const pathRegex = /\?import$/

export function md():Plugin {

  return {
    name: 'vite-plugin-md',
    configureServer:
        async (server) => {
          server.middlewares.use(async (req, res, next) => {
          	//  过滤.md文件进行处理
            if (req.originalUrl.endsWith('.md?import')) {
            	//  获取*.md文件的路径
              const filePath = path.join(process.cwd(), req.originalUrl.replace(pathRegex,''))
              // 内容转换成string
              const body = mdToJs<string>(fs.readFileSync(filePath).toString())
              //  发送响应
              res
                .writeHead(200, {
                  'Content-Length': Buffer.byteLength(body),
                  'Content-Type': 'application/javascript',
                })
                .end(body)
            }
            else {
              await next()
            }
          })
        },
  }
}

注意configureServer在运行生产版本时不会被调用:) 因此使用rollup提供的钩子transform,这样无论是开发还是生产环境都能被处理

最终代码:

// plugins/md.ts  
import path from 'path'
import fs from 'fs'
import { marked } from 'marked'
import { Plugin } from 'vite';

const fileRegex = /\.md$/

function mdToJs(str){
  const content = JSON.stringify(marked(str))
  return `export default ${content}`
}

export function md():Plugin {

  return {
    name: 'vite-plugin-md',
      transform: (code, id) => { 
      if (fileRegex.test(id)) {
        return {
          code: mdToJs<string>(fs.readFileSync(id).toString()),
          map: null, 
        }
      }
    },
  }
}

现在无论是开发还是生产环境,我们都能将markdown转换成string

将字符串渲染到页面上

安装 github-markdown-css

pnpm i github-markdown-css

引入

// main.ts
import 'github-markdown-css'

创建 src/components/Markdown.vue

<script lang="ts" setup>
const props = defineProps<{
  content: string
}>()
</script>

<template>
  <article
    class="markdown-body"
    v-html="content"
  />
</template>

通过h函数渲染

// src/home.vue
<script lang="ts" setup>
import {h} from 'vue'
import Markdown from './components/Markdown.vue'
import intro from './markdown/intro.md'
const md =(content) => h(Markdown,{content:intro,key:content:intro})
</script>

<template>
  <md />
</template>

报错指引

报错: Cannot find module '@/markdown/intro.md'

1.创建一个.d.ts文件,声明.md文件的类型

declare module '*.md' {
  import type { ComponentOptions } from 'vue'
  const Component: ComponentOptions
  export default Component
}
  1. 配置tsconfig.json文件
"include": ["src/*","*.d.ts"],

最后

以前都只是了解vite的常见的配置。这次通过编写一个自定义插件去更深入的了解vite。在编写过程中难免磕磕碰碰但还是通过翻阅文档、博客、浏览相关源码完成了这个功能。特别是当用自己的代码来扩展或改变Vue的功能实现想要的效果让我无比兴奋。写plugin插件是一种有趣而有挑战性的学习过程。通过此次编写发现ts的功能更强大了,比如:当我知道能够configureServer这个钩子做一些事情的时候,文档可能写的不是很详细。我会根据configureServer的类型推断查找他的属性看有无可以用到的东西,他在过程中给了我很多的提示指引。

参考:medium.com/@axwdev/wri…