上一小节我们对 rollup
和 esbuild
的使用有了基本的了解,了解了二者主要的 hook
使用。vite
插件需要兼容 rollup
和 esbuild
的插件机制,虽然 vite
兼容大部分 rollup
插件,但不是所有钩子都支持,本小节介绍下 vite
中的钩子及插件开发流程。
对于 rollup
插件的命名有两种,一种是 rollup-plugin-xxx
,这种是社区通用的,不是官方团队开发的,一种是 @rollup/xxx
,这种是官方的插件,@rollup
代表组织,个人还不能申请。我们自己开发的 vite
插件命名规则也保持 vite-plugin-xxx
格式。
以下钩子在服务器启动时被调用:(devServer
启动时,就第一次触发)
以下钩子会在每个传入模块请求时被调用:
以下钩子在服务器关闭时被调用:
如果我们想执行 rollup
特有的钩子,可以配置打包阶段
vite.config.js
build: {
rollupOptions:{
plugins: [] // 可以执行所有 rollup 的钩子,因为打包用的 rollup
}
}
注意:
modulePased
不会被调用,防止vite
对代码执行ast
解析,因为rollup
处理代码慢,插件执行完传给esbuild
解析执行,速度效率高,rollup
只负责打包
如果想开发 rollup
插件兼容 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, '-------------')
},
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()
}
})
}
},
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
插件执行时机,类似 loader
可以控制执行顺序
// 插件执行是个函数,传参
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
项目,发现触发 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
插件,具体了解每个钩子的使用,如果有问题欢迎留言,谢谢阅读!