Vite 学习(四) - vite 插件预学习

上一小节我们对 rollupesbuild 的使用有了基本的了解,了解了二者主要的 hook 使用。vite 插件需要兼容 rollupesbuild 的插件机制,虽然 vite 兼容大部分 rollup 插件,但不是所有钩子都支持,本小节介绍下 vite 中的钩子及插件开发流程。

插件命名

对于 rollup 插件的命名有两种,一种是 rollup-plugin-xxx,这种是社区通用的,不是官方团队开发的,一种是 @rollup/xxx,这种是官方的插件,@rollup 代表组织,个人还不能申请。我们自己开发的 vite 插件命名规则也保持 vite-plugin-xxx 格式。

兼容 rollup 的钩子

以下钩子在服务器启动时被调用:(devServer 启动时,就第一次触发)

  • options
  • buildStart

以下钩子会在每个传入模块请求时被调用:

  • resolveId 找到对应的文件
  • load 文件加载源码
  • transform 源码转化

以下钩子在服务器关闭时被调用:

  • buildEnd
  • closeBundle

如果我们想执行 rollup 特有的钩子,可以配置打包阶段

vite.config.js
build: {
  rollupOptions:{
    plugins: [] // 可以执行所有 rollup 的钩子,因为打包用的 rollup
  }
}

注意:modulePased 不会被调用,防止 vite 对代码执行 ast 解析,因为 rollup 处理代码慢,插件执行完传给 esbuild 解析执行,速度效率高,rollup 只负责打包

如果想开发 rollup 插件兼容 vite,需要满足:

  • 没有 modulePased 钩子
  • 打包钩子和输出钩子间没有强耦合, output 阶段的钩子不会执行

vite 独有的钩子

  • config 返回对象,合并到 vite.config.js 中配置中。可以返回两种格式,一种是对象,一种为 promise
  • configResolved 获取最终的 vite 配置,不可修改。
// 跟 plugins 写的顺序有关系,后面的会覆盖  test()  test('pre')
config(userConfig) {
  return new Promise((resolve) => {
    resolve({
      resolve: {
        alias: {
          '@/': enforce || '/src/',
        },
      },
    })
  })
},
config(userConfig) {
  return {
    resolve: {
      alias: {
        '@/': '/src/',
      },
    },
  }
},
configResolved(config) {
  // 真实最终的 config
  console.log(config.resolve, '-------------')
},

Vite 学习(四) - vite 插件预学习_第1张图片

  • configServer devserver 中间件,拿到 server 实例
configureServer(server) {
  console.log(server, '================')
  // 和 express 类似, 添加中间价,中间件最先执行,在 vite 的中间件前执行
  server.middlewares.use((req, res, next) => {
    if(req.url === '/test') {
      res.end('hellp test')
    } else {
      next()
    }
  })
},
    
    
返回一个函数,会在 vite 中间件之后执行
configureServer(server) {
  return () => {
    server.middlewares.use((req, res, next) => {
      if (req.url === '/test') {
        res.end('hellp test')
      } else {
        next()
      }
    })
  }    
},

Vite 学习(四) - vite 插件预学习_第2张图片

  • transformIndexHtml 获取入口的 html 文件,可以对其进行转换操作,动态修改

Vite 学习(四) - vite 插件预学习_第3张图片

  • handleHotUpdate vite 中的热更新处理,打包会被过滤
handleHotUpdate(ctx) {
  console.log(ctx, '==')
  ctx.server.ws.send({ // 通过 ws 发送数据
    type: 'custom',
    event: 'test',
    data: {
      hell0: 'world',
    },
  })
},
// 会被 treeshaking
// 在页面进行热更新监听
if (import.meta.hot) {
  import.meta.hot.on('test', (val) => {
    console.log(val, '-----')
  })
}

vite 插件执行机制

可以通过变量控制 vite 插件执行时机,类似 loader 可以控制执行顺序

  • pre 最快被执行的插件, 在 rollup alias 插件后就被调用, 根据 plugins 顺序执行
  • normal vite 核心插件执行后,build 执行前执行执行
  • post vite build 之后,代码构建执行后执行,例如代码打包大小、时间分析工具
// 插件执行是个函数,传参
plugins: [vue(), vueJsx(), testPlugins('post'),testPlugins(), testPlugins('pre')],

// 插件是个函数
export default (enforce?: 'pre' | 'post') => {
  return {
    name: 'test',
    enforce,
    // 启动就调用
    buildStart() {
      console.log('buildstart', enforce)
    },
    // 解析文件时调用,请求时;没有 return, undefined 意思当前插件没有找到相关文件
    // 后续走到了 vite 核心插件中,
    // resolveId() {
    //   console.log('resolveid', enforce) // 都是 pre
    // },
    load() {
      /**
       * 有 pre 和 post
       * 大部分不会再拆件中使用,内部核心插件用,
       */
      console.log('load', enforce)
    },
  }
}

Vite 学习(四) - vite 插件预学习_第4张图片

hmr 更新机制

我们创建个没框架的 vite 项目,发现触发 render 页面刷新,我们这时去监听 import.meta.hot

// 实现 hmr
// 必须要 导出
export function render() {
  document.querySelector("#app").innerHTML = `

Hello Vi33t99e!

Documentation `; } render(); // 可能不存在 hot, vite build,这段会被 treeshaking if (import.meta.hot) { // 文件可以接受 自己的 热更新,内容改变就会热重载 import.meta.hot.accept(); // 只针对第一个参数中依赖的文件改变,才会热更新 import.meta.hot.accept(['style.css'], (newM) => { newM.render() // 可以手动触发更新 }) // 只有依赖的文件变了才更新 // import.meta.hot.accept((newModule) => { // newModule.render() // }); }

这里 vite 热更新存在一个小问题,就是热更新前的逻辑会存在,我们需要手动关闭

// 这里的 timer 会一直存在,热更新完后会存在多个
let timer = setInterval(() => {
  console.log(8);
}, 1000);
render();

webpack 中的热更新做的是代理,删掉旧的模块对象,用新的,所以不会有代码冲突,类似:
__webpack__module__renderA = new Proxy()vite 是直接执行 render,但是旧的没有删除,会一直保留,我们需要监听 dispose 方法:

销毁时触发
import.meta.hot.dispose(() => {
  // 删除已有的
  if(timer) clearInterval(timer)
})

还有其他几种热更新钩子:

import.meta.hot.invalidate() // 强制 accept module 之后重新加载应用,页面刷新

// 热更新数据缓存,避免每次热更新重新开始
let index = import.meta.hot.data.cache.getIndex() || 0
import.meta.hot.data.cache = {
  getIndex() {
    return index
  }
}

本节对 vite 中插件钩子和热更新操作做了介绍,和 rollup 开发的不同及注意事项,下一节我们开始实现一个 vite 插件,具体了解每个钩子的使用,如果有问题欢迎留言,谢谢阅读!

你可能感兴趣的:(与前端沾边,前端,webpack,javascript)