記前端項目首屏加載優化(打包篇)
看了一下我司官網的webpack打包出來的大小情況,發現有很多可以優化的點,比如 lodash、moment.js、antd等等;
本文主要圍繞webpack的打包優化,並根據業務情況適當的做減法。
優化前分析
優化前一定要有一個界面能記錄目前的打包情況,推薦用webpack-bundle-analyzer這個包, 它可以看到打包后每個模塊的大小,還能給出gizp壓縮后的大小,在生產環境中加載的模塊都是經過gzip壓縮過的,可以作為真實訪問的大小依據。
安裝也很簡單:
// cli
npm install --save-dev webpack-bundle-analyzer
注意生產環境(production)是代表線上真實的環境,所以analyzer要對生產環境的包進行分析的,所以我配置了一下本地打包生產環境的構建配置,在package.json加入下面的配置:
"scripts": {
...
"local_production": "cross-env NODE_ENV=local_production npm run build"
}
然后在webpack配置里面判斷process.env.NODE_ENV === 'local_production'
,構建production環境的構建並且加入analyzer分析生產環境打包出來的情況。
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
if(process.env.NODE_ENV === 'local_production') {
webpack_config.plugins.push(
new BundleAnalyzerPlugin(
{
analyzerMode: 'server',
analyzerHost: '127.0.0.1',
analyzerPort: 8889,
reportFilename: 'report.html',
defaultSizes: 'parsed',
openAnalyzer: true,
generateStatsFile: false,
statsFilename: 'stats.json',
statsOptions: null,
logLevel: 'info'
}
)
);
}
這里是我的項目用analyzer生成出來的包大小情況(打包前)
主要看index.xxxx.js,它包含了所有的公共依賴,我們要做的就是減少不必要的公共資源的體積,可以減少大量不必要的代碼。
逐個擊破
分析antd
從上面的可以看出來antd.less占了很大部分面積,因為我要在項目中自定義theme,但是官方的那套配置的形式來自定義theme只能修改變量,不能改組件,所以我先加載所有的antd.less再在后面接着加載一個theme.less用於修改主題變量和修改antd組件樣式。
- 可能是我當時搭項目的時候想太多了,由於是官網項目,所有的組件都是根據ui來自己寫的,很少用到antd的組件,項目開發了幾十個頁面了也沒有用到這種自定義組件的情況,所以其實可以不加載這個龐大的antd.less,然后antd按需加載是必須的。
- 后來發現我項目中用到的antd組件只有兩個(輪播和單選框),其實輪播是可以用react-slick替代的,而單選框更是可以自己實現的,所以大膽的直接把antd給移除掉了,用其他插件替代即可。
移除了antd之后index包小了三百多k,這還遠遠不夠,接着看下面的優化點
優化lodash
lodash也是需要優化按需加載的方式的,推薦這篇教程Webpack按需打包Lodash的幾種方式, 按照教程改進后,lodash 小了500多k。
優化moment
其實moment引進來的時候會帶有很多語言包的,我們只用到了其中一個中文的包,所以其他語言包都可以去掉,
plugins: [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
]
后來又發現項目中只用到了moment().format()這個方法,由於moment.js只有一個大的moment.js模塊,沒有按模塊分開寫,無法按需打包,那么其實我們可以自己實現個簡易版的moment來替代moment.js,下面是我找到的實現簡易版moment代碼:
// 簡易版moment代替moment.js
class Moment {
private date:Date;
constructor(arg = new Date().getTime()) {
this.date = new Date(arg);
}
padStart(num) {
num = String(num);
if (num.length < 2) {
return '0' + num;
} else {
return num;
}
}
unix() {
return Math.round(this.date.getTime() / 1000);
}
static unix(timestamp) {
return new Moment(timestamp * 1000);
}
format(formatStr) {
const date = this.date;
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const week = date.getDay();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
const weeks = ['一', '二', '三', '四', '五', '六', '日'];
return formatStr.replace(/Y{2,4}|M{1,2}|D{1,2}|d{1,4}|h{1,2}|m{1,2}|s{1,2}/g, (match) => {
switch (match) {
case 'YY':
return String(year).slice(-2);
case 'YYY':
case 'YYYY':
return String(year);
case 'M':
return String(month);
case 'MM':
return this.padStart(month);
case 'D':
return String(day);
case 'DD':
return this.padStart(day);
case 'd':
return String(week);
case 'dd':
return weeks[week];
case 'ddd':
return '周' + weeks[week];
case 'dddd':
return '星期' + weeks[week];
case 'h':
return String(hour);
case 'hh':
return this.padStart(hour);
case 'm':
return String(minute);
case 'mm':
return this.padStart(minute);
case 's':
return String(second);
case 'ss':
return this.padStart(second);
default:
return match;
}
});
}
}
export const moment = (arg) => {
return new Moment(arg);
};
這樣就直接可以把moment.js 干掉了,包體積又小了不少。
下面是優化后的analyzer生成出來的包大小情況
包體從2.7M優化到了1.7M,gzip從297k減小到212k,訪問雖然只是快了一點點,但在低網速環境下訪問還是看得到區別的。
首屏加載視覺優化
接下來講一個跟包大小無關又很重要的優化點,就是單頁應用的第一個入口html,正常情況下入口html只是用來加載js包,等js加載完之后才渲染出相關界面出來,這個入口html本身沒有內容展示,但它是整個網站的第一個請求,取到這個入口html之后才開始加載js,等到加載完js才開始渲染界面,這段時間是占網站整體加載時間最多的,如下圖:
第一個請求只要128ms,直到加載完公共js渲染出界面需要1s左右,這時候如果入口index沒內容的話那就是純粹的白屏時間了,所以我們應該好好利用這個入口index.html,可以做一個骨架屏或者loading動畫,能讓用戶在等白屏時間里能夠有個界面能看到,停留時間會更長一些,也能讓用戶以為這個網站一下就刷出來看到東西的感覺。
對於這個入口index的利用,我是加入了頂部導航欄進去的,讓用戶可以第一眼看到導航欄知道有什么導航項,而且也是可以點進去的,而內容區對於不同的路徑訪問會有不同的界面,所以我就簡單的弄個loading即可。
至此,這一版優化減少了加載的時間,同時合理利用了入口index作為loading頁,提高用戶體驗。
總結
前端優化工作是一個長期且復雜的工作,有很多可以考慮的地方,可以根據網絡環境、框架、用戶群體、業務情況、代碼結構等多個方面合理地安排選擇優化方案,本文只是我對於現有公司官網的優化的一部分,在這里分享給大家,如果覺得有用就點個贊吧👍