前言
大家好,在上次我們已經把webpack從基礎使用到原理分析講過了,今天我們來聊一聊vite,vite為什麼快。
在上篇關於webpack中已經提到了,webpack會把我們的專案構建好然後交給瀏覽器,那麼這個過程就包含了js、css等等資源的構建,例如需要請求某一份資源,這份資源中又要請求另一份資源,構建完成才交給瀏覽器。
那麼vite是怎麼做的呢?
vite
vite讀取專案程式碼後,他就直接把這份唯一的html檔案輸出給瀏覽器了,讓瀏覽器來載入,那麼在瀏覽器載入html時就會碰到一系列的引入,那麼他就向vite發請求需要這一份js,js又向專案中取,拿到這份js後再交給瀏覽器,如此往復。這麼看,vite就像是一個後端的伺服器(所以你能明白我們配置vite.config.js用來處理跨域做代理了嗎),這也是為什麼我們vue3+vite構建一個新專案後,f12開啟就能看見明明還沒開始寫程式碼,就有很多個資源的請求了。
這就是webpack和vite在構建理念上的不同之處了,在交給瀏覽器之前webpac會讓瀏覽器等著,將資源全部載入完畢後才交付(從頭到尾將程式碼讀明白,甚至還會把瀏覽器識別不了的語法幫你降低版本)
構建vite
那麼接下來我們來看看vite是如何打造的,原理是什麼樣的,我們來自己做一個vite
專案結構:
npm i vue
index.html此處用module型別是因為如果不用這種方式引入,import語法是沒辦法識別的
<!doctype html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div id="app"> </div> <script type="module" src="/src/main.js"></script> </body> </html>
main.js
import {createApp} from "vue"; import './style.css' import App from './App.vue' createApp(App).mount('#app')
我們透過標籤引入這份js程式碼,瀏覽器會當成一個資源請求,但我們並沒有這個介面,所以會報一個404錯誤,但是我們用vite+vue也是一樣的,那麼vite在這個過程中起到了什麼作用?事實上就是想辦法讓這個介面存在
那麼我們在myVite根目錄下建立一個simple-vite.js來模擬一下 simple-vite.js
const http = require('http'); const fs = require('fs'); const path = require('path') const server = http.createServer((req, res) => { const {url,query} = req if (url === '/'){ res.writeHead(200, {'Content-Type': 'text/html'}) let html = fs.readFileSync('./index.html', 'utf-8') res.end(html) }else if (url.endsWith('.js')) { // /src/main.js const p = path.resolve(__dirname, url.slice(1)) console.log(p) res.writeHead(200, {'Content-Type': 'application/javascript'}) let content = fs.readFileSync(p, 'utf-8') res.end(content) } }) server.listen(5173, () => { console.log('專案執行在5173') })
npx nodemon simple-vite.js
然後訪問localhost:5173就會自動請求index.html進入到index.html後又會要main.js這個資源,此時就不會再報404的錯誤了,因為我們有這個介面,並且拿到了main.js這份資源。
但是,錯誤又出現了。
這種錯誤通常是沒有正確設定響應頭導致返回的檔案型別不正確或者是網頁中引用的模組指令碼也就是我們的import要來的檔案路徑不正確或者是檔案不存在
確實如此,我們import 一份vue的以後是from"vue"這裏不是一個路徑,我們必須告訴瀏覽器這份資源的正確路徑才能夠找到,因此我們需要一個方法來重寫import,將其更改為一個路徑。例如去node_modules/vue中尋找資源
js程式碼解讀複製程式碼const http = require('http'); const fs = require('fs'); const path = require('path') function rewriteImport(content){ return content.replace(/ from ['"](.*)['"]/g, function(s0,s1){ if(s1[0] !== '.' && s1[0] !== '/'){ return ` from '/@module/${s1}'` }else { return s0 } }) } const server = http.createServer((req, res) => { const {url,query} = req if (url === '/'){ res.writeHead(200, {'Content-Type': 'text/html'}) let html = fs.readFileSync('./index.html', 'utf-8') res.end(html) }else if (url.endsWith('.js')) { // /src/main.js const p = path.resolve(__dirname, url.slice(1)) console.log(p) res.writeHead(200, {'Content-Type': 'application/javascript'}) let content = fs.readFileSync(p, 'utf-8') res.end(rewriteImport(content)) } }) server.listen(5173, () => { console.log('專案執行在5173') })
此時,不僅請求了main.js,當讀到main.js後又會看見import xx from vue,此時會去請求vue資源。
那麼接下來的工作就簡單起來了,其實就是不斷的寫介面,如果你的路徑是我們改寫的@modules/xxx那就去node_modules下面找。
const http = require('http'); const fs = require('fs'); const path = require('path') function rewriteImport(content){ return content.replace(/ from ['"](.*)['"]/g, function(s0,s1){ if(s1[0] !== '.' && s1[0] !== '/'){ return ` from '/@module/${s1}'` }else { return s0 } }) } const server = http.createServer((req, res) => { const {url,query} = req if (url === '/'){ res.writeHead(200, {'Content-Type': 'text/html'}) let html = fs.readFileSync('./index.html', 'utf-8') res.end(html) }else if (url.endsWith('.js')) { // /src/main.js const p = path.resolve(__dirname, url.slice(1)) console.log(p) res.writeHead(200, {'Content-Type': 'application/javascript'}) let content = fs.readFileSync(p, 'utf-8') res.end(rewriteImport(content)) } }) server.listen(5173, () => { console.log('專案執行在5173') })
能夠看見,main.js請求了,vue原始碼也請求到了,剩下的就是css和vue的檔案如何請求,接下來還是else if繼續寫介面對吧?但是寫else if 拿vue字尾的請求,請求到了瀏覽器也沒辦法讀懂,因此需要對vue程式碼進行編譯,但是我們已經拿到vue的原始碼,vue原始碼中有編譯器。
const http = require('http'); const fs = require('fs'); const path = require('path') function rewriteImport(content){ return content.replace(/ from ['"](.*)['"]/g, function(s0,s1){ if(s1[0] !== '.' && s1[0] !== '/'){ return ` from '/@module/${s1}'` }else { return s0 } }) } const server = http.createServer((req, res) => { const {url,query} = req if (url === '/'){ res.writeHead(200, {'Content-Type': 'text/html'}) let html = fs.readFileSync('./index.html', 'utf-8') res.end(html) }else if (url.endsWith('.js')) { // /src/main.js const p = path.resolve(__dirname, url.slice(1)) console.log(p) res.writeHead(200, {'Content-Type': 'application/javascript'}) let content = fs.readFileSync(p, 'utf-8') res.end(rewriteImport(content)) }else if (url.startsWith('/@module/')){ const prefix = path.resolve(__dirname, 'node_modules',url.replace('/@module/', '')) const module = require(prefix + '/package.json').module // vue原始碼路徑 const p = path.resolve(prefix, module) // vue完整路徑 const content = fs.readFileSync(p, 'utf-8') res.writeHead(200,{ 'Content-Type': 'application/javascript' }) res.end(rewriteImport(content)) }else if () }) server.listen(5173, () => { console.log('專案執行在5173') })
小結
所以,vite為什麼快?
Vite相比於Webpack之所以構建快是因為,Vite藉助新版本瀏覽器可以讀懂模組化語法的特點,將專案中的模組化引入統一以一個又一個http請求的方式響應給瀏覽器,這樣做的好處就是省去了網路包構建過程中遞迴做依賴收集的耗時步驟,又因為Vite是開發環境的工具,絕大多數情況下我們不用不考慮相容性,不會有人開發時還用老版本的瀏覽器吧!
當然vite快,這並不是絕對的,如果一個專案有幾千幾萬個資原始檔,那發這麼多個http請求能快到哪裏呢?
宏觀結論:vite更快
作者:zykk
連結:https://juejin.cn/post/7434819155872120866