Webpack · Part 8 — Caching & Long-Term Caching
The file-naming strategy that lets browsers cache bundles for a year: [contenthash], deterministic moduleIds, extracting the runtime, and why splitting vendors keeps hashes stable. With an interactive contenthash cache simulator.
Khi app đã live, user quay lại không nên tải lại code không đổi. Mẹo là tên file dựa trên nội dung: nếu tên chỉ đổi khi nội dung đổi, bạn có thể bảo trình duyệt cache mỗi file mãi mãi. Làm sai thì user hoặc thấy code cũ hoặc tải lại mọi thứ mỗi lần deploy.
Sửa code bên dưới và xem hash file nào đổi và cái nào vẫn cache:
1. Chiến lược caching
Mẫu là file bất biến + tên băm. Bạn cấu hình server gửi Cache-Control: max-age=31536000, immutable cho asset băm. Vì filename đổi mỗi khi nội dung đổi, một deploy mới tạo tên mới — trình duyệt tải file mới và giữ file không đổi từ cache. HtmlWebpackPlugin (Phần 4) viết lại thẻ <script>/<link> sang tên mới tự động.
2. [contenthash]
Dùng token [contenthash] trong tên output — một hash dẫn xuất từ nội dung file:
output: {
filename: "[name].[contenthash].js",
chunkFilename: "[name].[contenthash].js", // for dynamic-import chunks
},
// and for extracted CSS (Part 4):
new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })
Còn có [hash] (hash cả-build — đổi mỗi build, tệ) và [chunkhash] (theo chunk). [contenthash] là cái bạn muốn — nó đổi ít nhất. Tắt nó trong demo để thấy tên đông cứng và rủi ro cache cũ.
3. Tách vendor để cache độc lập
node_modules hiếm khi đổi nhưng chiếm phần lớn byte. Giữ chúng trong chunk riêng (Phần 6) để sửa code app không làm vô hiệu file vendor khổng lồ:
optimization: {
splitChunks: { chunks: "all" },
}
Trong demo, sửa code app với “split vendors” bật: chỉ main tải lại trong khi vendors vẫn cache.
4. Trích runtime
Đây là lỗi tinh vi. Runtime của Webpack chứa một manifest ánh xạ ID module tới chunk. Nếu runtime đó nằm trong chunk vendor, thì bất kỳ thay đổi app nào cũng cập nhật manifest và do đó đổi hash vendor — phá hỏng cả mục đích. Trích nó ra:
optimization: {
runtimeChunk: "single", // separate runtime.js
}
Trong demo, tắt runtimeChunk và sửa code app — để ý vendors giờ cũng đổi. Bật lên và vendors vẫn cache.
5. ID module & chunk tất định
Mặc định ID module có thể dịch khi bạn thêm/bớt module, đổi hash của chunk không thực sự đổi. Production mode đã dùng ID deterministic, nhưng bạn có thể đặt rõ:
optimization: {
moduleIds: "deterministic",
chunkIds: "deterministic",
}
Điều này cho ID ngắn, ổn định dựa trên nội dung, nên sửa không liên quan không lan vào hash chunk khác.
6. Config caching hoàn chỉnh
module.exports = {
mode: "production",
output: {
filename: "[name].[contenthash].js",
chunkFilename: "[name].[contenthash].js",
clean: true,
},
optimization: {
moduleIds: "deterministic",
runtimeChunk: "single",
splitChunks: { chunks: "all" },
},
};
Ghép với Cache-Control: immutable trên file băm và cache ngắn trên index.html (file tham chiếu tên băm). Đó là công thức caching production trong năm dòng optimization.
7. Bài tập
1. Mỗi deploy, user quay lại tải lại bundle vendor 130 KB dù phụ thuộc không đổi. Bạn tách vendor nhưng không tách runtime. Sửa thế nào?
Lời giải
Thêm optimization.runtimeChunk = "single" để manifest ở file nhỏ riêng và thay đổi app không đổi hash vendor.
2. Bạn dùng [hash] thay vì [contenthash]. Vì sao mọi file tải lại mỗi deploy?
Lời giải
[hash] là hash cả-build — đổi mỗi build, nên mọi filename đổi dù nội dung không. Dùng [contenthash].
3. Bạn ship với filename: "bundle.js" (không hash) và Cache-Control: immutable. User báo thấy code cũ sau deploy. Vì sao?
Lời giải
Tên không đổi, nên trình duyệt cứ phục vụ file cache (cũ). Thêm [contenthash] để nội dung mới có tên mới.
Nâng cao:trong trình mô phỏng, tắt runtimeChunk, sửa code app, và xác nhận vendors tải lại; rồi bật lên và xác nhận chỉ runtime và main đổi.
Điểm chính
- Dùng
[contenthash]để filename chỉ đổi khi nội dung đổi. - Tách vendor để sửa app không làm vô hiệu bundle phụ thuộc.
runtimeChunk: "single"giữ manifest ngoài hash vendor.moduleIds: "deterministic"ngăn sửa không liên quan lan rộng.- Phục vụ file băm với
Cache-Control: immutable.
Tiếp theo
Phần 9 — Phân tích bundle & hiệu năng: đo thực sự cái gì trong bundle bằng analyzer, tìm phình to, ngân sách hiệu năng, và gợi ý prefetch/preload.