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


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

看了一下我司官網的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頁,提高用戶體驗。

總結

前端優化工作是一個長期且復雜的工作,有很多可以考慮的地方,可以根據網絡環境、框架、用戶群體、業務情況、代碼結構等多個方面合理地安排選擇優化方案,本文只是我對於現有公司官網的優化的一部分,在這里分享給大家,如果覺得有用就點個贊吧👍


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM