記前端項目首屏加載優化(打包篇)

記前端項目首屏加載優化(打包篇)

看了一下我司官網的webpack打包出來的大小狀況,發現有不少能夠優化的點,好比 lodash、moment.js、antd等等;
本文主要圍繞webpack的打包優化,並根據業務狀況適當的作減法。html

優化前分析

優化前必定要有一個界面能記錄目前的打包狀況,推薦用webpack-bundle-analyzer這個包, 它能夠看到打包後每一個模塊的大小,還能給出gizp壓縮後的大小,在生產環境中加載的模塊都是通過gzip壓縮過的,能夠做爲真實訪問的大小依據。
安裝也很簡單:前端

// cli
npm install --save-dev webpack-bundle-analyzer

注意生產環境(production)是表明線上真實的環境,因此analyzer要對生產環境的包進行分析的,因此我配置了一下本地打包生產環境的構建配置,在package.json加入下面的配置:react

"scripts": {
    ...
    "local_production": "cross-env NODE_ENV=local_production npm run build"
}

而後在webpack配置裏面判斷process.env.NODE_ENV === 'local_production',構建production環境的構建而且加入analyzer分析生產環境打包出來的狀況。webpack

// 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生成出來的包大小狀況(打包前)
git

主要看index.xxxx.js,它包含了全部的公共依賴,咱們要作的就是減小沒必要要的公共資源的體積,能夠減小大量沒必要要的代碼。github

逐個擊破

分析antd

從上面的能夠看出來antd.less佔了很大部分面積,由於我要在項目中自定義theme,可是官方的那套配置的形式來自定義theme只能修改變量,不能改組件,因此我先加載全部的antd.less再在後面接着加載一個theme.less用於修改主題變量和修改antd組件樣式。web

  • 多是我當時搭項目的時候想太多了,因爲是官網項目,全部的組件都是根據ui來本身寫的,不多用到antd的組件,項目開發了幾十個頁面了也沒有用到這種自定義組件的狀況,因此其實能夠不加載這個龐大的antd.less,而後antd按需加載是必須的。
  • 後來發現我項目中用到的antd組件只有兩個(輪播和單選框),其實輪播是能夠用react-slick替代的,而單選框更是能夠本身實現的,因此大膽的直接把antd給移除掉了,用其餘插件替代便可。

移除了antd以後index包小了三百多k,這還遠遠不夠,接着看下面的優化點npm

優化lodash

lodash也是須要優化按需加載的方式的,推薦這篇教程Webpack按需打包Lodash的幾種方式, 按照教程改進後,lodash 小了500多k。json

優化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頁,提升用戶體驗。

總結

前端優化工做是一個長期且複雜的工做,有不少能夠考慮的地方,能夠根據網絡環境、框架、用戶羣體、業務狀況、代碼結構等多個方面合理地安排選擇優化方案,本文只是我對於現有公司官網的優化的一部分,在這裏分享給你們,若是以爲有用就點個贊吧👍

相關文章
相關標籤/搜索