[項目實戰] Webpack to Vite, 爲開發提速!

背景

最近,就 前端開發過程中的痛點及可優化項 做了一次收集。 其中,構建耗時、項目編譯速度慢 的字眼出現了好幾次。

隨着業務的快速發展,我們很多項目的體積也快速膨脹。隨之而來的, 就是打包變慢等問題。

提升研發效率,是技術人永恆的追求。

我們項目也有啓動慢的問題,同事也提到過幾次。剛好我之前也做過類似的探索和優化, 於是就借這個機會,改造一下項目, 解決啓動耗時的問題

於昨天下午 (2021.4.7 23:00), 成功嵌入 Vite, 項目啓動時間由約 190s => 20s, 熱更新時間縮短爲 2s

中間踩了一些坑, 好在最後爬出來了, 相關技術要點都會在下文中呈現。

FBI Warning:以下文字,只是我結合自己的實際項目, 總結出來的一些淺薄的經驗, 如有錯誤,歡迎指正 :)

今天的主要內容:

正文

=====

爲什麼 Vite 啓動這麼快

底層實現上, Vite 是基於 esbuild 預構建依賴的。

esbuild 使用 go 編寫,並且比以 js 編寫的打包器預構建依賴, 快 10 - 100 倍。

因爲 js 跟 go 相比實在是太慢了,js 的一般操作都是毫秒計,go 則是納秒。

另外, 兩者的啓動方式也有所差異。

webpack 啓動方式

Vite 啓動方式

Webpack 會先打包,然後啓動開發服務器,請求服務器時直接給予打包結果。

而 Vite 是直接啓動開發服務器,請求哪個模塊再對該模塊進行實時編譯

由於現代瀏覽器本身就支持 ES Module,會自動向依賴的 Module 發出請求。

Vite 充分利用了這一點,將開發環境下的模塊文件,就作爲瀏覽器要執行的文件,而不是像 W ebpack 那樣進行打包合併

由於 Vite 在啓動的時候不需要打包,也就意味着不需要分析模塊的依賴不需要編譯。因此啓動速度非常快。當瀏覽器請求某個模塊時,再根據需要對模塊內容進行編譯。

這種按需動態編譯的方式,極大的縮減了編譯時間,項目越複雜、模塊越多,vite 的優勢越明顯。

在 HMR(熱更新)方面,當改動了一個模塊後,僅需讓瀏覽器重新請求該模塊即可,不像 webpack 那樣需要把該模塊的相關依賴模塊全部編譯一次,效率更高。

從實際的開發體驗來看, 在 Vite 模式下, 開發環境可以瞬間啓動, 但是等到頁面出來, 要等一段時間。

我的項目如何植入 Vite

新項目

創建一個 Vite 新項目就比較簡單:

yarn create @vitejs/app

生成好之後, 直接啓動就可以了:

已有項目

已有項目的遷移, 稍微繁瑣一些。

首先, 加入 Vite 的相關配置。這裏我使用了一個 cli 工具:wp2vite.

安裝好之後, 直接執行:

這一步, 會自動生成 Vite 的配置文件,並引入相關的依賴。

把依賴安裝一下, 啓動就可以了。

如果沒有意外的話, 你會收穫一堆報錯

恭喜你,進入開心愉快的踩坑環節。

我在改造過程中遇到的問題

1. alias 錯誤

項目代碼裏配置了一些別名,vite 無法識別,所以需要在 vite 裏面也配置 alias:

  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },

2. 無法識別 less 全局變量

解決辦法:

把自定義的全局變量從外部注入即可, 直接在 vite.config.js 的 css 選項中加入:

  css: {
    preprocessorOptions: {
      less: {
        modifyVars: {
          hack: `true;@import '${resolve('./src/vars.less')}';`,
          ...themeVariables,
        },
        javascriptEnabled: true,
      },
    },
  },

3. Uncaught Error: Target container is not a DOM element.

根元素未找到。

原因是:默認生成的 index.html 中:

<div id="root"></div>

id 是 root, 而邏輯中的是#app, 這裏直接改成 id=app 即可。

4. typings 文件找不到

typings 文件未找到

這個錯誤, 乍一看, 一頭霧水。

進去看一下源代碼和編譯後的代碼:

源代碼:

編譯後:

typings 文件這不是好好的在這嗎, 怎麼就找不到?

想了一下:Vite 不知道 typeings 文件是不需要被編譯的,需要告訴編譯器不編譯這個文件。

最後在 TS 官方文檔裏找到了答案:

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html

Type-Only Imports and Export

This feature is something most users may never have to think about; however, if you’ve hit issues under --isolatedModules, TypeScript’s transpileModule API, or Babel, this feature might be relevant.

TypeScript 3.8 adds a new syntax for type-only imports and exports.

import type { SomeThing } from "./some-module.js";
export type { SomeThing };

需要單獨引入 types, 於是把代碼改爲:

同時要注意, 如果一個文件有有多個導出, 也要分開引入:

唯一痛苦的是: 全局都需要改一遍, 體力活。

至此,typeings 問題完美解決。

5. 無法識別 svg

我們在使用 svg 作爲圖標組件的時候, 一般是:

import Icon from '@ant-design/icons';
import ErrorSvg from '@/assets/ico_error.svg';

const ErrorIcon = (props: any) => <Icon component={ErrorSvg} />;

// ...
<ErrorIcon />

瀏覽器報錯:

error occurred in the </src/assets/ico_error.svg> component

很明顯的看到, 這裏是把文件路徑作爲組件了。

現在要做的是:把這個文件路徑, 換成可以識別的組件。

搜索一番, 找到了個插件:vite-plugin-react-svg

加入配置:

const reactSvgPlugin = require('vite-plugin-react-svg');

plugins: [
  reactSvgPlugin(),
],
import MyIcon from './svgs/my-icon.svg?component';

function App() {
  return (
    <div>
      <MyIcon />
    </div>
  );
}

需要注意的是:引入的 svg 文件需要加 ?component 作爲後綴。

看了一下源碼, 這個後綴是用來作爲標識符的,

如果後綴匹配上是component,  就解析文件, 並緩存, 最後返回結果:

知道原理之後, 就需要把全部的 .svg => .svg?component

vscode 一鍵替換就可以, 不過注意別把 node_module 裏面的也替換了。

6. global 未定義

global 是 Node 裏面的變量, 會在客戶端報錯 ?

一層層看下去, 原來是引入的第三方包使用了 global。

看 vite 文檔裏提到了 Client Types:

追加到 tsconfig 裏面:

 "compilerOptions"{
    "types"["node""jest""vite/client"],
 }

然後, 並沒有什麼亂用。。。

沒辦法, 只得祭出 window 大法。

在入口 index.tsx 裏面加上:

(window as any).global = window;

刷新, 好了。

7. [未解決] 替代 HtmlWebpackPlugin

還需要注入一些外部變量, 修改入口 html, favicon, title 之類。

找到一個插件:vite-plugin-singlefile

不過並沒有什麼用。

有了解的同學請留言賜教。

至此, 整個 app 已經能在本地跑起來了, build 也沒問題。

7. 線上打包構建時, 內存溢出

本地能跑起來, 打包也沒問題, 後面當然是放到線上跑一跑啦。

立刻安排!

內存不足, 我就給你加點:

搞定!

關於 Vite 開發、打包上線的一些思考

從實際使用來看, vite 在一些功能上還是無法完全替代 webpack。

畢竟是後起之秀, 相關的生態還需要持續完善。

個人認爲,目前一種比較穩妥的方式是:

等相關工具再完善一些, 再考慮完全遷移過來。

相關代碼和結論

一個完整的 Vite demo

倉庫地址:https://github.com/beMySun/react-hooks-i18n-template/tree/test-wp2vite

業務項目的 vite.config.js 完整配置

import { defineConfig } from 'vite';
import reactRefresh from '@vitejs/plugin-react-refresh';
import legacyPlugin from '@vitejs/plugin-legacy';
import { resolve } from 'path';

const fs = require('fs');
const lessToJS = require('less-vars-to-js');
const themeVariables = lessToJS(fs.readFileSync(resolve(__dirname, './src/antd-custom.less')'utf8'));
const reactSvgPlugin = require('vite-plugin-react-svg');

// https://cn.vitejs.dev/config/
export default defineConfig({
  base: './',
  root: './',
  resolve: {
    alias: {
      'react-native''react-native-web',
      '@': resolve(__dirname, 'src'),
    },
  },
  define: {
    'process.env.REACT_APP_IS_LOCAL''\'true\'',
    'window.__CID__': JSON.stringify(process.env.cid || 'id'),
  },
  server: {
    port: 8080,
    proxy: {
      '/api': {
        target: 'https://stoku.test.shopee.co.id/',
        changeOrigin: true,
        cookieDomainRewrite: {
          'stoku.test.shopee.co.id': 'localhost',
        },
      },
    },
  },
  build: {
    target: 'es2015',
    minify: 'terser',
    manifest: false,
    sourcemap: false,
    outDir: 'build',
    rollupOptions: {},
  },
  esbuild: {},
  optimizeDeps: {},
  plugins: [
    // viteSingleFile({
    //   title: 'dynamic title', // doesn't work
    // }),
    reactSvgPlugin(),
    reactRefresh(),
    legacyPlugin({
      targets: [
        'Android > 39',
        'Chrome >= 60',
        'Safari >= 10.1',
        'iOS >= 10.3',
        'Firefox >= 54',
        'Edge >= 15',
      ],
    }),
    // vitePluginImp({
    //   libList: [
    //     {
    //       libName: 'antd',
    //       style: (name) =`antd/es/${name}/style`,
    //     },
    //   ],
    // }),
  ],
  css: {
    preprocessorOptions: {
      less: {
        modifyVars: {
          hack: `true;@import '${resolve('./src/vars.less')}';`,
          ...themeVariables,
        },
        javascriptEnabled: true,
      },
    },
  },
});

最後

使用 Vite 能大幅縮短項目構建時間,提升開發效率。

不過也要結合項目的實際情況,合理取捨。

對於我的這個項目而言,把 Vite 作爲輔助開發的一種方式,還是挺有用的。

期待 Vite 能繼續完善,爲研發提效。

好了, 內容大概就這麼多, 希望對大家有所幫助。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/MK0DPaY9hdQzgD2nyk4-4g