vue3复习-源码-编译原理-自定义vite插件

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 自动引入原理

参考

玩转vue3

相关推荐
热忱11281 小时前
elementUI Table组件实现表头吸顶效果
前端·vue.js·elementui
大叔_爱编程2 小时前
wx035基于springboot+vue+uniapp的校园二手交易小程序
vue.js·spring boot·小程序·uni-app·毕业设计·源码·课程设计
zhaocarbon2 小时前
VUE elTree 无子级 隐藏展开图标
前端·javascript·vue.js
匹马夕阳4 小时前
Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制
前端·javascript·vue.js
你熬夜了吗?4 小时前
日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件
前端·vue.js·信息可视化
沈梦研11 小时前
【Vscode】Vscode不能执行vue脚本的原因及解决方法
ide·vue.js·vscode
轻口味12 小时前
Vue.js 组件之间的通信模式
vue.js
fmdpenny15 小时前
Vue3初学之商品的增,删,改功能
开发语言·javascript·vue.js
涔溪15 小时前
有哪些常见的 Vue 错误?
前端·javascript·vue.js
亦黑迷失17 小时前
vue 项目优化之函数式组件
前端·vue.js·性能优化