使用webpack在開發中,只改動一句代碼,也需要數秒的熱更新,這是因為webpack需要將所有的模塊打包成一個一個或者多個模塊,然后啟動開發服務器,請求服務器時直接給予打包結果,這個程序隨著專案的擴大,速度會變慢,然后vite來了,
描述:針對Vue單頁面組件的無打包開發服務器,可以直接在瀏覽器運行請求的vue檔案
特點:
- 冷服務啟動-使用ES6 import預覽的時候不打包
- 開發中熱更新
- 按需進行編譯,不會重繪全部DOM
vite
使用vite創建vue3專案
1、Npm 創建 vite專案
npm init vite-app projectName
2、Yarn 創建vite專案
yarn create vite-app projectName
3、vite創建react專案
-
新建檔案夾,
-
進入檔案夾中命令npm init vite-app --template react
-
安裝依賴 yarn
-
運行 yarn dev
問題1:既然去掉了webpack打包步驟,那么vite是如何處理這些模塊的呢?
當宣告一個 script 標簽型別為 module 時,
<script type="module" src="https://www.cnblogs.com/src/main.js"></script>
瀏覽器就會像服務器發起一個GET http://localhost:3000/src/main.js請求main.js檔案,
瀏覽器請求到了main.js檔案,檢測到內部含有import引入的包,又會對其內部的 import參考發起 HTTP 請求獲取模塊的內容檔案!如: GET http://localhost:3000/@modules/vue.js 如: GET http://localhost:3000/src/App.vue 其Vite 的主要功能就是通過劫持瀏覽器的這些請求,并在后端進行相應的處理將專案中使用的檔案通過簡單的分解與整合,然后再回傳給瀏覽器渲染頁面,vite整個程序中沒有對檔案進行打包編譯,所以其運行速度比原始的webpack開發編譯速度快出許多!
- 在瀏覽器中啟動了一個socket服務,實時的接受一系列的指令,根據指令再處理相應的邏輯,
// src/node/serve/index.ts
export async function createServer(
inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
// 創建serve服務
const app = connect() as Connect.Server
const ws = createWebSocketServer(httpServer, logger)
const watchOptions = serverConfig.watch || {}
const watcher = chokidar.watch(root, {
ignored: [
'**/node_modules/**',
'**/.git/**',
...(watchOptions.ignored || [])
],
...
}) as FSWatcher
}
createWebSocketServer處理
// src/node/serve/ws.ts
export function createWebSocketServer(
server: Server,
logger: Logger
): WebSocketServer {
// 啟動一個webSocket服務
const wss = new WebSocket.Server({ noServer: true })
server.on('upgrade', (req, socket, head) => {
...
}
})
// 通知客戶端鏈接成功,需要請求檔案
wss.on('connection', (socket) => {
socket.send(JSON.stringify({ type: 'connected' }))
...
})
}
- server端負責在執行的各個階段向客戶端發送指令
// src/linet/client.ts
async function handleMessage(payload: HMRPayload) {
switch (payload.type) {
case 'connected': // scoket鏈接成功
console.log(`[vite] connected.`)
setInterval(() => socket.send('ping'), __HMR_TIMEOUT__)
break
case 'update':// js檔案更新
...
...
break
case 'custom'://自定義
...
break
case 'full-reload': //網頁重重繪
...
break
case 'prune': //移除模塊
...
break
case 'error':
...
break
default:
const check: never = payload
return check
}
}
當vite檔案監聽系統監聽到.vue組件發生變化之后,就會去決議編譯.vue組件,并向client發送一條對應指令,并把編譯后的代碼也發送給client
問題2:為什么給vue的模塊加一個前綴@modules ?
import { createApp } from 'vue'
編譯器能夠自動從node_modules尋找vue這個模塊,是因為npm install時,編譯器存盤了vue別名,因此可直接去node_modules中讀取,
但瀏覽器環境并沒有執行這個程序,因此依然會從當前檔案的同級路徑下尋找vue這個檔案,如果檔案不存在,則報404錯誤,因此我們要把 node_modules 變成瀏覽器環境可識別的位置,即 /@modules/
vue模塊安裝在node_modules中,瀏覽器ES Module是無法直接獲取到專案下node_modules目錄中的檔案,所以vite對import都做了一層處理,重寫了前綴使其帶有@modules
問題3:既然瀏覽器直接請求了.vue 檔案,那么檔案內容是如何做出決議的呢?
唯一編譯.vue檔案,被決議成render函式回傳給瀏覽器渲染頁面,當Vite遇到一個.vue后綴的檔案時,由于.vue模板檔案的特殊性,它被分割成template,css,腳本模塊三個模塊進行分別處理,最后放入script,template,css發送多個請求獲取,
單頁面檔案的請求都是以*.vue作為請求路徑結尾,當服務器接收到這種特點的http請求,主要處理
- 根據
ctx.path確定請求具體的vue檔案 - 使用
parseSFC決議該檔案,獲得descriptor,一個descriptor包含了這個組件的基本資訊,包括template、script和styles等屬性
然后根據descriptor和ctx.query.type選擇對應型別的方法,處理后回傳
// plugin-vue/src/index.ts
export default function vuePlugin(rawOptions: Options = {}): Plugin {
...
transform(code, id) {
const { filename, query } = parseVueRequest(id)
...
if (!query.vue) {
...
} else {
// 使用parseSFC決議該檔案
const descriptor = getDescriptor(filename)!
// 根據`descriptor`和`ctx.query.type`選擇對應型別決議的方法
if (query.type === 'template') {
return transformTemplateAsModule(code, descriptor, options, this)
} else if (query.type === 'style') {
...
}
}
}
}
// plugin-vue/src/template.ts
export function transformTemplateAsModule(
...
) {
...
if (options.devServer && !options.isProduction) {
returnCode += `\nimport.meta.hot.accept(({ render }) => {
__VUE_HMR_RUNTIME__.rerender(${JSON.stringify(descriptor.id)}, render)
})`
}
return {
code: returnCode,
map: result.map as any
}
}
總結:
1、默認采用ES 6原生模塊
2、默認會給vue的模塊加一個前綴@modules import { createApp } from '/@modules/vue.js'
3、決議.vue檔案
vite的優雅之處就在于需要某個模塊時動態引入,而不是提前打包,自然而然提高了開發體驗
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/249422.html
標籤:其他
