banner
AgedCoffee

AgedCoffee

元構建工具vite初探

寫在前面#

vite是一種全新的前端構建工具,在構建工具之前加上這個字在這個元宇宙(metaverse)的浪潮下多一個 meta 就好似加持了某種神秘的魔力。

由於 react 在 18 中引入了Server-Side Streaming, React Server Components 的特性,Vue 框架的作者尤雨溪同時也是 Vite 的作者就戲稱 React 已經
是 Meta Framework。

當然我稱 Vite 為元構建工具也有名副其實的含義

  • meta 這個詞取自於希臘語,本身帶有超越的含義,而 vite 的出現本身就有超越先階段專案都基於 webpack 構建這樣的現狀的意味。
  • 在中文神經元之類的詞語中元本身還有最小功能單元的意思,而 vite 相較於 webpack 的構建方案,最大的特點之一就是在開發階段不需要對源碼全部打包為一個 bundle 給瀏覽器運行,而是基於 esm 直接交給現代瀏覽器運行處理,而一個 esm 便可以理解為那個最小的功能單元。

言歸正傳,之所以會有將專案改造為 vite 驅動的想法,是因為在開發專案代碼量和文件數目變多的情況下,明顯感覺 webpack 下的 dev 環境每次冷啟動和熱加載的速度變慢,另外自己早些時候也聽聞過 vite 絲滑急速的開發體驗,便躍躍欲試。

接下來我們就主要講一講將現有的基於 webpack 的 react+antd 的專案改造為 vite 驅動的過程

package.json 的大瘦身#

入口 index.html 文件調整#

index.html 在專案最外層而不在 public 文件夾內

webpack

<!DOCTYPE html>
<html>
  <head>
    <link rel="icon" href="%PUBLIC_URL%/logo.ico" />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root" style="height: 100%"></div>
  </body>
</html>

vite

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="icon" href="/src/assets/logo.ico" />
  </head>
  <body>
    <div id="root"></div>
    <!-- vite 特有的 esm 入口文件 -->
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

react 以及 typescript 支持#

官方插件@vitejs/plugin-react提供完整的 react 支持

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://cn.vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
})

vite 內置了 esbuild 對 ts 文件的編譯處理 `,只需要在專案提供自己的 tsconfig 文件

less 以及 module-css 支持#

vite 內置對 less 以及 module-css 的支持,安裝 less 預處理依賴即可
由於 antd 的 less 使用了 less 的函數功能需要在 preprocessorOptions 開啟 javascriptEnabled 配置

export default defineConfig({
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true,
      },
    },
  },
})

專案的基礎 server 及代理配置#

export default defineConfig({
  server: {
    port: 3000,
    host: 'localhost',
    open: true,
    https: true,
    hmr: {
      host: 'localhost',
    },
    proxy: {
      '/api': "https://tartget-server",
      '/special/api': {
        target: "https://special-tartget-server",,
        changeOrigin: true,
        rewrite: path => path.replace(/^\/special/, ''),
      },
    },
  },
});

絕對路徑的 alisa 配置#

  • 在專案中我們一般使用絕對路徑引入模塊,用 @來替換根文件夾下的 src 文件夾路徑
  • 由於部分依賴的 node_modules 本身含有 @符,這樣的 import 不應該被匹配
  • 部分舊的 less 的 import 使用了~表示絕對引入的方式,這樣的引入需要做特殊的處理
export default defineConfig({
  resolve: {
    alias: [
      {
        find: /^@\//,
        replacement: `${path.resolve(__dirname, 'src')}${path.sep}`,
      },
      { find: /^~/, replacement: '' },
    ],
  },
})

靜態文件的引入#

一般的靜態資源引入 vite 本身支持了多種方案
在遷移過程中主要處理了一種需要根據變量拼接靜態資源的引用處理邏輯
由於不在 webpack 環境下無法使用 require 方法,需要用到 vite 這邊的 esm 原生功能import.meta.url

// 根據short動態拼接出國旗資源的圖片路徑
const getFlag = (short: string) => {
  return new URL(`./flags/${short.toLowerCase()}.png`, import.meta.url).href
}

環境變量的配置#

引入 dotenv 相關依賴,啟動命令添加 dotenv

{
  "scripts": {
    "dev": "dotenv -e .env.dev vite",
    "build": "tsc && dotenv -e .env.dev vite build",
    "serve": "vite preview"
  },
  "devDependencies": {
    "dotenv": "^8.2.0",
    "dotenv-cli": "^4.0.0",
    "dotenv-expand": "^5.1.0"
  }
}

在 vite 中需要遵守約定的前綴規範,VITE_

VITE_VAR=SOME_VALUE

在 src 中定義用於靜態校驗的 ts 文件env.d.ts

interface ImportMetaEnv extends Readonly<Record<string, string>> {
  readonly VITE_VAR: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

manual-chunk 打包優化邏輯#

在專案中使用懶加載 React-Router 路由讓代碼自動 split 一般可以解決大部分場景
在代碼分割仍然不合理的情況下可以在配置中添加手動處理 chunk 的生成邏輯

import path from 'path'
import { dependencies } from './package.json'

function renderChunks(deps) {
  let chunks = {}
  Object.keys(deps).forEach((key) => {
    if (key.includes('@types')) return
    if (['react', 'react-router-dom', 'react-dom'].includes(key)) return
    chunks[key] = [key]
  })
  return chunks
}

export default defineConfig({
  // 在 build 之下的邏輯只有生產打包會運行
  build: {
    sourcemap: false,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-router-dom', 'react-dom'],
          ...renderChunks(dependencies),
        },
      },
    },
  },
})

奇怪的踩坑#

  • moment 國際化包需要按照不一樣的方式引入生效
// webpack
import 'moment/locale/zh-cn'
// vite
import 'moment/dist/locale/zh-cn'
  • 部分依賴包在 webpack 生產環境沒有問題的包,在 vite 會報錯
    react-response 這個包在生產環境下會報錯 global 不存在,需要在 script 標籤中添加容錯邏輯
<script>
  if (global === undefined) {
    var global = window
  }
</script>

完整的 vite 配置#

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import { dependencies } from './package.json';

function renderChunks(deps) {
  let chunks = {};
  Object.keys(deps).forEach(key => {
    if (key.includes('@types')) return;
    if (['react', 'react-router-dom', 'react-dom'].includes(key)) return;
    chunks[key] = [key];
  });
  return chunks;
}

export default defineConfig({
  plugins: [
    react(),
  ],
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true,
      },
    },
  },
  server: {
    port: 3000,
    host: 'localhost',
    open: true,
    https: true,
    hmr: {
      host: 'localhost',
    },
    proxy: {
      '/api': "https://tartget-server",
      '/special/api': {
        target: "https://special-tartget-server",,
        changeOrigin: true,
        rewrite: path => path.replace(/^\/special/, ''),
      },
    },
  },
  resolve: {
    alias: [
      {
        find: /^@\//,
        replacement: `${path.resolve(__dirname, 'src')}${path.sep}`,
      },
      { find: /^~/, replacement: '' },
    ],
  },
  build: {
    sourcemap: false,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-router-dom', 'react-dom'],
          ...renderChunks(dependencies),
        },
      },
    },
  },
});

寫在最後#

  1. 遷移過後的專案在最新的 chrome 跑已經沒有問題,但對於一些舊瀏覽器的支持還需要驗證。
    (vite 本身提供了舊瀏覽器的支持插件)

  2. 由於vite在開發環境下和生產環境下兩種構築方式上還是存在一定的差異性,對於最終的線上部署可能會帶來一定的困擾。

  3. 另外針對遷移的專案對於 antd 的樣式引用方式是全量的引入了 less 的源碼,加上了自己的 less 主題變量覆蓋,由於瀏覽器不支持less文件的運行,vite任然需要對less文件進行完整的編譯之後專案才可以啟動,在less文件多的情況下編譯速度較慢 ,之後可以配置 less 的按需引用,以及 less 主題變量的插件注入。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。