vite整合了rollup,所以vite的插件可以看成是受限制的rollup插件悲幅,它支持部分rollup的Hooks套鹅。vite也提供僅屬于它自己的Hooks。
命名規(guī)范
- 插件希望在vite和rollup里使用: rollup-plugin-xxx
- 僅在vite使用:vite-plugin-xxx
兼容rollup部分Hooks(僅列舉常用的)
服務啟動時
會兼容rollup的options和buildStart鉤子汰具,只會執(zhí)行一次卓鹿,文件改變觸發(fā)的打包不會再觸發(fā)鉤子與文件更新無關,也符合插件編碼規(guī)范留荔。
每個模塊
為了實現(xiàn)代碼編譯能力吟孙,兼容resolveId、load存谎、transform拔疚、buildEnd。
- resolveId:找到對應文件
- load:加載文件的源碼
- transform:把文件源碼轉換成目標代碼
- buildEnd:代碼編譯完成
服務關閉時
會觸發(fā)closeBundle既荚。
在vite里使用rollup插件
如果需要在vite里使用rollup的插件,vite會用自己的規(guī)范讀取rollup插件的config栋艳,只會執(zhí)行vite里兼容的Hooks恰聘。
常見的場景是,已經(jīng)寫了rollup的插件,不想重寫一個插件來兼容vite了晴叨,有沒有辦法能使我們在vite按照rollup的規(guī)則來使用插件呢凿宾?
vite提供了rollupOptions
配置項,使我們可以在build中配置rollup 插件:
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
// ...
build: {
rollupOptions: {
plugins: []
}
}
})
vite 在 build 的時候其實是執(zhí)行了 rollup 的 build兼蕊。
?? 需要注意的是modulePased鉤子不會被觸發(fā)初厚,vite為了對代碼整體進行ast的解析,在開發(fā)環(huán)境代碼編譯是通過esbuild進行孙技,在執(zhí)行完rollup插件會傳給esbuild來產(chǎn)出最終的代碼产禾,所以modulePased這部分會交給esbuild來完成。
總結:適合在vite使用的rollup插件需要滿足三要素
- 沒有使用 moduleParsed 鉤子
- 打包和輸出之間沒有很強的耦合
- 代碼輸出不過分依賴Hooks的執(zhí)行或使用到的Hooks是vite所兼容的
vite專屬的Hooks
config
在config里返回的對象會被合并到vite的配置當中立即生效牵啦,可以根據(jù)vite當前配置(包含用戶自定義配置)自動生成額外的配置或初始化用戶漏傳的配置亚情,它的返回值有兩種方式,返回對象或Promise哈雏。
export default () => {
return {
name: 'test',
config(userConfig) {
// 方式1直接返回對象
return {
resolve: {
alias: {
'@diy': '/src/diy'
}
}
}
// 方式2在Promise里的resolve返回對象楞件,
return new Promise(resolve => {
resolve({
resolve: {
alias: {
'@diy': '/src/diy'
}
}
})
})
}
}
}
configResolved
所有插件的config執(zhí)行完畢會觸發(fā)這個hook,可以在這個鉤子里拿到最終的配置裳瘪,在這個鉤子不能再修改配置了土浸。
可以將配置存到插件的運行環(huán)境(閉包)中,方便后續(xù)在其它鉤子中讀到它彭羹。
export default () => {
let root = ''
return {
name: 'test',
config(userConfig) {
return { resolve: { alias: { '@diy': '/src/diy' } }
},
configResolved(config) {
root = config.root
console.log(config.resolve)
// { ..., alias: [
// { find: /^\/@vite\//, replacement: [Function: replacement] },
// { find: '@diy', replacement: '/src/diy' }
// ]
// }
}
}
}
configureServer
在這個鉤子里可以獲取server的實例黄伊,增加devServer中間件做一些處理。
// server屬性
interface ViteDevServer {
/**
* 被解析的 Vite 配置對象
*/
config: ResolvedConfig
/**
* 一個 connect 應用實例
* - 可以用于將自定義中間件附加到開發(fā)服務器皆怕。
* - 還可以用作自定義http服務器的處理函數(shù)毅舆。
或作為中間件用于任何 connect 風格的 Node.js 框架。
*
* https://github.com/senchalabs/connect#use-middleware
*/
middlewares: Connect.Server
/**
* 本機 node http 服務器實例
*/
httpServer: http.Server | null
/**
* chokidar 監(jiān)聽器實例
* https://github.com/paulmillr/chokidar#api
*/
watcher: FSWatcher
/**
* web socket 服務器愈腾,帶有 `send(payload)` 方法憋活。
*/
ws: WebSocketServer
/**
* Rollup 插件容器,可以針對給定文件運行插件鉤子虱黄。
*/
pluginContainer: PluginContainer
/**
* 跟蹤導入關系悦即、url 到文件映射和 hmr 狀態(tài)的模塊圖。
*/
moduleGraph: ModuleGraph
/**
* 以代碼方式解析橱乱、加載和轉換 url 并獲取結果
* 而不需要通過 http 請求管道辜梳。
*/
transformRequest(
url: string,
options?: TransformOptions
): Promise<TransformResult | null>
/**
* 應用 Vite 內(nèi)建 HTML 轉換和任意插件 HTML 轉換
*/
transformIndexHtml(url: string, html: string): Promise<string>
/**
* 加載一個給定的 URL 作為 SSR 的實例化模塊
*/
ssrLoadModule(
url: string,
options?: { isolated?: boolean }
): Promise<Record<string, any>>
/**
* 解決 ssr 錯誤堆棧信息
*/
ssrFixStacktrace(e: Error): void
/**
* 啟動服務器
*/
listen(port?: number, isRestart?: boolean): Promise<ViteDevServer>
/**
* 重啟服務器
*
* @param forceOptimize - 強制優(yōu)化器重新大伯啊,和命令行內(nèi)使用 --force 一致
*/
restart(forceOptimize?: boolean): Promise<void>
/**
* 停止服務器
*/
close(): Promise<void>
}
控制添加的中間件執(zhí)行順序:
export default () => {
return {
name: 'test',
configureServer(server) {
// 高優(yōu)先級:直接添加中間件優(yōu)先級很高泳叠,會在vite中間件執(zhí)行之前執(zhí)行
server.middlewares.use((req, res, next) => {
next()
})
// 低優(yōu)先級:在Hook的返回值里添加作瞄,會在vite中間件執(zhí)行之后執(zhí)行
return () => {
server.middlewares.use((req, res, next) => {
next()
})
}
}
}
}
中間件的入?yún)⒑蚭xpress中間件的入?yún)⒑x一樣。
?? 如果需要處理get請求的接口推薦使用高優(yōu)先級的方式危纫,vite的中間件會將頁面發(fā)出的get請求映射到index html里宗挥,輪不到低優(yōu)先級的中間件處理乌庶。
transformIndexHtml
處理入口html文件內(nèi)容的鉤子。
export default () => {
return {
name: 'test',
transformIndexHtml(html) {
console.log(html)
/**
* 控制臺打印index.html文件的內(nèi)容:
* <!DOCTYPE html>
* <html lang="en">
* <head>
* <meta charset="UTF-8" />
* <link rel="icon" href="/favicon.ico" />
* <meta name="viewport" content="width=device-width, initial-scale=1.0" />
* <title>Vite App</title>
* </head>
* <body>
* <div id="app"></div>
* <script type="module" src="/src/main.js"></script>
* </body>
* </html>
*/
}
}
}
handleHotUpdate
熱更新的時候會觸發(fā)的鉤子契耿,鉤子能拿到熱更新模塊的信息瞒大。
export default () => {
return {
name: 'test',
handleHotUpdate(ctx) {
console.log(ctx)
/**
* {
* file: 更新的文件
* timestamp: 更新的時間
* modules: 更新的module [
* ModuleNode: modulePased處理的內(nèi)容
* ]
* ...
* }
*/
}
}
}
熱更新后,通知客戶端處理熱更新:
export default () => {
return {
name: 'test',
handleHotUpdate(ctx) {
ctx.server.ws.send({
type: 'custom',
event: 'handleHotUpdate',
data: {
msg: 'hello client, hot updated',
file: ctx.file,
timestamp: ctx.timestamp
}
})
}
}
}
// app.jsx
if (import.meta.hot) {
import.meta.hot.on('handleHotUpdate', val => {
console.log(val)
})
}
// 觸發(fā)熱更新搪桂,控制臺打印
/**
* {
* msg: 'hello client, hot updated',
* file: '/.../src/App.jsx', (絕對路徑)
* timestamp: 1660728266941
* }
*/
vite插件執(zhí)行時機
相對于rollup插件是按照配置數(shù)組順序執(zhí)行vite的執(zhí)行順序更加靈活透敌,按照數(shù)組執(zhí)行的前提下支持3階段配置。
pre
首批被執(zhí)行的插件踢械,會在@rollup/plugin-alias插件執(zhí)行之后執(zhí)行酗电。normal(默認值)
第二批配執(zhí)行的插件,會在vite的build階段之前被執(zhí)行裸燎,可以根據(jù)配置判斷是否需要處理當前文件的代碼顾瞻。post
會在vite的build階段之后被執(zhí)行,進行代碼構建方面的工作(minimize德绿、代碼分析...)荷荤。
// vite-plugin-test.js
export default (enforce: 'pre' | 'post') => {
return {
name: 'test',
enforce,
buildStart() {
console.log('buildStart:', enforce);
}
}
}
// vite.config.js
import { defineConfig } from 'vite'
import testPlugin from './plugins/test-plugin'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [testPlugin('post'), testPlugin(), testPlugin('pre')]
})
// 執(zhí)行vite
#sh: vite
/**
* 終端打印
* buildStart: pre
* buildStart: undefined
* buildStart: post
*/
可以看到組件的執(zhí)行機制是先按照優(yōu)先級,再按順序移稳。