vite自定义插件
当我们使用vite的时候,其实也是一个把现有代码做编译,然后运行的过程。
我们可以利用vite的plugin自定义一些插件。在指定的钩子里面实现自己转化的逻辑。
autoPlugin自动导入包
逻辑
- 在vite的plugins加入处理的逻辑
- 判断当前文件是否为.vue文件
- 如果是把要输出的文本,通过匹配代码里面是否使用了相关api 如 ref computed 等
- 把匹配的包保存到helpers的集合里面
- 然后遍历helpers生成import语句,并插入到
<script setup>
的后面
新增导入的插件 auto-import
/src/auto-import/index.js
js
const vue3 = ['ref','computed','reactive','onMounted','watchEffect','watch'] // ....
export default function autoImportPlugin() {
return {
name: 'vite-plugin-auto-import', // 必须的,将会在 warning 和 error 中显示
enforce:'pre',
transform(code,id){
// console.log("id:",id) // 当前的页面路径 /Users/xxx/src/App.vue
// console.log("code:",code) //具体的页面代码 code: <template> <div @click="add"> ...
let vueReg = /\.vue$/
if(vueReg.test(id)){// 如果是vue文件 才处理
const helpers = new Set()
vue3.forEach(api=>{
const reg = new RegExp(api+"(.*)")
if(reg.test(code)){//遍历匹配的包
helpers.add(api)// 符合条件 导入集合
}
})
return code.replace('<script setup>',`<script setup>
import {${[...helpers].join(',')}} from 'vue' //自动导入
`)
}
return code
}
}
}
在vite.config.ts引入
vite.config.ts
js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import autoImportPlugin from './src/auto-import/index.js'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
autoImportPlugin() // 加入自定义插件
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
新增测试页面,ref不使用导入的包
js
<template>
--{{val}}--
</template>
<script setup>
const val = ref("oooo")
</script>
正常显示内容
页面输出代码内容, 可以看到多了 import {ref} from "/node_modules/.vite/deps/vue.js?v=ff90ac8e" //自动导入
这一行
js
import { createHotContext as __vite__createHotContext } from "/@vite/client";
import.meta.hot = __vite__createHotContext("/src/App.vue");
import {ref} from "/node_modules/.vite/deps/vue.js?v=ff90ac8e" //自动导入
const _sfc_main = {
__name: 'App',
setup(__props, { expose: __expose }) {
__expose();
const val = ref("oooo")
const __returned__ = { val, ref }
Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })
return __returned__
}
}
import { toDisplayString as _toDisplayString } from "/node_modules/.vite/deps/vue.js?v=ff90ac8e"
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return " --" + _toDisplayString($setup.val) + "-- "
}
_sfc_main.__hmrId = "7a7a37b1"
typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)
import.meta.hot.accept(mod => {
if (!mod) return
const { default: updated, _rerender_only } = mod
if (_rerender_only) {
__VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
} else {
__VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
}
})
import _export_sfc from "/@id/__x00__plugin-vue:export-helper"
export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__file',"/Users/jason.yang/Desktop/my-workspace/github/mini-vue3/src/App.vue"]])
trycatch自动导入
一般在写异步回调的时候,我们会使用async await 来简化写法,但需要加入 try catch 代码会变得复杂,而且繁琐。
我们在main.js添加 如下代码
js
import { createApp } from "vue";
import App from './App.vue'
createApp(App)
.mount('#app')
function createPromiseError(msg) {
return new Promise((resolve,reject) => {
setTimeout(() => {
reject(msg)
}, 2000);
})
}
async function testErrorFn() {
try {
await createPromiseError("网络异常")
} catch (e) {
console.log("error:" + e)
}
}
testErrorFn()
我们希望身省略掉 try/catch的代码,即代码只需要这样写
js
async function testErrorFn() {
await createPromiseError("网络异常")
}
实现工具 babel
由于解析代码比较复杂,我们可以使用成熟的库,babel
- @babel/parser 转化成AST
- @babel/traverse 遍历AST,用于优化AST使用
- @babel/types 类型判断
- @babel/core AST生成代码
实现逻辑
- 使用@babel/parser的 parse转化成ast
- 使用@bable/traverse de travers方法遍历ast
- 遍历同时使用 AwaitExpression识别是 await的代码
- 使用@bable/types的tryStatement方法插入 trycatch
- 最后调用@babel/core 的transformFormAstSnyc把AST转化成代码并返回给vite
新增一个插件vite-plugin-auto-try-catch
src/auto-try/index.js
js
import { parse } from '@babel/parser'
import traverse from '@babel/traverse'
import {
isTryStatement,
tryStatement,
isBlockStatement,
catchClause,
identifier,
blockStatement,
} from '@babel/types'
import { transformFromAstSync } from '@babel/core'
const catchStatement = parse(`
console.error('error:'+err)
console.log('https://www.google.com/search?q=+'+encodeURI(err)) // 同时输出谷歌查询
`).program.body
export default function autoImportPlugin() {
return {
name: 'vite-plugin-auto-tyrcatch', //
enforce:'pre',
transform(code,id){
let fileReg = /\.js$/
if(fileReg.test(id)){ // 只过滤处理js的代码
const ast = parse(code, {
sourceType: 'module'
})
// console.log(ast)
traverse(ast, {
AwaitExpression(path){
if (path.findParent((path) => isTryStatement(path.node))) { // 如果有try了 直接返回
return
}
const blockParentPath = path.findParent((path) => isBlockStatement(path.node)) //isBlockStatement是否函数体
const tryCatchAst = tryStatement(
blockParentPath.node,
// ast中新增try的ast
catchClause(
identifier('err'),
blockStatement(catchStatement),
)
)
blockParentPath.replaceWithMultiple([tryCatchAst]) // 使用有try的ast替换之前的ast
}
})
// 生成代码,generate
code = transformFromAstSync(ast,"",{
configFile:false
}).code
return code
}
return code
}
}
}
vite.config.ts
js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import autoImportPlugin from './src/auto-tyrcatch/index.js'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
autoImportPlugin() // 加入自定义插件
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
main.js 输出的代码
js
import {createApp} from "/node_modules/.vite/deps/vue.js?v=197a5af9";
import App from "/src/App.vue";
createApp(App).mount('#app');
function createPromiseError(msg) {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(msg);
}
, 2000);
}
);
}
async function testErrorFn() {
try {
await createPromiseError("网络异常");
} catch (err) {
console.error('error:' + err);
console.log('https://www.google.com/search?q=+' + encodeURI(err));
// 同时输出谷歌查询
}
}
testErrorFn();
控制台输出
扩展思考
- elementui plus 自动引入原理