切換語言為:簡體
為什麼前端打包出來的靜態檔名字是一串 Hash 值

為什麼前端打包出來的靜態檔名字是一串 Hash 值

  • 爱糖宝
  • 2024-09-25
  • 2056
  • 0
  • 0

前端打包後,靜態檔案的名字被改成一串 Hash 值(例如 app.abc123.js 或 style.abcdef.css),主要是爲了快取管理和效能最佳化。這是現代前端工程中常見的做法,通常由打包工具(如 Webpack、Vite 等)自動處理。

接下來我們來詳細講解一下這個知識點。

啓發式快取

在 Web 應用和瀏覽器快取中,伺服器通常會透過 HTTP 頭部資訊(如 Cache-Control、Expires)明確指示一個資源可以快取多長時間。但有時這些指示可能缺失,或者某些資源的快取控制資訊不完整,客戶端就會依賴啓發式規則來確定該資源的快取時長。這種規則可能基於資源的特徵、檔案型別,或者歷史經驗等。

啓發式快取的工作原理基於以下幾個步驟:

  1. 資源請求:客戶端請求某個資源,如果該資源沒有明確的快取過期時間,系統會選擇啓發式快取。

  2. 估算快取時間:基於啓發式規則(通常與資源的響應頭資訊或者資源型別相關),估算資源應該快取的時間。例如,系統可能會依據資源最後修改的時間、檔案型別等來推斷合適的快取時間。

  3. 快取儲存:估算出快取的時長後,客戶端會將該資源儲存在快取中,直到快取時間過期為止。

  4. 過期後重新請求:當快取時間到期後,客戶端將重新發出請求來獲取最新的資源。

它的主要使用場景有以下兩個方面:

  1. 無明確快取指示的資源:很多靜態資源(例如圖片、CSS 檔案、JavaScript 檔案)可能缺乏明確的 Cache-Control 或 Expires 指令。在這種情況下,啓發式快取會基於資源的型別、最後修改時間等規則來估計快取時長。

  2. 動態內容:某些動態生成的內容(例如 API 返回的資料)沒有明確的快取控制頭,但伺服器返回的內容在一定時間內不會頻繁更新。啓發式快取可以幫助提高效能,減少重複的網路請求。

啓發式快取使用的規則因平臺或瀏覽器實現不同而有所差異,但常見的啓發式規則包括:

  1. 基於 Last-Modified 頭估算:如果資源包含 Last-Modified 頭,瀏覽器或快取代理通常會基於該時間來計算快取過期時間。一個典型的規則是將 Last-Modified 的時間距離當前時間的一小部分(比如 10%)作為快取時間。例如資源最後修改時間是 2 天前,系統可以設定一個啓發式快取時間為 2天 * 10% = 4.8小時

  2. 基於檔案型別:不同型別的資源可以採用不同的啓發式快取策略。例如:圖片、字型等靜態資源通常可以快取更長時間(如 1 天到 1 周),而 JavaScript、CSS 等資源,雖然也是靜態的,但由於與功能直接相關,快取時間可能會短一些(如數小時到一天)。

  3. 預設時間設定:如果無法基於其他頭部資訊推斷,系統可能會採用預設的快取時間,比如 1 小時或 24 小時。

瀏覽器預設快取

當用戶首次訪問網站並請求 index.html 檔案時,瀏覽器會同時解析並載入其中引用的 JavaScript、CSS 等靜態資源。但瀏覽器已經考慮到了使用者的體驗:如果每次訪問都重新請求這些靜態資源,不僅載入時間變長,伺服器壓力也會增加,嚴重影響使用者體驗。爲了最佳化這一過程,瀏覽器會預設快取已請求過的靜態檔案,這種預設的快取機制就是啓發式快取。除非明確設定了 no-store,否則瀏覽器會自動快取靜態資源,避免重複下載,加快頁面載入速度。

透過給檔名加上 Hash 值(通常是檔案內容的 Hash),一旦檔案內容發生變化,檔名也會改變。瀏覽器會識別出這是一個新的檔案,從而重新載入最新版本的檔案,而不是使用舊的快取檔案。

例子:

  1. 第一次構建生成:app.abc123.js

  2. 修改程式碼後構建:app.def456.js

其中的 abc123 和 def456 就是基於檔案內容生成的 Hash 值。具體來說,Hash 值是對檔案內容進行雜湊演算法(如 MD5、SHA-256)處理生成的一個字串。這個字串獨特地表示了檔案的內容。如果檔案內容有任何變化,生成的 Hash 值也會不同。

這樣,檔名不同,瀏覽器會重新請求最新的檔案。在沒有使用 Hash 的情況下,如果檔名不變而內容變了,可能會導致快取汙染問題。瀏覽器可能還會繼續使用老版本的檔案,導致使用者訪問的頁面無法正確展示新功能或修復的 bug。

透過檔名中的 Hash,可以確保瀏覽器總是載入最新的資源,避免老版本的快取檔案汙染應用。

Hash 值的作用

那既然知道了瀏覽器會有預設的快取,當載入靜態資源的時候會命中啓發式快取並快取到本地。那如果我們重新部署前端包的時候,如何去請求新的靜態資源呢,而不是快取的靜態資源?這時候就得用到 hash 值了。

下面模擬了掘金網站的靜態資源獲取,當請求靜態資源的時候,實際訪問的是伺服器中靜態資源存放的位置:

為什麼前端打包出來的靜態檔名字是一串 Hash 值

返回即是當前請求靜態資源的具體內容:

為什麼前端打包出來的靜態檔名字是一串 Hash 值

第一次訪問時,瀏覽器會請求伺服器的資源並將其快取到本地,比如 a035f68.js 檔案會被快取到瀏覽器的磁碟或記憶體中。接下來,當用戶重新整理頁面時,瀏覽器會優先從快取中讀取資源(如從 disk cache 或 memory cache),以加快載入速度。

然而,如果前端重新部署後,假設 a035f68.js 這個檔名稱保持不變,瀏覽器就無法知道這個檔案已經更新了,因為瀏覽器預設會使用快取中的資源,除非快取已過期或被明確設定為不快取。這種情況下,瀏覽器不會主動去請求伺服器上的最新資源,導致頁面無法載入到最新的內容,影響使用者體驗。

瀏覽器的快取機制是透過資源的檔名來判斷的。如果檔名沒有發生變化,並且快取策略允許快取,且快取未過期,那麼瀏覽器將直接使用快取中的資源。相反,如果檔名發生了變化,或者快取設定要求重新驗證資源,瀏覽器纔會去伺服器請求最新的靜態資源,確保使用者看到的是最新的內容。

最佳化後的描述重點強調了瀏覽器是透過資源名稱、快取策略和過期時間來判斷是否使用快取還是請求伺服器資源的,這也是為什麼前端打包後使用帶有 Hash 值的檔名來保證資源更新。

第三方庫如何處理

對第三方庫的 Hash 處理主要涉及快取最佳化和避免不必要的重新下載。這類庫(如 React、Lodash 等)通常不會頻繁更改,因此你希望儘可能利用快取,但在庫版本升級時,確保能獲取最新的版本。

爲了更好地處理第三方庫的 Hash,你可以將第三方庫(如 React、Lodash 等)打包到單獨的檔案中,而不與業務程式碼混合。通常可以透過 Webpack 的 splitChunks 外掛或類似工具將庫程式碼和應用程式碼分開。這樣做的好處是:

  1. 第三方庫檔名的 Hash 值只與庫的內容相關,而與業務程式碼無關。

  2. 如果業務程式碼更新了,而第三方庫沒有變化,瀏覽器可以繼續使用快取中的第三方庫檔案,不必重新下載。

在 Webpack 中,可以透過以下方式配置 splitChunks:

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          chunks: "all",
        },
      },
    },
  },
};

這會將第三方庫打包成一個單獨的 vendors.[hash].js 檔案,避免每次業務程式碼變更時都重新生成第三方庫的 Hash。

另一種策略是將常見的第三方庫透過 CDN 載入,而不包含在專案的打包檔案中。這麼做可以讓這些庫由 CDN 提供快取,並且減少你本地專案的打包體積。例如,React、Vue、jQuery 等非常穩定的庫都可以直接透過 CDN 引入。

在 Webpack 中,使用 externals 來避免將第三方庫打包到專案中:

module.exports = {
  externals: {
    react: "React",
    "react-dom": "ReactDOM",
  },
};

在 HTML 檔案中透過 CDN 引入:

<script src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js"></script>

這樣,React 和 ReactDOM 就會直接從 CDN 載入,不會打包進最終生成的 JS 檔案中。

對於第三方庫,檔名的 Hash 是基於檔案內容生成的,因此庫的版本一旦發生變化,Hash 值也會發生變化。爲了確保 Hash 穩定且合理,你可以透過鎖定第三方庫的版本來控制庫檔案的變化。

在 package.json 中鎖定依賴的版本號,例如:

{
  "dependencies": {
    "react": "^17.0.0",
    "lodash": "^4.17.21"
  }
}

使用 package-lock.json 或 yarn.lock 檔案確保構建環境的一致性,防止庫的版本隨意變動,導致每次打包的 Hash 都不一致。

透過鎖定庫的版本,如果庫內容沒有變動,Hash 也不會變化,從而瀏覽器可以繼續使用快取中的版本。

如果第三方庫發生了更新(例如,你升級了 React 版本),生成的檔名的 Hash 值自然會發生變化。這時,瀏覽器會請求新的檔案,而不是使用快取中的舊版本。

這種機制可以確保在你明確升級第三方庫時,瀏覽器會自動載入最新的版本,而不會被快取機制阻擋。這也是為什麼透過檔名 Hash 控制快取是非常有效的方式:只有檔案內容實際改變時,Hash 纔會變化,而如果沒有更新,檔名就保持不變,快取繼續有效。

總結

前端打包時使用 Hash 值作為靜態檔名,主要是爲了快取最佳化、版本管理和避免快取汙染。當檔案內容發生變化時,打包工具會生成不同的 Hash 值,確保檔名唯一,從而強制瀏覽器載入最新版本的資源,避免載入舊快取檔案引發的問題。同時,如果檔案內容沒有變化,檔名保持不變,瀏覽器可以繼續使用快取中的資源,從而減少網路請求,提升載入效能和使用者體驗。透過這種方式,前端應用可以高效地管理靜態資源,保證使用者始終訪問到最新內容。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.