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

相关推荐
娃哈哈哈哈呀2 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
真滴book理喻4 小时前
Vue(四)
前端·javascript·vue.js
不是鱼6 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js
开心工作室_kaic6 小时前
springboot476基于vue篮球联盟管理系统(论文+源码)_kaic
前端·javascript·vue.js
川石教育6 小时前
Vue前端开发-缓存优化
前端·javascript·vue.js·缓存·前端框架·vue·数据缓存
搏博6 小时前
使用Vue创建前后端分离项目的过程(前端部分)
前端·javascript·vue.js
isSamle6 小时前
使用Vue+Django开发的旅游路书应用
前端·vue.js·django
ss2737 小时前
基于Springboot + vue实现的汽车资讯网站
vue.js·spring boot·后端
武昌库里写JAVA8 小时前
浅谈怎样系统的准备前端面试
数据结构·vue.js·spring boot·算法·课程设计
TttHhhYy8 小时前
uniapp+vue开发app,蓝牙连接,蓝牙接收文件保存到手机特定文件夹,从手机特定目录(可自定义),读取文件内容,这篇首先说如何读取,手机目录如何寻找
开发语言·前端·javascript·vue.js·uni-app