[項目實戰] Webpack to Vite, 爲開發提速!
背景
最近,就 前端開發過程中的痛點及可優化項
做了一次收集。 其中,構建耗時、項目編譯速度慢
的字眼出現了好幾次。
隨着業務的快速發展,我們很多項目的體積也快速膨脹。隨之而來的, 就是打包變慢等問題。
提升研發效率
,是技術人永恆的追求。
我們項目也有啓動慢的問題,同事也提到過幾次。剛好我之前也做過類似的探索和優化, 於是就借這個機會,改造一下項目, 解決啓動耗時的問題
。
於昨天下午 (2021.4.7 23:00), 成功嵌入 Vite, 項目啓動時間由約 190s => 20s
, 熱更新時間縮短爲 2s
。
中間踩了一些坑, 好在最後爬出來了, 相關技術要點都會在下文中呈現。
FBI Warning:以下文字,只是我結合自己的實際項目, 總結出來的一些淺薄的經驗, 如有錯誤,歡迎指正 :)
今天的主要內容:
-
爲什麼 Vite 啓動這麼快
-
我的項目如何植入 Vite
-
改造過程中遇到的問題以及解決方式
-
關於 Vite 開發、打包上線的一些思考
-
相關代碼和結論
正文
=====
爲什麼 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。
畢竟是後起之秀, 相關的生態還需要持續完善。
個人認爲,目前一種比較穩妥的方式是:
- 保留 webpack dev & build 的能力,
vite 僅作爲開發的輔助
等相關工具再完善一些, 再考慮完全遷移過來。
相關代碼和結論
一個完整的 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