<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          下一代前端開發(fā)利器——Vite(原理源碼解析)

          共 17933字,需瀏覽 36分鐘

           ·

          2022-03-10 23:40

          本文作者是360奇舞團(tuán)前端開發(fā)工程師

          大廠技術(shù)  高級前端  Node進(jìn)階

          點(diǎn)擊上方 程序員成長指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級Node交流群

          前言

          Hi,大家好!

          前段時(shí)間用Vue3搭建項(xiàng)目時(shí)看到同時(shí)推出的Vite,只當(dāng)它是一個(gè)新打包工具或者vue-cli的升級版,仍然選擇了用Webpack構(gòu)建項(xiàng)目。最近看了尤雨溪在VueConf上的演講視頻:《Vue3 生態(tài)進(jìn)展和計(jì)劃》[1],感覺它確實(shí)解決了現(xiàn)階段前端工程化的一些痛點(diǎn),也能體會(huì)到尤雨溪對Vite的重視和大力推廣的決心,再加上Vue本身的龐大用戶基數(shù),Vite確實(shí)有可能成為下一代前端構(gòu)建工具的突破口。

          本文將討論下Vite出現(xiàn)的背景,解決的痛點(diǎn),核心功能的實(shí)現(xiàn),存在的意義和預(yù)期的未來。Vite本身并不復(fù)雜。中文官方文檔非常清晰簡潔,建議大家使用前仔細(xì)讀下文檔。


          大綱

          • 背景
          • 什么是Vite?
          • 基本用法
          • 實(shí)現(xiàn)原理
          • 源碼分析
          • 優(yōu)勢與不足
          • 與傳統(tǒng)構(gòu)建工具對比
          • 兼容性
          • 未來

          背景

          這里的背景介紹會(huì)從與Vite緊密相關(guān)的兩個(gè)概念的發(fā)展史說起,一個(gè)是JavaScript的模塊化標(biāo)準(zhǔn),另一個(gè)是前端構(gòu)建工具。

          共存的模塊化標(biāo)準(zhǔn)

          為什么JavaScript會(huì)有多種共存的模塊化標(biāo)準(zhǔn)?因?yàn)閖s在設(shè)計(jì)之初并沒有模塊化的概念,隨著前端業(yè)務(wù)復(fù)雜度不斷提高,模塊化越來越受到開發(fā)者的重視,社區(qū)開始涌現(xiàn)多種模塊化解決方案,它們相互借鑒,也爭議不斷,形成多個(gè)派系,從CommonJS開始,到ES6正式推出ES Modules規(guī)范結(jié)束,所有爭論,終成歷史,ES Modules也成為前端重要的基礎(chǔ)設(shè)施。

          • CommonJS:現(xiàn)主要用于Node.js([email protected]開始支持直接使用ES Module)
          • AMDrequire.js 依賴前置,市場存量不建議使用
          • CMDsea.js 就近執(zhí)行,市場存量不建議使用
          • ES Module:ES語言規(guī)范,標(biāo)準(zhǔn),趨勢,未來

          對模塊化發(fā)展史感興趣的可以看下《前端模塊化開發(fā)那點(diǎn)歷史》@玉伯[2],而Vite的核心正是依靠瀏覽器對ES Module規(guī)范的實(shí)現(xiàn)。

          發(fā)展中的構(gòu)建工具

          近些年前端工程化發(fā)展迅速,各種構(gòu)建工具層出不窮,目前Webpack仍然占據(jù)統(tǒng)治地位,npm 每周下載量達(dá)到兩千多萬次。下面是我按 npm 發(fā)版時(shí)間線列出的開發(fā)者比較熟知的一些構(gòu)建工具。

          當(dāng)前工程化痛點(diǎn)

          現(xiàn)在常用的構(gòu)建工具如Webpack,主要是通過抓取-編譯-構(gòu)建整個(gè)應(yīng)用的代碼(也就是常說的打包過程),生成一份編譯、優(yōu)化后能良好兼容各個(gè)瀏覽器的的生產(chǎn)環(huán)境代碼。在開發(fā)環(huán)境流程也基本相同,需要先將整個(gè)應(yīng)用構(gòu)建打包后,再把打包后的代碼交給dev server(開發(fā)服務(wù)器)。

          Webpack等構(gòu)建工具的誕生給前端開發(fā)帶來了極大的便利,但隨著前端業(yè)務(wù)的復(fù)雜化,js代碼量呈指數(shù)增長,打包構(gòu)建時(shí)間越來越久,dev server(開發(fā)服務(wù)器)性能遇到瓶頸:

          • 緩慢的服務(wù)啟動(dòng): 大型項(xiàng)目中dev server啟動(dòng)時(shí)間達(dá)到幾十秒甚至幾分鐘。

          • 緩慢的HMR熱更新: 即使采用了 HMR 模式,其熱更新速度也會(huì)隨著應(yīng)用規(guī)模的增長而顯著下降,已達(dá)到性能瓶頸,無多少優(yōu)化空間。

          緩慢的開發(fā)環(huán)境,大大降低了開發(fā)者的幸福感,在以上背景下Vite應(yīng)運(yùn)而生。


          什么是Vite?

          基于esbuild與Rollup,依靠瀏覽器自身ESM編譯功能, 實(shí)現(xiàn)極致開發(fā)體驗(yàn)的新一代構(gòu)建工具!

          概念

          先介紹以下文中會(huì)經(jīng)常提到的一些基礎(chǔ)概念:

          • 依賴: 指開發(fā)不會(huì)變動(dòng)的部分(npm包、UI組件庫),esbuild進(jìn)行預(yù)構(gòu)建。
          • 源碼: 瀏覽器不能直接執(zhí)行的非js代碼(.jsx、.css、.vue等),vite只在瀏覽器請求相關(guān)源碼的時(shí)候進(jìn)行轉(zhuǎn)換,以提供ESM源碼。

          開發(fā)環(huán)境

          • 利用瀏覽器原生的ES Module編譯能力,省略費(fèi)時(shí)的編譯環(huán)節(jié),直給瀏覽器開發(fā)環(huán)境源碼,dev server只提供輕量服務(wù)。
          • 瀏覽器執(zhí)行ESM的import時(shí),會(huì)向dev server發(fā)起該模塊的ajax請求,服務(wù)器對源碼做簡單處理后返回給瀏覽器。
          • Vite中HMR是在原生 ESM 上執(zhí)行的。當(dāng)編輯一個(gè)文件時(shí),Vite 只需要精確地使已編輯的模塊失活,使得無論應(yīng)用大小如何,HMR 始終能保持快速更新。
          • 使用esbuild處理項(xiàng)目依賴,esbuild使用go編寫,比一般node.js編寫的編譯器快幾個(gè)數(shù)量級。

          生產(chǎn)環(huán)境

          • 集成Rollup打包生產(chǎn)環(huán)境代碼,依賴其成熟穩(wěn)定的生態(tài)與更簡潔的插件機(jī)制。

          處理流程對比

          Webpack通過先將整個(gè)應(yīng)用打包,再將打包后代碼提供給dev server,開發(fā)者才能開始開發(fā)。

          Vite直接將源碼交給瀏覽器,實(shí)現(xiàn)dev server秒開,瀏覽器顯示頁面需要相關(guān)模塊時(shí),再向dev server發(fā)起請求,服務(wù)器簡單處理后,將該模塊返回給瀏覽器,實(shí)現(xiàn)真正意義的按需加載。


          基本用法

          創(chuàng)建vite項(xiàng)目

          $ npm create vite@latest

          選取模板

          Vite 內(nèi)置6種常用模板與對應(yīng)的TS版本,可滿足前端大部分開發(fā)場景,可以點(diǎn)擊下列表格中模板直接在 StackBlitz[3] 中在線試用,還有其他更多的 社區(qū)維護(hù)模板[4]可以使用。

          JavaScript TypeScript
          vanilla vanilla-ts
          vue vue-ts
          react react-ts
          preact preact-ts
          lit lit-ts
          svelte svelte-ts

          啟動(dòng)

          {
            "scripts": {
              "dev""vite"// 啟動(dòng)開發(fā)服務(wù)器,別名:`vite dev`,`vite serve`
              "build""vite build"// 為生產(chǎn)環(huán)境構(gòu)建產(chǎn)物
              "preview""vite preview" // 本地預(yù)覽生產(chǎn)構(gòu)建產(chǎn)物
            }
          }

          實(shí)現(xiàn)原理

          ESbuild 編譯

          esbuild 使用go編寫,cpu密集下更具性能優(yōu)勢,編譯速度更快,以下摘自官網(wǎng)的構(gòu)建速度對比:
          瀏覽器:“開始了嗎?”
          服務(wù)器:“已經(jīng)結(jié)束了?!?/strong>
          開發(fā)者:“好快,好喜歡!!”

          image.png

          依賴預(yù)構(gòu)建

          • 模塊化兼容: 如開頭背景所寫,現(xiàn)仍共存多種模塊化標(biāo)準(zhǔn)代碼,Vite在預(yù)構(gòu)建階段將依賴中各種其他模塊化規(guī)范(CommonJS、UMD)轉(zhuǎn)換 成ESM,以提供給瀏覽器。
          • 性能優(yōu)化: npm包中大量的ESM代碼,大量的import請求,會(huì)造成網(wǎng)絡(luò)擁塞。Vite使用esbuild,將有大量內(nèi)部模塊的ESM關(guān)系轉(zhuǎn)換成單個(gè)模塊,以減少 import模塊請求次數(shù)。

          按需加載

          • 服務(wù)器只在接受到import請求的時(shí)候,才會(huì)編譯對應(yīng)的文件,將ESM源碼返回給瀏覽器,實(shí)現(xiàn)真正的按需加載。

          緩存

          • HTTP緩存: 充分利用http緩存做優(yōu)化,依賴(不會(huì)變動(dòng)的代碼)部分用max-age,immutable 強(qiáng)緩存,源碼部分用304協(xié)商緩存,提升頁面打開速度。
          • 文件系統(tǒng)緩存: Vite在預(yù)構(gòu)建階段,將構(gòu)建后的依賴緩存到node_modules/.vite ,相關(guān)配置更改時(shí),或手動(dòng)控制時(shí)才會(huì)重新構(gòu)建,以提升預(yù)構(gòu)建速度。

          重寫模塊路徑

          瀏覽器import只能引入相對/絕對路徑,而開發(fā)代碼經(jīng)常使用npm包名直接引入node_module中的模塊,需要做路徑轉(zhuǎn)換后交給瀏覽器。

          • es-module-lexer 掃描 import 語法
          • magic-string 重寫模塊的引入路徑
          // 開發(fā)代碼
          import { createApp } from 'vue'

          // 轉(zhuǎn)換后
          import { createApp } from '/node_modules/vue/dist/vue.js'

          源碼分析

          Webpack-dev-server類似Vite同樣使用WebSocket與客戶端建立連接,實(shí)現(xiàn)熱更新,源碼實(shí)現(xiàn)基本可分為兩部分,源碼位置在:

          • vite/packages/vite/src/client client(用于客戶端)
          • vite/packages/vite/src/node server(用于開發(fā)服務(wù)器)

          client 代碼會(huì)在啟動(dòng)服務(wù)時(shí)注入到客戶端,用于客戶端對于WebSocket消息的處理(如更新頁面某個(gè)模塊、刷新頁面);server 代碼是服務(wù)端邏輯,用于處理代碼的構(gòu)建與頁面模塊的請求。

          簡單看了下源碼([email protected]),核心功能主要是以下幾個(gè)方法(以下為源碼截取,部分邏輯做了刪減):

          1. 命令行啟動(dòng)服務(wù)npm run dev后,源碼執(zhí)行cli.ts,調(diào)用createServer方法,創(chuàng)建http服務(wù),監(jiān)聽開發(fā)服務(wù)器端口。
          // 源碼位置 vite/packages/vite/src/node/cli.ts
          const { createServer } = await import('./server')
          try {
              const server = await createServer({
                  root,
                  base: options.base,
                  ...
              })
              if (!server.httpServer) {
                  throw new Error('HTTP server not available')
              }
              await server.listen()
          }
          1. createServer方法的執(zhí)行做了很多工作,如整合配置項(xiàng)、創(chuàng)建http服務(wù)(早期通過koa創(chuàng)建)、創(chuàng)建WebSocket服務(wù)、創(chuàng)建源碼的文件監(jiān)聽、插件執(zhí)行、optimize優(yōu)化等。下面注釋中標(biāo)出。
          // 源碼位置 vite/packages/vite/src/node/server/index.ts
          export async function createServer(
              inlineConfig: InlineConfig = {}
          ): Promise<ViteDevServer
          {
              // Vite 配置整合
              const config = await resolveConfig(inlineConfig, 'serve''development')
              const root = config.root
              const serverConfig = config.server

              // 創(chuàng)建http服務(wù)
              const httpServer = await resolveHttpServer(serverConfig, middlewares, httpsOptions)

              // 創(chuàng)建ws服務(wù)
              const ws = createWebSocketServer(httpServer, config, httpsOptions)

              // 創(chuàng)建watcher,設(shè)置代碼文件監(jiān)聽
              const watcher = chokidar.watch(path.resolve(root), {
                  ignored: [
                      '**/node_modules/**',
                      '**/.git/**',
                      ...(Array.isArray(ignored) ? ignored : [ignored])
                  ],
                  ...watchOptions
              }) as FSWatcher

              // 創(chuàng)建server對象
              const server: ViteDevServer = {
                  config,
                  middlewares,
                  httpServer,
                  watcher,
                  ws,
                  moduleGraph,
                  listen,
                  ...
              }

              // 文件監(jiān)聽變動(dòng),websocket向前端通信
              watcher.on('change'async (file) => {
                  ...
                  handleHMRUpdate()
              })

              // 非常多的 middleware
              middlewares.use(...)
              
              // optimize
              const runOptimize = async () => {...}

              return server
          }
          1. 使用chokidar[5]監(jiān)聽文件變化,綁定監(jiān)聽事件。
          // 源碼位置 vite/packages/vite/src/node/server/index.ts
            const watcher = chokidar.watch(path.resolve(root), {
              ignored: [
                '**/node_modules/**',
                '**/.git/**',
                ...(Array.isArray(ignored) ? ignored : [ignored])
              ],
              ignoreInitialtrue,
              ignorePermissionErrorstrue,
              disableGlobbingtrue,
              ...watchOptions
            }) as FSWatcher
          1. 通過 ws[6] 來創(chuàng)建WebSocket服務(wù),用于監(jiān)聽到文件變化時(shí)觸發(fā)熱更新,向客戶端發(fā)送消息。
          // 源碼位置 vite/packages/vite/src/node/server/ws.ts
          export function createWebSocketServer(...){
              let wss: WebSocket
              const hmr = isObject(config.server.hmr) && config.server.hmr
              const wsServer = (hmr && hmr.server) || server

              if (wsServer) {
                  wss = new WebSocket({ noServertrue })
                  wsServer.on('upgrade', (req, socket, head) => {
                      // 服務(wù)就緒
                      if (req.headers['sec-websocket-protocol'] === HMR_HEADER) {
                          wss.handleUpgrade(req, socket as Socket, head, (ws) => {
                              wss.emit('connection', ws, req)
                          })
                      }
                  })
              } else {
                  ...
              }
              // 服務(wù)準(zhǔn)備就緒,就能在瀏覽器控制臺(tái)看到熟悉的打印 [vite] connected.
              wss.on('connection', (socket) => {
                  socket.send(JSON.stringify({ type'connected' }))
                  ...
              })
              // 失敗
              wss.on('error', (e: Error & { code: string }) => {
                  ...
              })
              // 返回ws對象
              return {
                  on: wss.on.bind(wss),
                  off: wss.off.bind(wss),
                  // 向客戶端發(fā)送信息
                  // 多個(gè)客戶端同時(shí)觸發(fā)
                  send(payload: HMRPayload) {
                      const stringified = JSON.stringify(payload)
                      wss.clients.forEach((client) => {
                          // readyState 1 means the connection is open
                          client.send(stringified)
                      })
                  }
              }
          }
          1. 在服務(wù)啟動(dòng)時(shí)會(huì)向?yàn)g覽器注入代碼,用于處理客戶端接收到的WebSocket消息,如重新發(fā)起模塊請求、刷新頁面。
          //源碼位置 vite/packages/vite/src/client/client.ts
          async function handleMessage(payload: HMRPayload{
            switch (payload.type) {
              case 'connected':
                console.log(`[vite] connected.`)
                break
              case 'update':
                notifyListeners('vite:beforeUpdate', payload)
                ...
                break
              case 'custom': {
                notifyListeners(payload.event as CustomEventName<any>, payload.data)
                ...
                break
              }
              case 'full-reload':
                notifyListeners('vite:beforeFullReload', payload)
                ...
                break
              case 'prune':
                notifyListeners('vite:beforePrune', payload)
                ...
                break
              case 'error': {
                notifyListeners('vite:error', payload)
                ...
                break
              }
              default: {
                const check: never = payload
                return check
              }
            }
          }

          優(yōu)勢

          • 快!快!非??欤?!
          • 高度集成,開箱即用。
          • 基于ESM急速熱更新,無需打包編譯。
          • 基于esbuild的依賴預(yù)處理,比Webpack等node編寫的編譯器快幾個(gè)數(shù)量級。
          • 兼容Rollup龐大的插件機(jī)制,插件開發(fā)更簡潔。
          • 不與Vue綁定,支持React等其他框架,獨(dú)立的構(gòu)建工具。
          • 內(nèi)置SSR支持。
          • 天然支持TS。

          不足

          • Vue仍為第一優(yōu)先支持,量身定做的編譯插件,對React的支持不如Vue強(qiáng)大。
          • 雖然已經(jīng)推出2.0正式版,已經(jīng)可以用于正式線上生產(chǎn),但目前市場上實(shí)踐少。
          • 生產(chǎn)環(huán)境集成Rollup打包,與開發(fā)環(huán)境最終執(zhí)行的代碼不一致。

          與 webpack 對比

          由于Vite主打的是開發(fā)環(huán)境的極致體驗(yàn),生產(chǎn)環(huán)境集成Rollup,這里的對比主要是Webpack-dev-serverVite-dev-server的對比:

          • 到目前很長時(shí)間以來Webpack在前端工程領(lǐng)域占統(tǒng)治地位,Vite推出以來備受關(guān)注,社區(qū)活躍,GitHub star 數(shù)量激增,目前達(dá)到37.4K
          • Webpack配置豐富使用極為靈活但上手成本高,Vite開箱即用配置高度集成
          • Webpack啟動(dòng)服務(wù)需打包構(gòu)建,速度慢,Vite免編譯可秒開
          • Webpack熱更新需打包構(gòu)建,速度慢,Vite毫秒響應(yīng)
          • Webpack成熟穩(wěn)定、資源豐富、大量實(shí)踐案例,Vite實(shí)踐較少
          • Vite使用esbuild編譯,構(gòu)建速度比webpack快幾個(gè)數(shù)量級

          兼容性

          • 默認(rèn)目標(biāo)瀏覽器是在script標(biāo)簽上支持原生 ESM 和 原生 ESM 動(dòng)態(tài)導(dǎo)入
          • 可使用官方插件 @vitejs/plugin-legacy,轉(zhuǎn)義成傳統(tǒng)版本和相對應(yīng)的polyfill

          未來探索

          • 傳統(tǒng)構(gòu)建工具性能已到瓶頸,主打開發(fā)體驗(yàn)的Vite,可能會(huì)受到歡迎。
          • 主流瀏覽器基本支持ESM,ESM將成為主流。
          • ViteVue3.0代替vue-cli,作為官方腳手架,會(huì)大大提高使用量。
          • Vite2.0推出后,已可以在實(shí)際項(xiàng)目中使用Vite。
          • 如果覺得直接使用Vite太冒險(xiǎn),又確實(shí)有dev server速度慢的問題需要解決,可以嘗試用Vite單獨(dú)搭建一套dev server

          相關(guān)資源

          官方插件

          除了支持現(xiàn)有的Rollup插件系統(tǒng)外,官方提供了四個(gè)最關(guān)鍵的插件

          • @vitejs/plugin-vue 提供 Vue3 單文件組件支持
          • @vitejs/plugin-vue-jsx  提供 Vue3 JSX 支持(專用的 Babel 轉(zhuǎn)換插件)
          • @vitejs/plugin-react 提供完整的 React 支持
          • @vitejs/plugin-legacy 為打包后的文件提供傳統(tǒng)瀏覽器兼容性支持

          UI組件庫

          • Element UI[7]:支持 vite 引入

          相關(guān)鏈接

          • Vite官網(wǎng)[8]
          • Vue3 生態(tài)進(jìn)展和計(jì)劃-尤雨溪[9]
          • Vite源碼解析[10]
          • Develop with Vite | Vite快速入門 - Anthony Fu ? Vue北京聚會(huì) Day 13[11]

               
          Node 社群



          我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。



          如果你覺得這篇內(nèi)容對你有幫助,我想請你幫我2個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多人也能看到這篇文章
          2. 訂閱官方博客 www.inode.club 讓我們一起成長

          點(diǎn)贊和在看就是最大的支持??

          參考資料

          [1]

          《Vue3 生態(tài)進(jìn)展和計(jì)劃》: https://www.yuque.com/vueconf/mkwv0c/xqyxix

          [2]

          《前端模塊化開發(fā)那點(diǎn)歷史》: https://github.com/seajs/seajs/issues/588

          [3]

          StackBlitz: https://vite.new/

          [4]

          社區(qū)維護(hù)模板: https://github.com/vitejs/awesome-vite#templates

          [5]

          chokidar: https://www.npmjs.com/package/chokidar

          [6]

          ws: https://www.npmjs.com/package/ws

          [7]

          Element UI: https://element-plus.gitee.io/zh-CN/guide/quickstart.html#%E6%8C%89%E9%9C%80%E5%AF%BC%E5%85%A5

          [8]

          Vite官網(wǎng): https://cn.vitejs.dev/

          [9]

          Vue3 生態(tài)進(jìn)展和計(jì)劃-尤雨溪: https://www.yuque.com/vueconf/mkwv0c/xqyxix

          [10]

          Vite源碼解析: http://vite.ssr-fc.com/

          [11]

          Develop with Vite | Vite快速入門 - Anthony Fu ? Vue北京聚會(huì) Day 13: https://www.youtube.com/watch?v=xx8gEHet6n8


          瀏覽 47
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线观看亚洲无码视频 | 男女高清无码 | 91在线成人免费视频 | 亚洲欧美人妻 | 中文字幕成人在线观看 |