前幾天晚上下班的時候, 路過隔壁項目組, 聽他們在聊項目構建的事:javascript
如今線上打包時間太長了, 修個 bug 1 分鐘, 發佈一下半小時, 賊難受。
他們項目比較龐大, 線上構建時間特別長, 基本都在15分鐘以上
。css
和他們簡單聊了會, 回去瞅了一下本身項目的構建時間:html
其實也挺長的, 因而抽空優化了一下, 效果仍是比較明顯的:java
在正文部分,我將分享的內容主要是:react
一些提高 webpack 打包性能的配置
優化大型項目構建時間的一些思考
但願對你們有所啓發。webpack
咱們項目不是很大, 是一箇中型的國際化項目, 一百來個頁面。 git
以前本地構建時間挺長的,初次啓動要三次分鐘, 後面我配置了 Vite
, 本地啓動時間下降到了 20s
左右,感興趣的能夠移步我這篇文章:github
看了一下,線上構建時間五六分鐘,不痛不癢,可是應該也有優化空間,因而準備優化一下。typescript
既然要優化構建時間, 第一步固然是先發現問題
, 找出比較耗時
的階段,再加以優化。
這裏我用到了SMP
插件。
SMP
插件用法很是簡單, 這裏也簡單提一下:
// webpack.config.js const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const smp = new SpeedMeasurePlugin(); module.exports = smp.wrap({ // ... });
利用 SMP
插件得出各個階段的打包耗時:
發現兩個比較明顯的問題:
IgnorePlugin
耗時接近 20 秒。less-loader
部分執行了2次,浪費了一分多鐘。ts-loader
耗時一分半, 也挺長的。查看了一下配置, 發現配置裏的 IgnorePlugin
並無達到預期的效果, 刪掉。
查看配置後發現, 在處理less
的部分,確實多處理了一遍。
less 文件的處理,能夠直接看官方文檔,文檔地址:
https://webpack.docschina.org...
個人配置:
{ test: /\.less$/, use: [ 'style-loader', 'css-loader', { loader: 'less-loader', options: { javascriptEnabled: true, sourceMap: true, modifyVars: { // inject our own global vars // https://github.com/ant-design/ant-design/issues/16464#issuecomment-491656849 hack: `true;@import '${require.resolve('./src/vars.less')}';`, ...themeVariables, }, limit: 10000, name: '[name].[hash:7].[ext]', outputPath: 'styles/', }, }, ], }, { test: /\.css$/, use: ['style-loader', 'css-loader'], },
對於ts-loader
部分的優化, 能夠參考:
https://webpack.js.org/guides...
文檔上也有比較清晰的描述:
文檔建議, 咱們開啓transpileOnly
選項,關閉類型檢查。
若是要類型檢查, 可使用 ForkTsCheckerWebpackPlugin
,這個插件會在另一個進程中作相關的檢查。
這個插件,咱們在優化構建時內存溢出的問題上, 也作了探索, 感興趣的能夠移步我這篇文章:
如今咱們也開啓這個選項。
開啓以後, 本地構建的時候, 本地報了個警告:
這個錯誤, 十分的眼熟, 是以前咱們講過的 import type
的問題,
修復一下:
問題解決。
從新構建, 獲得以下結果:
優化以後以後, 咱們發現:
IgnorePlugin、HtmlWebpackPlugin
時間大幅縮短。less-loader
等恢復了正常,只執行了一次。ts-loader
時間大幅縮短,由1分30秒縮短爲40秒。本地效果明顯,須要去線上構建驗證。
在線上執行以後, 獲得以下結果:
而後去檢查了一下頁面,也都是正常的。
完美!
回頭看,不難發現,其實也沒改多少東西, 就收穫了不錯的效果。
針對中小型項目來講, 改改配置每每就能達到咱們的要求, 可是若是是面對大型項目
呢?
好比那種數十個模塊, 幾百個頁面的項目。
回到開頭那個問題: 修個 bug 1 分鐘, 發佈一下半小時
。
簡單的修改配置, 都沒法把時間降下來, 這時候該怎麼辦呢?
假設咱們有一個項目,大模塊就有將近30個:
每一個大模塊裏面又有幾十個頁面,這種系統構建時間會比較久, 須要作優化。
並且到了項目後期,問題會愈來愈明顯, 好比:
面對這種狀況,一種可行的作法是:拆分子應用
。
拆分以後的架構:
每一個子項目都有單獨的入口, 是能夠獨立部署的項目。
子項目打成單獨umd
包:
在主項目啓動的時候, 再去加載這些子項目:
加載完成以後, 須要處理路由
以及store
, 示例代碼:
// base export const bootstrap = () => { // ... ReactDOM.render(( <Provider store={store}> <Router history={history}> <App defaultInitialData={_initialData} /> </Router> </Provider> ), document.getElementById('root')); return Promise.resolve(); }; // main const loadSubApp = (htmlEntry: string) => { return importHTML(`${htmlEntry}?${Date.now()}`) .then((res: any) => res.execScripts()) .then((exportedValues: any) => { console.log(`importHTML: ${htmlEntry} loaded, exports:`, exportedValues); const { store, router } = exportedValues || {} as any; router && addCustomRouter(router); store && addCustomStore(store); }) .catch(e => { console.error('importHTML: ${htmlEntry} load error:', e); }); }; const load = () => { if (__ENV__ !== 'dev') { const paths: string[] = []; subAppConfig.subApps.forEach(item => { if (item.project === localStorage.getItem('ops_project')) { paths.push(...item.paths); } }); Promise.all(paths.map(path => loadSubApp(path))) .catch(e => console.log(e)) .finally(setAllLoaded); } else { setAllLoaded(); } }; const init = () => { console.log('init: Start to bootstrap the main APP'); addCustomStore(rootStore); bootstrap().then(() => { load(); }); }; init();
common
包externals
給樣式添加以子項目爲名的 namespace
:
以 ops 項目爲例。
讓開發調試 ops-common 包像本地文件同樣方便:
在同一個project上爲每一個子項目申請獨立module
優勢:
獨立發佈
, 子模塊和主模塊解偶
。單獨編譯
的,主項目只須要作引入便可, 以此減小主模塊的構建時間
。缺點:
通常來講,對於中小型項目,作好打包配置的優化, 可以解決一部分問題。
大型項目的構建時間優化, 能夠考慮拆分子應用的模式。
只不過這種模式須要考慮一些維護
的問題,好比如何維護版本 tag、如何快速回滾等。
這些須要結合大家項目的實際狀況
再作決定。
今天的內容就這麼多,但願對你們有所啓發。
祝你們五一快樂~~
若是以爲文章內容有幫助, 能夠關注下我哦, 掌握最新動態。
也能夠加我微信 「 scaukk 」, 一塊兒探討。