天天看點

[項目實戰] Webpack to Vite, 為開發提速!正文最後

背景

最近,就

前端開發過程中的痛點及可優化項

做了一次收集。 其中,

建構耗時、項目編譯速度慢

的字眼出現了好幾次。

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

提升研發效率

,是技術人永恒的追求。

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

解決啟動耗時的問題

于昨天下午(2021.4.7 23:00), 成功嵌入 Vite, 項目啟動時間由約

190s => 20s

, 熱更新時間縮短為

2s

[項目實戰] Webpack to Vite, 為開發提速!正文最後

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

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

今天的主要内容:

  • 為什麼 Vite 啟動這麼快

  • 我的項目如何植入 Vite

  • 改造過程中遇到的問題以及解決方式

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

  • 相關代碼和結論

正文

為什麼 Vite 啟動這麼快

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

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

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

另外, 兩者的

啟動方式

也有所差異。

webpack 啟動方式

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

Vite 啟動方式

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

Webpack 會

先打包

,然後啟動開發伺服器,請求伺服器時直接給予打包結果。

而 Vite 是

直接啟動

開發伺服器,請求哪個子產品再對該子產品進行

實時編譯

由于現代浏覽器本身就支援 ES Module,會自動向依賴的 Module 送出請求。

Vite 充分利用了這一點,将開發環境下的子產品檔案,就作為浏覽器要執行的檔案,而不是像 W ebpack 那樣進行

打包合并

由于 Vite 在啟動的時候

不需要打包

,也就意味着

不需要分析子產品的依賴

不需要編譯

。是以啟動速度非常快。當浏覽器請求某個子產品時,再根據需要對子產品内容進行編譯。

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

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

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

我的項目如何植入 Vite

新項目

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

yarn create @vitejs/app
           
[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

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

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

已有項目

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

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

wp2vite

.

安裝好之後, 直接執行:

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

這一步, 會自動生成 Vite 的配置檔案,并引入相關的依賴。

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

如果沒有意外的話, 你會

收獲一堆報錯

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

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

1. alias 錯誤

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

項目代碼裡配置了一些别名,vite 無法識别,是以需要在vite 裡面也配置 alias:

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

2. 無法識别 less 全局變量

[項目實戰] Webpack to Vite, 為開發提速!正文最後

解決辦法:

把自定義的全局變量從外部注入即可, 直接在

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.

[項目實戰] Webpack to Vite, 為開發提速!正文最後

根元素未找到。

原因是:預設生成的 index.html 中:

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

id 是 root, 而邏輯中的是

#app

, 這裡直接改成

id=app

即可。

4. typings 檔案找不到

[項目實戰] Webpack to Vite, 為開發提速!正文最後

typings 檔案未找到

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

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

源代碼:

[項目實戰] Webpack to Vite, 為開發提速!正文最後

編譯後:

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

[項目實戰] Webpack to Vite, 為開發提速!正文最後

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, 于是把代碼改為:

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

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

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

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

至此,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 />

           

浏覽器報錯:

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

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

作為字尾。

看了一下源碼, 這個字尾是用來作為辨別符的,

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

如果字尾比對上是

component

,  就解析檔案, 并緩存, 最後傳回結果:

[項目實戰] Webpack to Vite, 為開發提速!正文最後

image.png

知道原理之後, 就需要把全部的

.svg

=>

.svg?component

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

6. global 未定義

[項目實戰] Webpack to Vite, 為開發提速!正文最後

global

是 Node裡面的變量, 會在用戶端報錯 ?

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

看 vite 文檔裡提到了 Client Types:

[項目實戰] Webpack to Vite, 為開發提速!正文最後

追加到

tsconfig

裡面:

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

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

[項目實戰] Webpack to Vite, 為開發提速!正文最後

沒辦法, 隻得祭出

window

大法。

在入口index.tsx 裡面加上:

(window as any).global = window;
           

重新整理, 好了。

[項目實戰] Webpack to Vite, 為開發提速!正文最後

7. [未解決] 替代HtmlWebpackPlugin

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

找到一個插件:

vite-plugin-singlefile

不過并沒有什麼用。

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

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

7. 線上打包建構時, 記憶體溢出

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

立刻安排!

[項目實戰] Webpack to Vite, 為開發提速!正文最後

記憶體不足, 我就給你加點:

[項目實戰] Webpack to Vite, 為開發提速!正文最後
[項目實戰] Webpack to Vite, 為開發提速!正文最後

搞定!

[項目實戰] Webpack to Vite, 為開發提速!正文最後

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

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

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

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

  • 保留 webpack dev & build 的能力,

    vite 僅作為開發的輔助

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

相關代碼和結論

一個完整的 Vite demo

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

[項目實戰] Webpack to Vite, 為開發提速!正文最後

業務項目的 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 能繼續完善,為研發提效。

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

才疏學淺,如有錯誤, 歡迎指正。

謝謝。

[項目實戰] Webpack to Vite, 為開發提速!正文最後

END