本文是Varlet组件库源码主题阅读系列的第五篇。看完本文,你可以学习如何编写一个Vite插件,支持直接使用md文件作为路由组件。在[BuildingDocumentationSite]()中,我们介绍了路由的动态生成逻辑。里面提到文档是用Markdown格式写的,在路由文件中直接使用md文件作为路由组件:routing就是组件的路由。映射,这个组件显然是指Vue组件。Vue组件是一个包含特定选项的JavaScript对象。我们通常使用Vue单文件进行开发,单文件最终会被编译成一个选项对象。这个作品是@vitejs/plugin-vue,显然这个插件无法处理Markdown文件,所以最终无法生成正确的Vue组件。解决办法是写一个Vite插件,指定在@vitejs/plugin-vue插件之前调用,将.md文件内容转换成Vue单文件格式,然后配置@vitejs/plugin-vue插件让它顺便处理扩展名为.md的文件可以正常处理,因为它已经被转换成Vue单个文件的语法格式。接下来我们从源码的角度来细细的看一下。Vite配置之前的文章详细介绍了启动服务时的Vite配置。这里只看涉及的插件部分://varlet-cli/src/config/vite.config.tsimportvuefrom'@vitejs/plugin-vue'importmdfrom'@varlet/markdown-vite-plugin'exportfunctiongetDevConfig(varletConfig:Record):InlineConfig{return{plugins:[vue({include:[/\.vue$/,/\.md$/],//仅限vue插件默认处理.vue文件,通过这个参数配置,也可以处理.md文件}),md({style:get(varletConfig,'highlight.style')}),//使用md文件转换插件-in,使用插件时可以传入参数]}}markdown-vite-plugin插件代码在varlet-markdown-vite-plugin目录下,Vite插件是接收参数的函数在使用过程中传入,最终返回一个对象。Vite插件扩展了Rollup的接口,并且有一些Vite特有的配置项。配置项基本上有两种,一种是属性,一种是钩子函数。插件的主要逻辑在钩子函数中。Rollup和Vite在构建和编译的各个时刻都提供了hook,插件可以根据功能选择对应的hook。Vite插件文档:插件API。汇总插件文档:插件开发。//varlet-markdown-vite-plugin/index.jsfunctionVarletMarkdownVitePlugin(options){return{name:'varlet-markdown-vite-plugin',//插件名称enforce:'pre',//插件调用顺序//Rollup钩子,转换文件内容transform(source,id){if(!/\.md$/.test(id)){return}try{returnmarkdownToVue(source,options)}catch(e){this.error(e)return''}},//Vitehook热更新asynchandleHotUpdate(ctx){if(!/\.md$/.test(ctx.file))returnconstreadSource=ctx.readctx.read=asyncfunction(){returnmarkdownToVue(awaitreadSource(),options)}},}}module.exports=VarletMarkdownVitePlugin上面是这个插件的函数,返回一个对象,name属性是插件的名字,就是必需的,用于错误输出的信息和提示;enforce用于指定hooks的调用顺序:没有指定vue插件,所以md插件会在它之前被调用,以确保.md文件的内容已经转换为它。接下来配置了两个钩子函数,我们详细看一下。md文件内容转换transform是Rollup在构建阶段提供的一个hook。您可以在此挂钩中转换文件的内容。首先判断文件后缀是否为.md。如果不是,请不要处理它。如果是,调用markdownToVue方法://varlet-markdown-vite-plugin/index.jsfunctionmarkdownToVue(source,options){const{source:vueSource,imports,components}=extractComponents(source)//...}supports导入md文件中的vue组件源,即文件内容,进来先调用extractComponents方法。这个方法是做什么用的?用于支持在md文件中引入Vue组件,比如布局组件中Row组件的文档:引入了Responsive.vue组件,最终在页面上的渲染效果如下:知道了它的功能,让我们来看看实现://varlet-markdown-vite-plugin/index.jsfunctionextractComponents(source){constcomponentRE=/import(.+)from['"].+['"]/constimportRE=/import.+from['"].+['"]/gconstvueRE=/```vue((.|\r|\n)*?)```/gconstimports=[]constcomponents=[]//替换```vue....```的内容source=source.replace(vueRE,(_,p1)=>{//解析出导入语句列表constpartImports=p1.match(importRE)constpartComponents=partImports?.map((importer)=>{//移除换行符importer=importer.replace(/(\n|\r)/g,'')//解析导入的组件名称constcomponent=importer.replace(componentRE,'$1')//收集导入语句和导入的组件!imports.includes(importer)&&imports.push(importer)!components.includes(component)&&components.push(component)//使用组件返回字符串return`<${kebabCase(component)}/>`})返回partComponents?`${partComponents.join('\n')}
`:''})return{imports,components,source,}}拿前面的例子来说,来源是:xxx```vueimportBasicExamplefrom'../example/Responsive.vue'```xxx匹配vueRE,p1是:importBasicExamplefrom'../example/Responsive.vue'使用importRE正则后匹配,可以得到partImports数组:[`importBasicExamplefrom'../example/Responsive.vue'`]遍历这个数组,然后解析出组件为BasicExample,收集i??mport语句和组件名,然后拼接templatestringinto: