最近,就 前端開發過程中的痛點及可優化項
做了一次收集。 其中,構建耗時、專案編譯速度慢
的字眼出現了好幾次。
隨著業務的快速發展,我們很多專案的體積也快速膨脹。 隨之而來的, 就是打包變慢等問題。
提升研發效率
,是技術人永恆的追求。
我們專案也有啟動慢的問題,同事也提到過幾次。 剛好我之前也做過類似的探索和優化, 於是就借這個機會,改造一下專案, 解決啟動耗時的問題
。
於昨天下午(2021.4.7 23:00), 成功嵌入 Vite, 專案啟動時間由約 190s => 20s
, 熱更新時間縮短為 2s
。
中間踩了一些坑, 好在最後爬出來了, 相關技術要點都會在下文中呈現。
FBI Warning: 以下文字,只是我結合自己的實際專案, 總結出來的一些淺薄的經驗, 如有錯誤,歡迎指正 :)
今天的主要內容:
為什麼 Vite 啟動這麼快
我的專案如何植入 Vite
我在改造過程中遇到的問題
關於 Vite 開發、打包上線的一些思考
相關程式碼和結論
底層實現上, Vite 是基於 esbuild 預構建依賴的。
esbuild 使用 go 編寫,並且比以 js 編寫的打包器預構建依賴, 快 10 - 100 倍。
因為 js 跟 go 相比實在是太慢了,js 的一般操作都是毫秒計,go 則是納秒。
另外, 兩者的啟動方式
也有所差異。
Webpack 會先打包
,然後啟動開發伺服器,請求伺服器時直接給予打包結果。
而 Vite 是直接啟動
開發伺服器,請求哪個模組再對該模組進行實時編譯
。
由於現代瀏覽器本身就支援 ES Module,會自動向依賴的 Module 發出請求。
Vite 充分利用了這一點,將開發環境下的模組檔案,就作為瀏覽器要執行的檔案,而不是像 W ebpack 那樣進行打包合併
。
由於 Vite 在啟動的時候不需要打包
,也就意味著不需要分析模組的依賴
、不需要編譯
。
因此啟動速度非常快。當瀏覽器請求某個模組時,再根據需要對模組內容進行編譯。
這種按需動態編譯的方式,極大的縮減了編譯時間,專案越複雜、模組越多,vite 的優勢越明顯。
在 HMR(熱更新)方面,當改動了一個模組後,僅需讓瀏覽器重新請求該模組即可,不像webpack那樣需要把該模組的相關依賴模組全部編譯一次,效率更高。
從實際的開發體驗來看, 在 Vite 模式下, 開發環境可以瞬間啟動, 但是等到頁面出來, 要等一段時間。
建立一個 Vite 新專案就比較簡單:
yarn create @vitejs/app
生成好之後, 直接啟動就可以了:
已有專案的遷移, 稍微繁瑣一些。
首先, 加入 Vite 的相關設定。 這裡我使用了一個 cli 工具: wp2vite
.
安裝好之後, 直接執行:
這一步, 會自動生成 Vite 的組態檔,並引入相關的依賴。
把依賴安裝一下, 啟動就可以了。
如果沒有意外的話, 你會收穫一堆報錯
。
恭喜你,進入開心愉快的踩坑環節。
專案程式碼裡設定了一些別名,vite 無法識別,所以需要在vite 裡面也設定 alias:
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
解決辦法:
把自定義的全域性變數從外部注入即可, 直接在 vite.config.js
的 css 選項中加入:
css: {
preprocessorOptions: {
less: {
modifyVars: {
hack: `true;@import '${resolve('./src/vars.less')}';`,
...themeVariables,
},
javascriptEnabled: true,
},
},
},
根元素未找到。
原因是: 預設生成的 index.html 中:
<div id="root"></div>
id 是 root, 而邏輯中的是#app
, 這裡直接改成 id=app
即可。
typings 檔案未找到
。
這個錯誤, 乍一看, 一頭霧水。
進去看一下原始碼和編譯後的程式碼:
原始碼:
編譯後:
typings 檔案這不是好好的在這嗎, 怎麼就找不到?
想了一下: Vite 不知道 typeings 檔案是不需要被編譯的,需要告訴編譯器不編譯這個檔案。
最後在 TS 官方檔案裡找到了答案:
https://www.typescriptlang.or...
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 問題完美解決。
我們在使用 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 裡面的也替換了。
global
是 Node裡面的變數, 會在使用者端報錯 ?
一層層看下去, 原來是引入的第三方包使用了global。
看 vite 檔案裡提到了 Client Types:
追加到 tsconfig
裡面:
"compilerOptions": {
"types": ["node", "jest", "vite/client"],
}
然後, 並沒有什麼亂用。。。
沒辦法, 只得祭出 window
大法。
在入口index.tsx 裡面加上:
(window as any).global = window;
重新整理, 好了。
還需要注入一些外部變數, 修改入口html, favicon, title 之類。
找到一個外掛: vite-plugin-singlefile
不過並沒有什麼用。
有了解的同學請留言賜教。
至此, 整個app 已經能在本地跑起來了, build 也沒問題。
本地能跑起來, 打包也沒問題, 後面當然是放到線上跑一跑啦。
立刻安排!
記憶體不足, 我就給你加點:
搞定!
從實際使用來看, vite 在一些功能上還是無法完全替代 webpack。
畢竟是後起之秀, 相關的生態還需要持續完善。
個人認為,目前一種比較穩妥的方式是:
vite 僅作為開發的輔助
等相關工具再完善一些, 再考慮完全遷移過來。
倉庫地址: https://github.com/beMySun/re...
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 能繼續完善,為研發提效。
好了, 內容大概就這麼多, 希望對大家有所幫助。
才疏學淺,如有錯誤, 歡迎指正。
謝謝。
最後,如果覺得內容有幫助, 可以關注下我的公眾號,掌握最新動態,一起學習!