背景:
在编写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格式的字符串并导出
- 安装
marked
pnpm i marked
pnpm i @types/marked
- 使用
// 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
}
- 配置tsconfig.json文件
"include": ["src/*","*.d.ts"],
最后
以前都只是了解vite的常见的配置。这次通过编写一个自定义插件去更深入的了解vite。在编写过程中难免磕磕碰碰但还是通过翻阅文档、博客、浏览相关源码完成了这个功能。特别是当用自己的代码来扩展或改变Vue的功能实现想要的效果让我无比兴奋。写plugin插件是一种有趣而有挑战性的学习过程。通过此次编写发现ts的功能更强大了,比如:当我知道能够configureServer这个钩子做一些事情的时候,文档可能写的不是很详细。我会根据configureServer的类型推断查找他的属性看有无可以用到的东西,他在过程中给了我很多的提示指引。