博客 / 詳情

返回

PC首頁資源加載速度由8s降到2s的優化實踐

隨着需求的不斷開發,前端項目不斷膨脹,業務提出:你們的首頁加載也太慢啦,我都需要7、8秒才能看到內容,於是乎主管就讓我聯合後端開啓優化專項,目標是3s內展示完全首頁的內容。

性能指標

開啓優化時,我們要清晰的知道現狀和目標,以及我們採用什麼樣的手段,通過檢測什麼指標來查看到優化的過程。

結果指標

根據這個目標,我們可以選擇一些性能指標,google 提供了基於用户體驗的性能指標,如FCP、LCP、FID、TTI、TBT、CLS等,也有指標更少新的用户體驗量化方式 Web Vitals,只選取 LCP、FID、CLS。

我們這次主要選擇的指標是 FCPLCP。FCP 表示着用户能最快看到頁面內容的時間,LCP 則是可視區域的最大內容,這兩個指標代表着用户真實對於頁面快和慢的體驗,FCP 在 1.8 秒內,LCP 在2.5 秒內是比較好的。

通過 chrome 瀏覽器 lighthouse 功能可查看的當前頁面的結果指標數據,該項目首頁 LCP 和 FCP 時間分別為 7.5s、1s,LCP 非常之慢了。

過程指標

FCPLCP 是希望達到的結果,在優化的過程中,我們需要一些數據來記錄到底在哪些方向做的優化能導致一個比較好的結果,比如請求數量、頁面加載時間、打包總體積/入口文件體積、依賴的 cdn 數量、傳輸資源體積。

無痕模式下通過 chrome 開發者工具 network 記錄這些過程指標,當前項目首頁過程指標:請求(requests) 117 個,頁面加載時間(Load)3.79 秒,打包總體積(resources) 21.8MB、依賴的 CDN(需要自己點擊頁面右鍵查看網頁源碼去數)25個js 、7個css資源,傳輸資源體積(transferred)6.6MB

較多的資源數量、較大的打包體積都導致了頁面加載速度變慢。

頁面生命週期

首先我們得知道頁面的生命週期,才能有針對性的去對前端參與的過程進行優化。那麼從瀏覽器地址欄輸入url,到頁面渲染出來,主要經過了哪些步驟呢?

  • 輸入域名後,DNS 域名解析
  • 發起TCP的3次握手
  • 建立TCP連接後發起 http 請求
  • 服務器端響應http請求,瀏覽器得到html代碼
  • 瀏覽器解析html代碼,並請求html代碼中的資源
  • 瀏覽器對頁面進行渲染呈現給用户(DOM、CSSOM、渲染樹)

在這個過程中,有很多網絡、服務器參與的過程,而我們主要關注前端能夠進行的優化,比如使用 DNS 預解析、緩存機制、減少項目編譯後 html / css/ js 包資源大小。

通用優化

刪除無用代碼

這一步適用於所有項目,定期的清理掉不需要的代碼能避免項目無止境的膨脹。因我們項目的歷史遺留問題,存在一些無用的cdn資源、依賴、組件、配置,在頁面加載的過程中他們佔據了一定的網絡帶寬。

無用 cdn 資源

當前業務場景下這些資源都已經用不到,也無需在頁面初始化的時候加載。

功能重複的資源

由不同開發者根據集團規範引入了,兩個不同域名但相同功能的前端異常數據採集的 cdn 資源,只需保留一個仍然維護並且推薦使用的即可。

無用依賴

找遍整個項目都用不到的依賴

無用請求邏輯

為了測試組件,跟隨首頁發送的請求,但因業務場景不太相符,未真正投入使用

無用文件

可用webpack插件 unused-files-webpack-plugin 找到已不需要使用的文件,再通過 nodejs 中內置的文件處理 fs 定義函數將其遞歸刪除。

網絡相關

域名收斂

在項目中,我們可能需要加載很多不同的內容,無論是了為了頁面更為美觀的圖片、字體,還是不適合編譯到項目中體積較大的cdn的資源(如 echarts、谷歌地圖)。

我們知道通過 http 請求獲取資源需要經歷 DNS 域名解析、TCP 三次握手、建立 TCP 連接後發送 http 請求、服務器響應 .... 在這裏第一步就是域名解析,如果本地沒有緩存,那麼這裏將會從頂級域名開始一層層往下找,需要耗費很多時間。

通過域名收斂,將相同的應用/資源整合到同一域名,減少不同域名之間 DNS 的解析時間,我們項目資源主要有幾大類(xxx代表公司域名)

  • JS、CSS 使用 g.alicdn.comassets.xxx.cn
  • 圖片收斂到文件平台 imgcdn.xxx.cnassets.xxx.cn
  • 字體圖標收斂到 at.alicdn.com
  • 特殊如高德地圖 webapi.amap.com 之類

DNS預解析

在執行 js 文件前會存在一些空閒時間,可以用來解析 dns 地址,dns 預解析是異步執行的,不會對 html 頁面代碼造成阻塞,這樣在真正加載資源時可以減少用户的等待時間。

上面已經將域名進行了收斂,html 中 <link> 元素通過 dns- prefetch 的 rel 屬性提供這項功能,然後在 href 屬性中指定要跨域的域名(僅作用於與當前頁面不一樣的域名)。

<link rel="dns-prefetch" href= "//imgcdn.xxx.com">

以上兩個步驟分別刪除無用的資源和提升網絡連接,和項目類型、業務場景關聯不大,屬於很多項目都通用的方案。接下來的步驟需要根據資源編譯情況來進行優化。

編譯產物優化

在 webpack 配置中增加插件 webpack-analyzer-plugin,本地運行項目,編譯後的資源包情況將在在 8888 端口展示。從編譯產物中,我們可以看到每個 js 包原始大小,壓縮後尺寸,js中主要有哪些庫,根據這些具體的信息進行優化。

圖中 vendor.js 是項目打包的入口文件,是第一次打開任意頁面都會加載的資源,仔細觀察那些體積較大的資源,想辦法進行優化。

合併 echarts 版本

從圖中可以看到,繪圖組件有兩個組件庫,BizCharts 、echarts,而 echarts 還分為 5.4 和 4.9 的版本,並且在 index.html 中還通過 cdn 引入了 4.2.1 版本並且沒有使用 externals排除打包。

找到不同 echarts 版本的組件來源,我們項目中兩個版本分別來自 echarts 自身和公司業務組件,還有 react-for-echarts 組件的依賴 。

優化方案是找一個各組件都可以使用版本,將 package.json 和 cdn地址中echarts版本與組件庫中保持一致(使用 5.3.3版本),通過 externals 排除打包,這樣直接使用 cdn 資源,不再將 echarts 編譯至入口文件 vendor.js 中

// index.html
"externals": {
    "echarts": "echarts"
}

另外還有 BizCharts,因為使用到的菜單已下線,所以直接將這部分刪除。

loadash 工具縮包

項目中常會用到 loadash 之類的工具庫,為了使用幾個函數將整個工具庫的資源都引入非常的不值得,比較好的方式是隻通過 import 引入具體需要使用的文件,如 import _add from 'lodash/fp/add'

但很有可能並不是項目中的每個人都會遵守這樣的規則,如果使用全量引入的方式,如 import _ from 'lodash', 導致全量資源被編譯至項目主入口文件中。

為了避免這種情況,我們可以使用 babel 插件來 babel-plugin-lodash 將全量引入方式編譯成按文件引入。

import _ from 'lodash'
import { add } from 'lodash/fp'

const addOne = add(1)
_.map([1, 2, 3], addOne)

編譯成

import _add from 'lodash/fp/add'
import _map from 'lodash/map'

const addOne = _add(1)
_map([1, 2, 3], addOne)

babel-plugin-lodashbabel 插件, 在 webpack 中配置到 babel-loader

rules: [
      {
        exclude: /node_modules/,
        test: /\.js$/,
        use: [{
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: ['lodash'],
          },
        }],
      },
    ],

我們項目使用的是集團統一腳手架,只暴露出 webpack-chain 的方式去修改,所以是這樣使用。

const merge = require('lodash/merge');
 config.module
  .rule('jsx')
  .use('babel-loader')
  .tap((options) => {
    return merge(options, {
      plugins: ['babel-plugin-lodash'],
    });
  });

從圖中可以看出 xlsx 資源編譯後的體積也非常大,但我們項目當前業務場景已沒有使用到,所以直接刪除,像低頻使用的資源,可以替換成 cdn 鏈接或者拆包編譯並延遲引入。

通過以上刪除、合併整理,主入口資源從 7.51M 降低 2.51 M。

devtool

藉助 chrome 瀏覽器的 devtool 來進行進行進一步的處理

lighthouse 建議

將項目打開在 chrome 瀏覽器中,使用 lighthouse 不僅可以用來檢測項目 LCP、FCP評分,還有一些針對當前檢測頁面的建議。

點開之後就是每一條具體的建議,比如提示存在一些首頁用不到的 js、css 資源,為圖片增加寬高減少佈局偏移。

首頁用不到但其它地方需要使用 js、css 資源可以去除入口 index.html 的引入,改為到指定文件需要使用時再延遲加載 cdn 資源。

方案大概是這樣:

  • 如果指定頁面沒有加載需要的資源,需要通過 scrpt 標籤加載需要的 js 資源,link 標籤加載需要的 css 資源
  • 判斷 cdn 引入的資源已掛載在 window 對象之後,再開始執行頁面的渲染

這樣每個頁面負責自己所需要的資源,無需將所有資源的加載壓力都放到首頁

network 查看接口響應

性能優化也不是前端努力就足夠的,頁面加載過程中,後端接口響應速度也很關鍵,如果後端接口響應過慢,前端拿不到數據也無法進行渲染。

在我們項目首頁中,獲取用户菜單權限的接口非常的慢,每次打開頁面都需要一秒鐘左右才能響應,存在很嚴重的阻塞問題,反饋給後端同學進行優化後,能保持在 100-200毫秒完成響應。

優化結果

通過以上優化,性能過程指標,load 時長、資源體積、依賴cdn數量、傳輸資源體積都有了很大的提升。

性能結果指標 LCP 保持在 1秒,FCP 由 7.5 秒降低至 1.1 秒,頁面渲染完成由 8秒 降到 2秒左右,達到了我的預期。

優化完還有非常重要的事情就是 一定要充分測試! 已上線的需求在進行如此大的改動後,一定要先自測一遍業務功能是否受影響,再提給測試同學排期對整個項目進行測試,等到充分測試完成才能發佈。

總結

攻城容易守城難,做好了一次優化不意味着能夠長期的保持,重要的是全組的同學在平時的需求開發中注意性能做好維護工作,不然資源會像滾雪球一樣越來越大,頁面的加載速度就在無形之中越來越慢。

user avatar kasong 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.