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的个人博客网站的设计与实现/基于node.js的博客系统的设计与实现#express框架、vscode
vue.js·node.js·express
定栓2 小时前
vue3入门-v-model、ref和reactive讲解
前端·javascript·vue.js
LIUENG3 小时前
Vue3 响应式原理
前端·vue.js
HYI3 小时前
小公司前端多分支测试太痛苦?我自己写了个轻量 CLI
nginx·vite
wycode4 小时前
Vue2实践(3)之用component做一个动态表单(二)
前端·javascript·vue.js
wycode5 小时前
Vue2实践(2)之用component做一个动态表单(一)
前端·javascript·vue.js
第七种黄昏5 小时前
Vue3 中的 ref、模板引用和 defineExpose 详解
前端·javascript·vue.js
pepedd8646 小时前
还在开发vue2老项目吗?本文带你梳理vue版本区别
前端·vue.js·trae
前端缘梦6 小时前
深入理解 Vue 中的虚拟 DOM:原理与实战价值
前端·vue.js·面试
HWL56796 小时前
pnpm(Performant npm)的安装
前端·vue.js·npm·node.js