antd+react專案遷移vite的解決方案

福祿網路技術團隊發表於2021-04-19

antd+react+webpack往往是以react技術棧為主的前端專案的標準組合,三者都有成熟的生態和穩定的表現,但隨著前端圈的技術不斷革新,號稱下一代構建平臺vite2的釋出,webpack似乎不那麼香了,為什麼這麼說呢,因為vite太快了。經過一段時間的嘗試,決定在專案中把webpack替換成vite試試,遂寫成本文分享給大家。

Vite是什麼

作為本文的主角,首先簡單介紹一下vite這個構建工具,該工具是尤雨溪推出的【下一代前端開發和構建工具】,vite其實也不是一個新的工具,早在一年多以前,就已經推出了很多版本,直到2.x版本的推出,在前端圈引起了足夠大的震動,標誌著vite的成熟和強大,這裡並不打算詳細介紹vite,大家可以參考官網https://cn.vitejs.dev/ 瞭解。

遷移過程

瞭解了vite這款工具之後,我們就可以著手準備做遷移工作了;

1.安裝vite依賴

npm i vite antd-vite-import-plugin @vitejs/plugin-react-refresh vite-plugin-html -D

2.更新專案原有依賴項

這裡我們專案使用的是dva+antd3.x作為基礎的開發框架,這裡我將系統的主要依賴項都升級到了最新的版本,比如dva我用的2.6.0-beta.22版本,其他附帶的react、react-dom、react-router-dom及@babel/plugin-transform-runtime等相關依賴項都更新了(antd還是3.x版本,暫未更新到4.x的大版本),這一塊取決於自己的實際需求;

3.專案根目錄新增vite.config.js配置檔案

和webpack的配置檔案比起來,vite的簡單了許多,而且很多功能都是內建的,比如對靜態資源的處理,功能開啟也比較簡單,具體如下:

import { defineConfig } from 'vite';
import vitePluginHtml from 'vite-plugin-html';
import reactRefresh from '@vitejs/plugin-react-refresh';

export default defineConfig({
    css: {
        preprocessorOptions: {
            less: {
                javascriptEnabled: true,
            },
        }
    },
    publicDir: './src/configs',
    plugins: [
        reactRefresh(),
        antdViteImportPlugin(),
        vitePluginHtml({
            minify: true,
            inject: {
                injectData: {
                    title: 'vite-react-example',
                    injectScript: '<script src="/configs.js"></script>', // publicDir作為根目錄
                },
                injectOptions: {
                    filename: './index.html', // 模板頁
                }
            },
        }),
    ],
    server: {
        open: true,
        port: 10010,
    }
});

這裡我們使用了vite-plugin-html外掛作為html-webpack-plugin的替代方案,其中需要注意injectData和injectOptions選項,injectData可以方便的往我們的模板頁中插入自定義資料,injectOptions可以指定模板頁,還有其他配置項可以參考https://www.npmjs.com/package/vite-plugin-html 。相應的需要改造index.html頁面:

<!DOCTYPE html>
<head>
    <meta charset="utf-8">
    <title><%- title %></title>
    ......
</head>
<body>
    <div id="app"></div>
    <script>
        var global = globalThis || window; // 防止啟動報錯
    </script>
    <%- injectScript %>
    <script type="module" src="/src/index.jsx"></script>
</body>
</html>

和webpack有差異的是,這裡我們需要手動指定一下入口檔案,script的標籤type為module。

4.修改檔案字尾

這裡的檔案是以js作為字尾的react元件,在webpack構建平臺下,js(x)ts(x)都是沒啥問題的,但如果使用vite的話,那麼最好是tsjsxtsx 的字尾檔案,關於這個問題,可以看下這個issue:https://github.com/vitejs/vite/issues/1552 ,最後作者發出批量改個字尾有這麼難的吐槽,算了,還是改吧,如果覺得手動改麻煩,寫個指令碼也不是啥難事。

5.新增啟動指令碼

"scripts": {
	"dev": "vite",
	"build": "vite build",
	......
}

到這裡應該就差不多了吧,但是情況卻不是那麼順利,專案居然跑不起來,好吧,沒有那麼一帆風順的事情,接下來我們來看下遇到的問題吧。

遇到的問題

1.decorators not support

在業務程式碼中,我們使用了dva提供的connect來繫結狀態,形如:

@connect(state =&gt; state.foo)
class Foo extends React.PureComponent {
	....
}

但是decorators語法居然不被vite支援,關於這個問題,也有一個issus:https://github.com/vitejs/vite/issues/2349 ,目前沒有一個好的解決辦法,只好去掉decorators,改用常規的函式繫結了。

2.antd Unknown theme type: undefined, name: undefined

我們專案目前還是使用的antd的3.x版本,在啟動時就出現了這個錯誤,其實主要是antd元件初始化的時候,載入了antd/es/icon/index.js檔案:

import * as allIcons from '@ant-design/icons/lib/dist';
......
ReactIcon.add.apply(ReactIcon, _toConsumableArray(Object.keys(allIcons).map(function (key) {
  return allIcons[key];
})));
......

'@ant-design/icons/lib/dist'匯出的物件是{ default: {...} },要正確訪問的形式是allIcons.default,而不是allIcons,因此導致獲取不到icon的正確匯出物件,關於這個問題,大家可以看下這個issue:https://github.com/ant-design/ant-design/issues/19002 ,這裡要說明的一點是antd4.x版本不會出現,但是對於我們的專案來講,目前還不會升級到4這個大版本,那麼怎麼解決呢,其實只要引用antd/lib下的元件,就沒有這個問題了,我們可以看下antd/lib/icon/index.js:

......
var allIcons = _interopRequireWildcard(require("@ant-design/icons/lib/dist"));

function _interopRequireWildcard(obj) { if (obj &amp;&amp; obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" &amp;&amp; typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache &amp;&amp; cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty &amp;&amp; Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc &amp;&amp; (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
......

這裡_interopRequireWildcard方法幫我們處理了匯出的問題,那麼修改一下babel-plugin-import的配置不就好了麼,好吧,沒有那麼容易的,vite對babel-plugin-import支援不那麼好,首先還是去vite的issues裡面找找看,有一個類似的提問:https://github.com/vitejs/vite/issues/1389 ,看了下,並沒有解決我的問題,裡面提到的幾個外掛倒是給我了思路,那就自己寫個vite外掛去實現我們的需求唄。外掛的思路很簡單,就是將antd元件的引入方式進行統一的修改:

---修改前---
import { Button } from 'antd';
---修改後---
import Button from 'antd/lib/button';
import 'antd/lib/button/style/index.css';

這裡需要說明的是css樣式的引入,如果引入style/index或者style/css,會出現require is not defined的問題,因為這兩個js檔案中使用了require,但是vite在預編譯時不是node環境,當然就報錯了。
關於這個外掛的使用,可以參考https://www.npmjs.com/package/antd-vite-import-plugin 。

3.'default' is not exported

有時候三方依賴項載入會出錯,例如'default' is not exported等,這裡可以參考https://github.com/vitejs/vite/issues/2679

在實際開發過程中,還是難免遇到一些奇怪的問題,這都是嚐鮮的代價。

速度之爭

vite的一個優勢就是快,那麼和webpack相比,到底有多大的差距呢,這裡我們用webpack和vite分別啟動同一個本地專案:

構建工具 啟動時間(ms)
vite 702ms
webpack 7093ms

這裡只是一個粗略的對比,從資料上看有十倍之差,單從速度上講,vite是很快了,根據官網的解釋,Vite 將會使用 esbuild 預構建依賴。Esbuild 使用 Go 編寫,並且比以 JavaScript 編寫的打包器預構建依賴快 10-100 倍。

最後

經歷過一番折騰後,覺得vite2的成熟度有所欠缺,在一些小專案中可以試試。對我而言還是決定先用webpack吧,畢竟webpack經過這麼多年的發展,坑很少,而目前vite對於react來說還是不那麼完美。

福祿ICH·架構組 福袋

相關文章