前端技術(shù)優(yōu)選 今天
以下文章來源于淘系前端團(tuán)隊(duì) ,作者木偶
文末福利:開發(fā)者藏經(jīng)閣
NO.1
vite 是什么
vite —— 一個(gè)由 vue 作者尤雨溪開發(fā)的 web 開發(fā)工具,它具有以下特點(diǎn):
快速的冷啟動(dòng)
即時(shí)的模塊熱更新
真正的按需編譯
從作者在微博上的發(fā)言:
Vite,一個(gè)基于瀏覽器原生 ES imports 的開發(fā)服務(wù)器。利用瀏覽器去解析 imports,在服務(wù)器端按需編譯返回,完全跳過了打包這個(gè)概念,服務(wù)器隨起隨用。同時(shí)不僅有 Vue 文件支持,還搞定了熱更新,而且熱更新的速度不會(huì)隨著模塊增多而變慢。針對(duì)生產(chǎn)環(huán)境則可以把同一份代碼用 rollup 打。雖然現(xiàn)在還比較粗糙,但這個(gè)方向我覺得是有潛力的,做得好可以徹底解決改一行代碼等半天熱更新的問題。
中可以看出 vite 主要特點(diǎn)是基于瀏覽器 native 的 ES module (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) 來開發(fā),省略打包這個(gè)步驟,因?yàn)樾枰裁促Y源直接在瀏覽器里引入即可。
基于瀏覽器 ES module 來開發(fā) web 應(yīng)用也不是什么新鮮事,snowpack 也基于此,不過目前此項(xiàng)目社區(qū)中并沒有流行起來,vite 的出現(xiàn)也許會(huì)讓這種開發(fā)方式再火一陣子。
有趣的是 vite 算是革了 webpack 的命了(生產(chǎn)環(huán)境用 rollup),所以 webpack 的開發(fā)者直接喊大哥了...
作者注:本文完成于 vite 早期時(shí)候,vite 部分功能和原理有更新。
NO.2
vite 的使用方式
同常見的開發(fā)工具一樣,vite 提供了用 npm 或者 yarn 一建生成項(xiàng)目結(jié)構(gòu)的方式,使用 yarn 在終端執(zhí)行:
$ yarn create vite-app <project-name>
$ cd <project-name>
$ yarn
$ yarn dev
即可初始化一個(gè) vite 項(xiàng)目(默認(rèn)應(yīng)用模板為 vue3.x),生成的項(xiàng)目結(jié)構(gòu)十分簡(jiǎn)潔:
|____node_modules
|____App.vue // 應(yīng)用入口
|____index.html // 頁(yè)面入口
|____vite.config.js // 配置文件
|____package.json
執(zhí)行 yarn dev
即可啟動(dòng)應(yīng)用 。
NO.3
vite 啟動(dòng)鏈路
命令解析
這部分代碼在 src/node/cli.ts 里,主要內(nèi)容是借助 minimist —— 一個(gè)輕量級(jí)的命令解析工具解析 npm scripts,解析的函數(shù)是 resolveOptions
,精簡(jiǎn)后的代碼片段如下。
function resolveOptions() {
// command 可以是 dev/build/optimize
if (argv._[0]) {
argv.command = argv._[0];
}
return argv;
}
拿到 options 后,會(huì)根據(jù) options.command
的值判斷是執(zhí)行在開發(fā)環(huán)境需要的 runServe 命令或生產(chǎn)環(huán)境需要的 runBuild 命令。
if (!options.command || options.command === 'serve') {
runServe(options)
} else if (options.command === 'build') {
runBuild(options)
} else if (options.command === 'optimize') {
runOptimize(options)
}
在 runServe
方法中,執(zhí)行 server 模塊的創(chuàng)建開發(fā)服務(wù)器方法,同樣在 runBuild
中執(zhí)行 build 模塊的構(gòu)建方法。
最新的版本中還增加了 optimize 命令的支持,關(guān)于 optimize 做了什么,我們下文再說。
server
這部分代碼在 src/node/server/index.ts 里,主要暴露一個(gè) createServer
方法。
vite 使用 koa 作 web server,使用 clmloader 創(chuàng)建了一個(gè)監(jiān)聽文件改動(dòng)的 watcher,同時(shí)實(shí)現(xiàn)了一個(gè)插件機(jī)制,將 koa-app 和 watcher 以及其他必要工具組合成一個(gè) context 對(duì)象注入到每個(gè) plugin 中。
context 組成如下:
plugin 依次從 context 里獲取上面這些組成部分,有的 plugin 在 koa 實(shí)例添加了幾個(gè) middleware,有的借助 watcher 實(shí)現(xiàn)對(duì)文件的改動(dòng)監(jiān)聽,這種插件機(jī)制帶來的好處是整個(gè)應(yīng)用結(jié)構(gòu)清晰,同時(shí)每個(gè)插件處理不同的事情,職責(zé)更分明,
plugin
上文我們說到 plugin,那么有哪些 plugin 呢?它們分別是:
用戶注入的 plugins —— 自定義 plugin
hmrPlugin —— 處理 hmr
htmlRewritePlugin —— 重寫 html 內(nèi)的 script 內(nèi)容
moduleRewritePlugin —— 重寫模塊中的 import 導(dǎo)入
moduleResolvePlugin ——獲取模塊內(nèi)容
vuePlugin —— 處理 vue 單文件組件
esbuildPlugin —— 使用 esbuild 處理資源
assetPathPlugin —— 處理靜態(tài)資源
serveStaticPlugin —— 托管靜態(tài)資源
cssPlugin —— 處理 css/less/sass 等引用
...
我們來看 plugin 的實(shí)現(xiàn)方式,開發(fā)一個(gè)用來攔截 json 文件 plugin 可以這么實(shí)現(xiàn):
interface ServerPluginContext {
root: string
app: Koa
server: Server
watcher: HMRWatcher
resolver: InternalResolver
config: ServerConfig
}
type ServerPlugin = (ctx:ServerPluginContext)=> void;
const JsonInterceptPlugin:ServerPlugin = ({app})=>{
app.use(async (ctx, next) => {
await next()
if (ctx.path.endsWith('.json') && ctx.body) {
ctx.type = 'js'
ctx.body = `export default json`
}
})
}
vite 背后的原理都在 plugin 里,這里不再一一解釋每個(gè) plugin 的作用,會(huì)放在下文背后的原理中一并討論。
build
這部分代碼在 node/build/index.ts 中,build 目錄的結(jié)構(gòu)雖然與 server 相似,同樣導(dǎo)出一個(gè) build 方法,同樣也有許多 plugin,不過這些 plugin 與 server 中的用途不一樣,因?yàn)?build 使用了 rollup ,所以這些 plugin 也是為 rollup 打包的 plugin ,本文就不再多提。
NO.4
vite 運(yùn)行原理
ES module
要了解 vite 的運(yùn)行原理,首先要知道什么是 ES module,目前流覽器對(duì)其的支持如下:
主流的瀏覽器(IE11除外)均已經(jīng)支持,其最大的特點(diǎn)是在瀏覽器端使用 export
import
的方式導(dǎo)入和導(dǎo)出模塊,在 script 標(biāo)簽里設(shè)置 type="module"
,然后使用模塊內(nèi)容。
<script type="module">
import { bar } from './bar.js'
</script>
當(dāng) html 里嵌入上面的 script 標(biāo)簽時(shí)候,瀏覽器會(huì)發(fā)起 http 請(qǐng)求,請(qǐng)求 htttp server 托管的 bar.js ,在 bar.js 里,我們用 named export 導(dǎo)出 bar
變量,在上面的 script 中能獲取到 bar 的定義。
// bar.js
export const bar = 'bar';
在 vite 中的作用
打開運(yùn)行中的 vite 項(xiàng)目,訪問 view-source 可以發(fā)現(xiàn) html 里有段這樣的代碼:
<script type="module">
import { createApp } from '/@modules/vue'
import App from '/App.vue'
createApp(App).mount('#app')
</script>
從這段代碼中,我們能 get 到以下幾點(diǎn)信息:
從 http://localhost:3000/@modules/vue
中獲取 createApp
這個(gè)方法
從 http://localhost:3000/App.vue
中獲取應(yīng)用入口
使用 createApp
創(chuàng)建應(yīng)用并掛載節(jié)點(diǎn)
createApp
是 vue3.X 的 api,只需知道這是創(chuàng)建了 vue 應(yīng)用即可,vite 利用 ES module,把 “構(gòu)建 vue 應(yīng)用” 這個(gè)本來需要通過 webpack 打包后才能執(zhí)行的代碼直接放在瀏覽器里執(zhí)行,這么做是為了:
去掉打包步驟
實(shí)現(xiàn)按需加載
打包的概念是開發(fā)者利用打包工具將應(yīng)用各個(gè)模塊集合在一起形成 bundle,以一定規(guī)則讀取模塊的代碼——以便在不支持模塊化的瀏覽器里使用。
為了在瀏覽器里加載各模塊,打包工具會(huì)借助膠水代碼用來組裝各模塊,比如 webpack 使用 map
存放模塊 id 和路徑,使用 __webpack_require__
方法獲取模塊導(dǎo)出。
vite 利用瀏覽器原生支持模塊化導(dǎo)入這一特性,省略了對(duì)模塊的組裝,也就不需要生成 bundle,所以打包這一步就可以省略了。
前面說到,webpack 之類的打包工具會(huì)將各模塊提前打包進(jìn) bundle 里,但打包的過程是靜態(tài)的——不管某個(gè)模塊的代碼是否執(zhí)行到,這個(gè)模塊都要打包到 bundle 里,這樣的壞處就是隨著項(xiàng)目越來越大打包后的 bundle 也越來越大。
開發(fā)者為了減少 bundle 大小,會(huì)使用動(dòng)態(tài)引入 import()
的方式異步的加載模塊( 被引入模塊依然需要提前打包),又或者使用 tree shaking 等方式盡力的去掉未引用的模塊,然而這些方式都不如 vite 的優(yōu)雅,vite 可以只在需要某個(gè)模塊的時(shí)候動(dòng)態(tài)(借助 import()
)的引入它,而不需要提前打包,雖然只能用在開發(fā)環(huán)境,不過這就夠了。
vite 如何處理 ESM
既然 vite 使用 ESM 在瀏覽器里使用模塊,那么這一步究竟是怎么做的?
上文提到過,在瀏覽器里使用 ES module 是使用 http 請(qǐng)求拿到模塊,所以 vite 必須提供一個(gè) web server 去代理這些模塊,上文中提到的 koa
就是負(fù)責(zé)這個(gè)事情,vite 通過對(duì)請(qǐng)求路徑的劫持獲取資源的內(nèi)容返回給瀏覽器,不過 vite 對(duì)于模塊導(dǎo)入做了特殊處理。
通過工程下的 index.html 和開發(fā)環(huán)境下的 html 源文件對(duì)比,發(fā)現(xiàn) script 標(biāo)簽里的內(nèi)容發(fā)生了改變,由
<script type="module">
import { createApp } from 'vue'
import App from '/App.vue'
createApp(App).mount('#app')
</script>
變成了
<script type="module">
import { createApp } from '/@modules/vue'
import App from '/App.vue'
createApp(App).mount('#app')
</script>
vite 對(duì)import
都做了一層處理,其過程如下:
在 koa 中間件里獲取請(qǐng)求 body
通過 es-module-lexer 解析資源 ast 拿到 import 的內(nèi)容
判斷 import 的資源是否是絕對(duì)路徑,絕對(duì)視為 npm 模塊
返回處理后的資源路徑:"vue" => "/@modules/vue"
這部分代碼在 serverPluginModuleRewrite 這個(gè) plugin 中,
如果我們?cè)谀K里寫下以下代碼的時(shí)候,瀏覽器中的 esm 是不可能獲取到導(dǎo)入的模塊內(nèi)容的:
import vue from 'vue'
因?yàn)?nbsp;vue
這個(gè)模塊安裝在 node_modules 里,以往使用 webpack,webpack遇到上面的代碼,會(huì)幫我們做以下幾件事:
獲取這段代碼的內(nèi)容
解析成 AST
遍歷 AST 拿到 import
語(yǔ)句中的包的名稱
使用 enhanced-resolve 拿到包的實(shí)際地址進(jìn)行打包,
但是瀏覽器中 ESM 無法直接訪問項(xiàng)目下的 node_modules,所以 vite 對(duì)所有 import 都做了處理,用帶有 @modules 的前綴重寫它們。
從另外一個(gè)角度來看這是非常比較巧妙的做法,把文件路徑的 rewrite 都寫在同一個(gè) plugin 里,這樣后續(xù)如果加入更多邏輯,改動(dòng)起來不會(huì)影響其他 plugin,其他 plugin 拿到資源路徑都是 @modules ,比如說后續(xù)可能加入 alias 的配置:就像 webpack alias 一樣:可以將項(xiàng)目里的本地文件配置成絕對(duì)路徑的引用。
在下一個(gè) koa middleware 中,用正則匹配到路徑上帶有 @modules 的資源,再通過 require('xxx')
拿到 包的導(dǎo)出返回給瀏覽器。
以往使用 webpack 之類的打包工具,它們除了將模塊組裝到一起形成 bundle,還可以讓使用了不同模塊規(guī)范的包互相引用,比如:
ES module (esm) 導(dǎo)入 cjs
CommonJS (cjs) 導(dǎo)入 esm
dynamic import 導(dǎo)入 esm
dynamic import 導(dǎo)入 cjs
關(guān)于 es module 的坑可以看這篇文章(https://zhuanlan.zhihu.com/p/40733281)。
起初在 vite 還只是為 vue3.x 設(shè)計(jì)的時(shí)候,對(duì) vue esm 包是經(jīng)過特殊處理的,比如:需要 @vue/runtime-dom
這個(gè)包的內(nèi)容,不能直接通過 require('
@vue/runtime-dom'
)得到,而需要通過 require('@vue/runtime-dom/dist/runtime-dom.esm-bundler.js'
的方式,這樣可以使得 vite 拿到符合 esm 模塊標(biāo)準(zhǔn)的 vue 包。
目前社區(qū)中大部分模塊都沒有設(shè)置默認(rèn)導(dǎo)出 esm,而是導(dǎo)出了 cjs 的包,既然 vue3.0 需要額外處理才能拿到 esm 的包內(nèi)容,那么其他日常使用的 npm 包是不是也同樣需要支持?答案是肯定的,目前在 vite 項(xiàng)目里直接使用 lodash 還是會(huì)報(bào)錯(cuò)的。
不過 vite 在最近的更新中,加入了 optimize
命令,這個(gè)命令專門為解決模塊引用的坑而開發(fā),例如我們要在 vite 中使用 lodash,只需要在 vite.config.js (vite 配置文件)中,配置 optimizeDeps
對(duì)象,在 include
數(shù)組中添加 lodash。
// vite.config.js
module.exports = {
optimizeDeps: {
include: ["lodash"]
}
}
這樣 vite 在執(zhí)行 runOptimize 的時(shí)候中會(huì)使用 roolup 對(duì) lodash 包重新編譯,將編譯成符合 esm 模塊規(guī)范的新的包放入 node_modules 下的 .vite_opt_cache 中,然后配合 resolver 對(duì) lodash 的導(dǎo)入進(jìn)行處理:使用編譯后的包內(nèi)容代替原來 lodash 的包的內(nèi)容,這樣就解決了 vite 中不能使用 cjs 包的問題,這部分代碼在 depOptimizer.ts 里。
不過這里還有個(gè)問題,由于在 depOptimizer.ts 中,vite 只會(huì)處理在項(xiàng)目下 package.json 里的 dependencies
里聲明好的包進(jìn)行處理,所以無法在項(xiàng)目里使用
import pick from 'lodash/pick'
的方式單使用 pick 方法,而要使用
import lodash from 'lodash'
lodash.pick()
的方式,這可能在生產(chǎn)環(huán)境下使用某些包的時(shí)候?qū)?bundle 的體積有影響。
返回模塊的內(nèi)容的代碼在:serverPluginModuleResolve.ts 這個(gè) plugin 中。
vite 如何編譯模塊
最初 vite 為 vue3.x 開發(fā),所以這里的編譯指的是編譯 vue 單文件組件,其他 es 模塊可以直接導(dǎo)入內(nèi)容。
vue 單文件組件(簡(jiǎn)稱 SFC) 是 vue 的一個(gè)亮點(diǎn),前端屆對(duì) SFC 褒貶不一,個(gè)人看來,SFC 是利大于弊的,雖然 SFC 帶來了額外的開發(fā)工作量,比如為了解析 template 要寫模板解析器,還要在 SFC 中解析出邏輯和樣式,在 vscode 里要寫 vscode 插件,在 webpack 里要寫 vue-loader,但是對(duì)于使用方來說可以在一個(gè)文件里可以同時(shí)寫 template、js、style,省了各文件互相跳轉(zhuǎn)。
與 vue-loader 相似,vite 在解析 vue 文件的時(shí)候也要分別處理多次,我們打開瀏覽器的 network,可以看到:
1 個(gè)請(qǐng)求的 query 中什么都沒有,另 2 個(gè)請(qǐng)求分別通過在 query 里指定了 type 為 style 和 template。
先來看看如何將一個(gè) SFC 變成多個(gè)請(qǐng)求,我們從第一次請(qǐng)求開始分析,簡(jiǎn)化后的代碼如下:
function vuePlugin({app}){
app.use(async (ctx, next) => {
if (!ctx.path.endsWith('.vue') && !ctx.vue) {
return next()
}
const query = ctx.query
// 獲取文件名稱
let filename = resolver.requestToFile(publicPath)
// 解析器解析 SFC
const descriptor = await parseSFC(root, filename, ctx.body)
if (!descriptor) {
ctx.status = 404
return
}
// 第一次請(qǐng)求 .vue
if (!query.type) {
if (descriptor.script && descriptor.script.src) {
filename = await resolveSrcImport(descriptor.script, ctx, resolver)
}
ctx.type = 'js'
// body 返回解析后的代碼
ctx.body = await compileSFCMain(descriptor, filename, publicPath)
}
// ...
}
在 compileSFCMain 中是一段長(zhǎng)長(zhǎng)的 generate 代碼:
function compileSFCMain(descriptor, filePath: string, publicPath: string) {
let code = ''
if (descriptor.script) {
let content = descriptor.script.content
code += content.replace(`export default`, 'const __script =')
} else {
code += `const __script = {}`
}
if (descriptor.styles) {
code += `\nimport { updateStyle } from "${hmrClientId}"\n`
descriptor.styles.forEach((s, i) => {
const styleRequest = publicPath + `?type=style&index=${i}`
code += `\nupdateStyle("${id}-${i}", ${JSON.stringify(styleRequest)})`
})
if (hasScoped) {
code += `\n__script.__scopeId = "data-v-${id}"`
}
}
if (descriptor.template) {
code += `\nimport { render as __render } from ${JSON.stringify(
publicPath + `?type=template`
)}`
code += `\n__script.render = __render`
}
code += `\n__script.__hmrId = ${JSON.stringify(publicPath)}`
code += `\n__script.__file = ${JSON.stringify(filePath)}`
code += `\nexport default __script`
return code
}
直接看 generate 后的代碼:
import { updateStyle } from "/vite/hmr"
updateStyle("c44b8200-0", "/App.vue?type=style&index=0")
__script.__scopeId = "data-v-c44b8200"
import { render as __render } from "/App.vue?type=template"
__script.render = __render
__script.__hmrId = "/App.vue"
__script.__file = "/Users/muou/work/playground/vite-app/App.vue"
export default __script
出現(xiàn)了 vite/hmr
的導(dǎo)入,vite/hmr
具體內(nèi)容我們下文再分析,從這段代碼中可以看到,對(duì)于 style vite 使用 updateStyle
這個(gè)方法處理,updateStyle
內(nèi)容非常簡(jiǎn)單,這里就不貼代碼了,就做了 1 件事:通過創(chuàng)建 style 元素,設(shè)置了它的 innerHtml 為 css 內(nèi)容。
這兩種方式都會(huì)使得瀏覽器發(fā)起 http 請(qǐng)求,這樣就能被 koa 中間件捕獲到了,所以就形成了上文我們看到的:對(duì)一個(gè) .vue 文件處理三次的情景。
這部分代碼在:serverPluginVue 這個(gè) plugin 里。
如果在 vite 項(xiàng)目里引入一個(gè) sass 文件會(huì)怎么樣?
最初 vite 只是為 vue 項(xiàng)目開發(fā),所以并沒有對(duì) css 預(yù)編譯的支持,不過隨著后續(xù)的幾次大更新,在 vite 項(xiàng)目里使用 sass/less 等也可以跟使用 webpack 的時(shí)候一樣優(yōu)雅了,只需要安裝對(duì)應(yīng)的 css 預(yù)處理器即可。
在 cssPlugin 中,通過正則:/(.+).(less|sass|scss|styl|stylus)$/
判斷路徑是否需要 css 預(yù)編譯,如果命中正則,就借助 cssUtils 里的方法借助 postcss 對(duì)要導(dǎo)入的 css 文件編譯。
vite 熱更新的實(shí)現(xiàn)
上文中出現(xiàn)了 vite/hmr ,這就是 vite 處理熱更新的關(guān)鍵,在 serverPluginHmr plugin 中,對(duì)于 path
等于 vite/hmr
做了一次判斷:
app.use(async (ctx, next) => {
if (ctx.path === '/vite/hmr') {
ctx.type = 'js'
ctx.status = 200
ctx.body = hmrClient
}
}
hmrClient 是 vite 熱更新的客戶端代碼,需要在瀏覽器里執(zhí)行,這里先來說說通用的熱更新實(shí)現(xiàn),熱更新一般需要四個(gè)部分:
首先需要 web 框架支持模塊的 rerender/reload
通過 watcher 監(jiān)聽文件改動(dòng)
通過 server 端編譯資源,并推送新模塊內(nèi)容給 client 。
client 收到新的模塊內(nèi)容,執(zhí)行 rerender/reload
vite 也不例外同樣有這四個(gè)部分,其中客戶端代碼在:client.ts 里,服務(wù)端代碼在 serverPluginHmr 里,對(duì)于 vue 組件的更新,通過 vue3.x 中的 HMRRuntime
處理的。
在 client 端, WebSocket
監(jiān)聽了一些更新的類型,然后分別處理,它們是:
vue-reload —— vue 組件更新:通過 import 導(dǎo)入新的 vue 組件,然后執(zhí)行 HMRRuntime.reload
vue-rerender —— vue template 更新:通過 import 導(dǎo)入新的 template ,然后執(zhí)行 HMRRuntime.rerender
vue-style-update —— vue style 更新:直接插入新的 stylesheet
style-update —— css 更新:document 插入新的 stylesheet
style-remove —— css 移除:document 刪除 stylesheet
js-update —— js 更新:直接執(zhí)行
full-reload —— 頁(yè)面 roload:使用 window.reload
刷新頁(yè)面
在 server 端,通過 watcher 監(jiān)聽頁(yè)面改動(dòng),根據(jù)文件類型判斷是 js Reload 還是 Vue Reload:
watcher.on('change', async (file) => {
const timestamp = Date.now()
if (file.endsWith('.vue')) {
handleVueReload(file, timestamp)
} else if (
file.endsWith('.module.css') ||
!(file.endsWith('.css') || cssTransforms.some((t) => t.test(file, {})))
) {
// everything except plain .css are considered HMR dependencies.
// plain css has its own HMR logic in ./serverPluginCss.ts.
handleJSReload(file, timestamp)
}
})
在 handleVueReload 方法里,會(huì)使用解析器拿到當(dāng)前文件的 template/script/style ,并且與緩存里的上一次解析的結(jié)果進(jìn)行比較,如果 template 發(fā)生改變就執(zhí)行 vue-rerender,如果 style 發(fā)生改變就執(zhí)行 vue-style-update,簡(jiǎn)化后的邏輯如下:
async function handleVueReload(
file
timestamp,
content
) {
// 獲取緩存
const cacheEntry = vueCache.get(file)
// 解析 vue 文件
const descriptor = await parseSFC(root, file, content)
if (!descriptor) {
// read failed
return
}
// 拿到上一次解析結(jié)果
const prevDescriptor = cacheEntry && cacheEntry.descriptor
// 設(shè)置刷新變量
let needReload = false // script 改變標(biāo)記
let needCssModuleReload = false // css 改變標(biāo)記
let needRerender = false // template 改變標(biāo)記
// 判斷 script 是否相同
if (!isEqual(descriptor.script, prevDescriptor.script)) {
needReload = true
}
// 判斷 template 是否相同
if (!isEqual(descriptor.template, prevDescriptor.template)) {
needRerender = true
}
// 通過 send 發(fā)送 socket
if (needRerender){
send({
type: 'vue-rerender',
path: publicPath,
timestamp
})
}
}
handleJSReload
方法則是根據(jù)文件路徑引用,判斷被哪個(gè) vue 組件所依賴,如果未找到 vue 組件依賴,則判斷頁(yè)面需要刷新,否則走組件更新邏輯,這里就不貼代碼了。
整體代碼在 client.ts 和 serverPluginHmr.ts 里。
NO.5
結(jié)語(yǔ)
本文分析了 vite 的啟動(dòng)鏈路以及背后的部分原理,雖然在短時(shí)間內(nèi) vite 不會(huì)替代 webpack,但是能夠看到社區(qū)中多了一種方案還是很興奮的,這也是我寫下這篇文章的原因。
vite 更新的實(shí)在太快了,佩服尤大的勤奮和開源精神,短短一個(gè)月就加入了諸如 css 預(yù)編譯/react支持/通用 hmr 的支持,由于篇幅有限本文不再一一介紹這些新特性,這些新的特性等待讀者朋友們自行去探討了。
聯(lián)系客服