十八.vite之插件实现篇
前言
在上一篇中我们简单了解了一下Vite插件的基本概念和规范,这篇我们开始对vite插件进行实战,对插件钩子的基本用法进行一定的了解。
源代码放在vue3-basic-admin,该项目是一款开源开箱即用的中后台管理系统。基于
Vue3
、Vite
、Element-Plus
、TypeScript
、Pinia
等主流技术开发,内置许多开箱即用的组件,能快速构建中后台管理系统。
准备
如还没看过Vite的插件基本概念,可查看上一篇文章和项目实战项目:
为了方便插件开发,这里就先做简单点,在项目根目录建立build
文件夹,里面存放一些我们自定义的插件。
# build/test.js
export function testPlugin(){
return {
//插件名字
name:'vite-plugin-test',
options(options) {
},
buildStart(options){
console.log(options)
},
}
}
使用插件:
# vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {testPlugin} from "./build/test"
export default defineConfig({
plugins: [vue(),testPlugin()],
})
resolveId钩子
resolveId 钩子可以获取文件路径,在该钩子下可以对文件路径进行重写或其他操作。例如:通过该钩子来实现alias操作。
import path from "path"
export function testPlugin(){
return {
//插件名字
name:'vite-plugin-test',
resolveId(id) {
if (id.startsWith('@')) {
return id.replace('@',path.resolve(process.cwd(), 'src'))
}
},
}
}
load钩子
load钩子可拦截文件读取,模块引入读取操作,例如我想拦截某个组件读取,这样写后,对应的组件里面的内容加载时就会被替换成code的内容。
export function testPlugin(){
return {
//插件名字
name:'vite-plugin-test',
load(id) {
if (id.includes('HelloWorld.vue')) {
return {
code: `<template>1111</template>`,
map: ''
}
}
}
}
}
我们还可以通过resolveId钩子+load钩子去实现加载虚拟模块功能,例如:页面中引入了一个不存在的模块
import test from "virtual:test-modules"
我们可以通过resolveId钩子+load钩子去实现这个虚拟模块。
这里提一下,Rollup定义的规范,如果插件是以
\0
开头,则会跳过解析
const moduleName="virtual:test-modules"
export function testPlugin(){
return {
//插件名字
name:'vite-plugin-test',
resolveId(id) {
if (id === moduleName) {
return `\0${id}`
}
},
load(id) {
if (id===`\0${moduleName}`) {
return {
code: `const data = '虚拟test-modlues常量'; \n export default data; `,
map: ''
}
}
}
}
}
import test from "virtual:test-modules"
console.log(test) //虚拟test-modlues常量
transform钩子
transform钩子可以转换单个模块对应的代码,和load钩子不同的是,他有两个参数,code
和id
,这里的id
和上面一样,这个code
就是他对应的代码。例如我们这里通过这个钩子去实现setup
添加name
功能。
主要实现逻辑:通过 transform钩子拿到后缀为.vue
的文件,然后拖过模板解析工具解析该文件是否存在setup
,如果存在且setup
上存在name
,则给code
再添加一个script
标签,这样即可页面上仅需setup
上添加name
,其他的交给插件去处理。
题外话,这里要用到一个工具:@vue/compiler-sfc,这个工具主要是去编译vue
文件,它对外暴露了:compileTemplate,compileStyle, compileScript,parse等方法,其中 parse 方法主要是解析vue文件,解析出来的文件如下:
然后compileTemplate,compileStyle, compileScript分别是处理template,style和script的工具,将对应的语法转换成浏览器能识别的代码。
export function testPlugin(){
return {
//插件名字
name:'vite-plugin-test',
enforce: "pre",
transform(code,id){
// 过滤文件格式
if (/\.vue$/.test(id) || /\.vue\?.*type=script.*/.test(id)) {
// 解析文件
const { descriptor } = parse(code);
// 判断是否存在setup标签
if (!descriptor.script && descriptor.scriptSetup
&& !descriptor.scriptSetup.attrs?.extendIgnore
) {
const result = compileScript(descriptor, { id });
// 拿到setup上的name属性
const name = result.attrs.name;
const lang = result.attrs.lang;
const inheritAttrs = result.attrs.inheritAttrs;
//如果name存在,则创建一个script标签,然后添加到code上
if (name || inheritAttrs) {
const template = `<script${lang ? ` lang="${lang}"` : ""}>
import { defineComponent } from 'vue'
export default defineComponent({
${name ? `name: "${name}",` : ""}
${inheritAttrs ? `inheritAttrs: ${inheritAttrs !== "false"},` : ""}
})
</script>\n`;
code += template;
}
}
}
return code;
}
}
}
transformIndexHtml钩子
transformIndexHtml钩子主要是操作index.html
文件,它会返回当前的html
文件,这里可以通过该钩子实现一个简单的 修改index.html
的title
的插件。
export function testPlugin({title=''}){
return {
//插件名字
name:'vite-plugin-test',
enforce: "pre",
transformIndexHtml(html){
return html.replace(/<title>(.*?)<\/title>/,`<title>${title}</title>`)
}
}
}
使用:
# vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {testPlugin} from "./build/test"
export default defineConfig({
plugins: [vue(),testPlugin({title:'我是测试插件名字'})],
})
closeBundle钩子
closeBundle 钩子是打包后最后执行的钩子,他和buildEnd的区别是,它是打包代码生成后触发的钩子。通过该钩子我们可以写一个统计打包用时的插件。
import picocolors from "picocolors";
import dayjs from "dayjs";
import duration from 'dayjs/plugin/duration' // 按需加载插件
dayjs.extend(duration) // 使用插件
const { green, blue, bold } = picocolors;
export function testPlugin(){
let config;
let startTime;
let endTime;
return {
//插件名字
name:'vite-plugin-test',
//获取最终vite配置
configResolved(resolvedConfig) {
config = resolvedConfig;
},
//打包开始钩子
buildStart(){
console.log(bold(green(`👏欢迎使用系统,现在正全力为您${config.command === "build" ? "打包" : "编译"}`)));
if (config.command === "build") {
//如果当前是build 环境
startTime = dayjs(new Date());
}
},
closeBundle(){
if (config.command === "build") {
endTime = dayjs(new Date());
console.log(bold(green(`恭喜打包完成🎉(总用时${dayjs.duration(endTime.diff(startTime)).format("mm分ss秒")})`)));
}
}
}
}
总结
以上就是Vite插件的一些简单实战,还是挺有意思的,学会了后可以自己写一些插件,然后发布到npm上,这样vite的生态会越来越强大,如有问题可以评论区留言,大家一起共同进步。
其他文章
转载自:https://juejin.cn/post/7210278786592292920