使用webpack在開發中,只改動一句代碼,也須要數秒的熱更新,這是由於webpack須要將全部的模塊打包成一個一個或者多個模塊,而後啓動開發服務器,請求服務器時直接給予打包結果。這個過程隨着項目的擴大,速度會變慢。而後vite來了。css
描述:針對Vue單頁面組件的無打包開發服務器,能夠直接在瀏覽器運行請求的vue文件vue
特色:node
一、Npm 建立 vite項目react
npm init vite-app projectNamewebpack
二、Yarn 建立vite項目git
yarn create vite-app projectNameweb
三、vite建立react項目npm
新建文件夾。後端
進入文件夾中命令npm init vite-app --template react瀏覽器
安裝依賴 yarn
運行 yarn dev
當聲明一個 script
標籤類型爲 module
時,
<script type="module" src="/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
開發編譯速度快出許多!
// 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' })) ... }) }
// 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
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
.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 } }
一、默認採用ES 6原生模塊
二、默認會給vue的模塊加一個前綴@modules import { createApp } from '/@modules/vue.js'
三、解析.vue文件
vite的優雅之處就在於須要某個模塊時動態引入,而不是提早打包,天然而然提升了開發體驗