[項目實戰(zhàn)] Webpack to Vite, 為開發(fā)提速!

背景
最近,就 前端開發(fā)過程中的痛點及可優(yōu)化項 做了一次收集。 其中,構(gòu)建耗時、項目編譯速度慢 的字眼出現(xiàn)了好幾次。
隨著業(yè)務(wù)的快速發(fā)展,我們很多項目的體積也快速膨脹。隨之而來的, 就是打包變慢等問題。
提升研發(fā)效率,是技術(shù)人永恒的追求。
我們項目也有啟動慢的問題,同事也提到過幾次。剛好我之前也做過類似的探索和優(yōu)化, 于是就借這個機會,改造一下項目, 解決啟動耗時的問題。
于昨天下午(2021.4.7 23:00), 成功嵌入 Vite, 項目啟動時間由約 190s => 20s, 熱更新時間縮短為 2s。

中間踩了一些坑, 好在最后爬出來了, 相關(guān)技術(shù)要點都會在下文中呈現(xiàn)。
FBI Warning:以下文字,只是我結(jié)合自己的實際項目, 總結(jié)出來的一些淺薄的經(jīng)驗, 如有錯誤,歡迎指正 :)
今天的主要內(nèi)容:
為什么 Vite 啟動這么快我的項目如何植入 Vite改造過程中遇到的問題以及解決方式關(guān)于 Vite 開發(fā)、打包上線的一些思考相關(guān)代碼和結(jié)論
正文
為什么 Vite 啟動這么快
底層實現(xiàn)上, Vite 是基于 esbuild 預(yù)構(gòu)建依賴的。
esbuild 使用 go 編寫,并且比以 js 編寫的打包器預(yù)構(gòu)建依賴, 快 10 - 100 倍。
因為 js 跟 go 相比實在是太慢了,js 的一般操作都是毫秒計,go 則是納秒。
另外, 兩者的啟動方式也有所差異。
webpack 啟動方式

Vite 啟動方式

Webpack 會先打包,然后啟動開發(fā)服務(wù)器,請求服務(wù)器時直接給予打包結(jié)果。
而 Vite 是直接啟動開發(fā)服務(wù)器,請求哪個模塊再對該模塊進行實時編譯。
由于現(xiàn)代瀏覽器本身就支持 ES Module,會自動向依賴的 Module 發(fā)出請求。
Vite 充分利用了這一點,將開發(fā)環(huán)境下的模塊文件,就作為瀏覽器要執(zhí)行的文件,而不是像 W ebpack 那樣進行打包合并。
由于 Vite 在啟動的時候不需要打包,也就意味著不需要分析模塊的依賴、不需要編譯。因此啟動速度非常快。當瀏覽器請求某個模塊時,再根據(jù)需要對模塊內(nèi)容進行編譯。
這種按需動態(tài)編譯的方式,極大的縮減了編譯時間,項目越復(fù)雜、模塊越多,vite 的優(yōu)勢越明顯。
在 HMR(熱更新)方面,當改動了一個模塊后,僅需讓瀏覽器重新請求該模塊即可,不像webpack那樣需要把該模塊的相關(guān)依賴模塊全部編譯一次,效率更高。
從實際的開發(fā)體驗來看, 在 Vite 模式下, 開發(fā)環(huán)境可以瞬間啟動, 但是等到頁面出來, 要等一段時間。
我的項目如何植入 Vite
新項目
創(chuàng)建一個 Vite 新項目就比較簡單:
yarn create @vitejs/app


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

已有項目
已有項目的遷移, 稍微繁瑣一些。
首先, 加入 Vite 的相關(guān)配置。這里我使用了一個 cli 工具:wp2vite.
安裝好之后, 直接執(zhí)行:

這一步, 會自動生成 Vite 的配置文件,并引入相關(guān)的依賴。
把依賴安裝一下, 啟動就可以了。
如果沒有意外的話, 你會收獲一堆報錯。
恭喜你,進入開心愉快的踩坑環(huán)節(jié)。
我在改造過程中遇到的問題
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, 于是把代碼改為:

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

唯一痛苦的是: 全局都需要改一遍, 體力活。
至此,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
很明顯的看到, 這里是把文件路徑作為組件了。
現(xiàn)在要做的是:把這個文件路徑, 換成可以識別的組件。
搜索一番, 找到了個插件: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, 就解析文件, 并緩存, 最后返回結(jié)果:

知道原理之后, 就需要把全部的 .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
不過并沒有什么用。
有了解的同學(xué)請留言賜教。
至此, 整個 app 已經(jīng)能在本地跑起來了, build 也沒問題。
7. 線上打包構(gòu)建時, 內(nèi)存溢出
本地能跑起來, 打包也沒問題, 后面當然是放到線上跑一跑啦。
立刻安排!

內(nèi)存不足, 我就給你加點:


搞定!

關(guān)于 Vite 開發(fā)、打包上線的一些思考
從實際使用來看, vite 在一些功能上還是無法完全替代 webpack。
畢竟是后起之秀, 相關(guān)的生態(tài)還需要持續(xù)完善。
個人認為,目前一種比較穩(wěn)妥的方式是:
保留 webpack dev & build 的能力, vite 僅作為開發(fā)的輔助
等相關(guān)工具再完善一些, 再考慮完全遷移過來。
相關(guān)代碼和結(jié)論
一個完整的 Vite demo
倉庫地址:https://github.com/beMySun/react-hooks-i18n-template/tree/test-wp2vite

業(yè)務(wù)項目的 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 能大幅縮短項目構(gòu)建時間,提升開發(fā)效率。
不過也要結(jié)合項目的實際情況,合理取舍。
對于我的這個項目而言,把 Vite 作為輔助開發(fā)的一種方式,還是挺有用的。
期待 Vite 能繼續(xù)完善,為研發(fā)提效。
好了, 內(nèi)容大概就這么多, 希望對大家有所幫助。
才疏學(xué)淺,如有錯誤, 歡迎指正。
謝謝。
END


“分享、點贊、在看” 支持一波
