背景
Vite是一個構(gòu)建工具,旨在為現(xiàn)代web項(xiàng)目提供更快辽旋、更精簡的開發(fā)體驗(yàn)珠增。
vite主要分兩個模塊:
- 通過native ES module實(shí)現(xiàn)的本地開發(fā)服務(wù),可以提供極快的熱更新服務(wù)。
- 通過rollup對生產(chǎn)環(huán)境打包剂跟,可以極大地優(yōu)化打包生產(chǎn)環(huán)境的代碼术吝。
vite 和 webpack 開發(fā)環(huán)境最大的區(qū)別就是vite 在開發(fā)環(huán)境拋棄了打包這一個理念阿逃,直接在開發(fā)環(huán)境使用Javascript module,減少打包帶來的時間損耗,極大地方便了本地開發(fā)。
對于vite的學(xué)習(xí)窗悯,我主要總結(jié)了以下四個模塊進(jìn)行總結(jié)。
- 模塊路徑解析
- 不同格式文件處理
- 熱更新
- 預(yù)打包
模塊路徑解析
對于一個native es module服務(wù)系統(tǒng)而言偷拔,不同模塊的路徑解析非常重要蒋院,這里面有以下幾個問題:
- node_modules等特殊路徑內(nèi)容如何處理
- 相對路徑不便于記錄唯一文件路徑
vite的解決方式:
- 將裸模塊(node_modules)做轉(zhuǎn)換 "vue.js" --> "/@modules/vue.js"
- 將相對路徑轉(zhuǎn)為絕對路徑,便于vite統(tǒng)一文件路徑識別莲绰。 import '../../a.js' --> '/src/a.js'
不同格式文件處理
通過不同格式的處理欺旧,我們可以理解類似于webpack loader對于不同文件是如何處理的, 了解vite工作機(jī)制。
- vue
- css
- json
- html
對于不同格式的文件蛤签,vite統(tǒng)一都處理成javascript格式辞友,在返回的response 中添加
Content-Type: application/javascript; charset=utf-8
vue:
vue 的組件是一個單文件組件的機(jī)制。一個vue組件的定義基本分三個部分:
<template></template>
<script></script>
<style></style>
編譯器會將一個vue組件的三部分分別處理。在vite中称龙,請求一個組件的資源:
Helloworld.vue script 邏輯部分編譯:
// 此文件可以理解為一個組件的script邏輯部分
import string from '/src/string.js'
const __script = {
name: 'HelloWorld',
props: {
msg: String
},
data() {
return {
age: 123
}
}
}
// 這里引入組件的template部分
import "/src/components/HelloWorld.vue?type=style&index=0"
// 這里引入組件的style部分
import {render as __render} from "/src/components/HelloWorld.vue?type=template"
__script.render = __render
__script.__hmrId = "/src/components/HelloWorld.vue"
__script.__file = "/Users/lizhuang/gitcode/vite-test/src/components/HelloWorld.vue"
export default __script
HelloWorld.vue?type=style 樣式部分編譯:
import { updateStyle } from "/vite/client"
const css = "\nh1 {\n background: red;\n}\n"
updateStyle("62a9ebed-0", css)
export default css
HelloWorld.vue?type=template 結(jié)構(gòu)部分編譯:
import {
toDisplayString as _toDisplayString,
createVNode as _createVNode,
createTextVNode as _createTextVNode,
Fragment as _Fragment,
openBlock as _openBlock,
createBlock as _createBlock
} from "/@modules/vue.js"
const _hoisted_1 = /*#__PURE__*/
_createVNode("p", null, "string1", -1 /* HOISTED */
)
const _hoisted_2 = /*#__PURE__*/
_createVNode("p", null, [/*#__PURE__*/
_createTextVNode("Edit "), /*#__PURE__*/
_createVNode("code", null, "components/HelloWorld.vue"), /*#__PURE__*/
_createTextVNode(" to test hot module replacement.")], -1 /* HOISTED */
)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(),
_createBlock(_Fragment, null, [_createVNode("h1", null, _toDisplayString($props.msg), 1 /* TEXT */
), _createVNode("button", {
onClick: _cache[1] || (_cache[1] = $event=>($data.count++))
}, "count is: " + _toDisplayString($data.count), 1 /* TEXT */
), _hoisted_1, _createVNode("p", null, _toDisplayString($data.string), 1 /* TEXT */
), _hoisted_2], 64 /* STABLE_FRAGMENT */
))
}
JSON 文件格式處理
通過 rollup-pluginutils 的dataToEsm方法
{
custom: 'data',
to: ['treeshake']
}
轉(zhuǎn)變?yōu)椋?/p>
export const custom = 'data';
export const to = ['treeshake'];
export default { custom, to };
CSS 文件格式處理
其實(shí)在vue的樣式部分已經(jīng)有所涉及留拾。
import { updateStyle } from "/vite/client"
const css = "\nh1 {\n background: red;\n}\n"
updateStyle("62a9ebed-0", css)
export default css
其中 updateStyle 是更新樣式的關(guān)鍵函數(shù),我們進(jìn)行分析:
/**
* content: css文件內(nèi)容
*/
function updateStyle(id, content) {
...
if (!style) {
style = new CSSStyleSheet()
style.replaceSync(content)
document.adoptedStyleSheets = [...document.adoptedStyleSheets, style]
} else {
style.replaceSync(content)
}
...
}
vite利用** CSSStyleSheet **代表一個樣式表茵瀑,利用javascript的接口編輯或者添加相關(guān)的樣式间驮。
當(dāng)然還有一個特殊的情況就是css文件中有@import 等操作, 這種特殊的情況躬厌,vite直接使用style標(biāo)簽進(jìn)行樣式插入马昨。
cosnt style = document.createElement('style')
style.setAttribute('type', 'text/css')
style.innerHTML = content
document.head.appendChild(style)
熱更新
vite1 代碼較少,這可以讓我們低成本的學(xué)習(xí)一個開發(fā)環(huán)境熱更新的具體細(xì)節(jié)
其中第四部處理不同文件的方式扛施,列在了下方:
async function handleMessage(payload: HMRPayload) {
const { path, changeSrcPath, timestamp } = payload as UpdatePayload
switch (payload.type) {
case 'connected':
console.log(`[vite] connected.`)
break
case 'vue-reload':
queueUpdate(
import(`${path}?t=${timestamp}`)
.catch((err) => warnFailedFetch(err, path))
.then((m) => () => {
__VUE_HMR_RUNTIME__.reload(path, m.default)
console.log(`[vite] ${path} reloaded.`)
})
)
break
case 'vue-rerender':
const templatePath = `${path}?type=template`
import(`${templatePath}&t=${timestamp}`).then((m) => {
__VUE_HMR_RUNTIME__.rerender(path, m.render)
console.log(`[vite] ${path} template updated.`)
})
break
case 'style-update':
// check if this is referenced in html via <link>
const el = document.querySelector(`link[href*='${path}']`)
if (el) {
el.setAttribute(
'href',
`${path}${path.includes('?') ? '&' : '?'}t=${timestamp}`
)
break
}
// imported CSS
const importQuery = path.includes('?') ? '&import' : '?import'
await import(`${path}${importQuery}&t=${timestamp}`)
break
.... 還有很多鸿捧,就不一一列舉了
}
}
預(yù)打包
vite的預(yù)打包優(yōu)化手段其實(shí)和小程序頁面預(yù)加載技術(shù),以及網(wǎng)頁的prefetch疙渣,preload等的原理是基本一致的匙奴,當(dāng)我們盡量少的打包過后,那么預(yù)打包那些沒有處理的文件就是優(yōu)化的手段之一妄荔。
vite 會去分析package.json 當(dāng)中的依賴項(xiàng)泼菌,會將依賴進(jìn)行打包并緩存:
其中l(wèi)odash較為特殊,因?yàn)槠湮募姸嗬沧猓绻贿M(jìn)行預(yù)打包的話哗伯,開發(fā)項(xiàng)目將會請求很多相關(guān)文件,造成網(wǎng)頁reload時性能衰減篷角。所以vite預(yù)打包的另外一個重要的功能就是通過rollup或者esbuild(vite 不同版本實(shí)現(xiàn)不同)焊刹,將過于零散的文件打包,減少網(wǎng)絡(luò)請求恳蹲,提高頁面reload性能虐块。