Webpack · Part 9 — Bundle Analysis & Performance
Measure what is actually in your bundle: webpack-bundle-analyzer treemaps, finding bloat and duplicates, performance budgets that fail the build, and prefetch/preload hints. With an interactive bundle analyzer.
You can’t optimize what you can’t see {Bạn không thể tối ưu cái không thấy}. Most bloated bundles are bloated for boring, fixable reasons: a date library that ships every locale, a full lodash import, two versions of the same dependency {Đa số bundle phình to vì những lý do nhàm chán, sửa được: một thư viện ngày tháng ship mọi locale, một import lodash đầy đủ, hai phiên bản của cùng phụ thuộc}. This part is about finding and fixing them {Phần này là về tìm và sửa chúng}.
Click blocks in the treemap below to fix the common offenders and watch the bundle drop under budget {Bấm các khối trong treemap bên dưới để sửa các thủ phạm phổ biến và xem bundle giảm dưới ngân sách}:
1. Measure first {Đo trước}
Add webpack-bundle-analyzer and run it on a production build {Thêm webpack-bundle-analyzer và chạy nó trên bản build production}:
npm install --save-dev webpack-bundle-analyzer
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
module.exports = {
plugins: [new BundleAnalyzerPlugin({ analyzerMode: "static" })],
};
It opens an interactive treemap where block size = file size {Nó mở một treemap tương tác nơi kích thước khối = kích thước file}. Big blocks are where your bytes go {Khối to là nơi byte của bạn đi}. You can also generate --json stats and inspect them with online tools {Bạn cũng có thể tạo stats --json và xem bằng công cụ online}.
2. The usual offenders {Các thủ phạm thường gặp}
| Symptom {Triệu chứng} | Fix {Sửa} |
|---|---|
moment ~230 KB | switch to dayjs (~2 KB), or IgnorePlugin to drop locales {đổi sang dayjs, hoặc IgnorePlugin để bỏ locale} |
full lodash | import { debounce } from "lodash-es" + tree shaking (Part 7) {import có tên + tree shaking} |
| duplicate deps (two axios versions) | resolve.alias to one copy, or npm dedupe {resolve.alias về một bản, hoặc npm dedupe} |
| huge polyfills | target modern browsers; trim @babel/preset-env targets {nhắm trình duyệt hiện đại; cắt targets} |
| source maps in bundle | use source-map not inline-source-map for prod (Part 5) {dùng source-map không inline-source-map} |
In the analyzer demo, click moment, lodash, and the duplicate axios block to apply these fixes and watch the total fall {Trong demo analyzer, bấm moment, lodash, và khối axios trùng để áp các fix này và xem tổng giảm}.
3. Dropping moment locales {Bỏ locale của moment}
If you must keep moment, drop the locales you don’t use {Nếu phải giữ moment, bỏ locale không dùng}:
const webpack = require("webpack");
plugins: [
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
],
That single plugin can cut ~200 KB {Một plugin đó có thể cắt ~200 KB}. But dayjs or date-fns are better long-term choices {Nhưng dayjs hoặc date-fns là lựa chọn dài hạn tốt hơn}.
4. Performance budgets {Ngân sách hiệu năng}
Make Webpack fail or warn when a bundle exceeds a size threshold, so bloat is caught in CI {Cho Webpack lỗi hoặc cảnh báo khi một bundle vượt ngưỡng kích thước, để bắt phình to trong CI}:
module.exports = {
performance: {
maxEntrypointSize: 250000, // bytes
maxAssetSize: 250000,
hints: "warning", // or "error" to fail the build
},
};
A budget turns “the bundle slowly got huge over six months” into “the PR that crossed the line fails review” {Một ngân sách biến “bundle dần phình to trong sáu tháng” thành “PR vượt giới hạn bị fail review”}. The demo’s budget bar shows the 250 KB line {Thanh ngân sách của demo hiện đường 250 KB}.
5. Prefetch & preload {Prefetch & preload}
Once split (Part 6), use resource hints so navigation feels instant without bloating the initial load {Khi đã tách (Phần 6), dùng resource hint để điều hướng tức thì mà không phình tải ban đầu}:
import(/* webpackPrefetch: true */ "./routes/Settings"); // fetch when idle
import(/* webpackPreload: true */ "./CriticalChart"); // fetch with parent
- Prefetch = “the user will probably need this soon” — low priority, idle time {= “user có lẽ sẽ cần sớm” — ưu tiên thấp, lúc rảnh}.
- Preload = “this is needed by this page right now” — high priority, parallel {= “trang này cần ngay bây giờ” — ưu tiên cao, song song}.
Use prefetch liberally for likely-next routes, preload sparingly for current-page critical chunks {Dùng prefetch thoải mái cho route có khả năng tiếp theo, preload tiết kiệm cho chunk quan trọng của trang hiện tại}.
6. Other speed wins {Các thắng lợi tốc độ khác}
cache: { type: "filesystem" }— caches build work between runs, dramatically faster rebuilds {cache công việc build giữa các lần chạy, build lại nhanh hơn nhiều}.thread-loader/swc-loader/esbuild-loader— faster transpilation than Babel {transpile nhanh hơn Babel}.externals— exclude a CDN-loaded dependency from the bundle entirely {loại một phụ thuộc tải-từ-CDN khỏi bundle hoàn toàn}.- Narrow loader
include/excludeso loaders run on as few files as possible {Thu hẹpinclude/excludeđể loader chạy trên ít file nhất}.
module.exports = {
cache: { type: "filesystem" }, // huge rebuild speedup
};
7. Exercises {Bài tập}
1. The analyzer shows a 230 KB block for moment, mostly locale files you don’t use. Two ways to shrink it? {Analyzer hiện khối 230 KB cho moment, chủ yếu là file locale bạn không dùng. Hai cách thu nhỏ?}
Solution {Lời giải}
IgnorePlugin to drop locales, or replace moment with dayjs/date-fns {IgnorePlugin để bỏ locale, hoặc thay moment bằng dayjs/date-fns}.
2. You want CI to fail when someone pushes a PR that makes the main bundle exceed 250 KB. What do you configure? {Bạn muốn CI fail khi ai đó push PR làm bundle chính vượt 250 KB. Cấu hình gì?}
Solution {Lời giải}
performance: { maxEntrypointSize: 250000, hints: "error" } {performance: { maxEntrypointSize: 250000, hints: "error" }}.
3. The analyzer shows two copies of axios at different versions. How do you dedupe? {Analyzer hiện hai bản axios phiên bản khác nhau. Khử trùng lặp thế nào?}
Solution {Lời giải}
Align versions (npm dedupe) or force one copy with resolve.alias: { axios: path.resolve("node_modules/axios") } {Đồng bộ phiên bản (npm dedupe) hoặc ép một bản với resolve.alias}.
Stretch {Nâng cao}: in the analyzer demo, fix moment, lodash, and the duplicate axios, and confirm the bundle crosses from over-budget (red) to within-budget (green) {trong demo analyzer, sửa moment, lodash, và axios trùng, và xác nhận bundle chuyển từ vượt-ngân-sách (đỏ) sang trong-ngân-sách (xanh)}.
Key takeaways {Điểm chính}
- Measure with
webpack-bundle-analyzerbefore optimizing {Đo bằngwebpack-bundle-analyzertrước khi tối ưu}. - Common bloat:
momentlocales, fulllodash, duplicate deps, fat polyfills {Phình phổ biến: localemoment,lodashđầy đủ, phụ thuộc trùng, polyfill to}. - Set performance budgets to catch regressions in CI {Đặt ngân sách hiệu năng để bắt thoái lui trong CI}.
- Use prefetch/preload hints for smart loading {Dùng gợi ý prefetch/preload để tải thông minh}.
cache: { type: "filesystem" }speeds up rebuilds dramatically {cache: { type: "filesystem" }tăng tốc build lại đáng kể}.
Next up {Tiếp theo}
Part 10 — Advanced resolve + authoring your own loader & plugin: resolve internals and externals, then writing a custom loader and a custom plugin to understand Webpack from the inside {Phần 10 — Resolve nâng cao + tự viết loader & plugin: nội bộ resolve và externals, rồi viết một loader tùy chỉnh và một plugin tùy chỉnh để hiểu Webpack từ bên trong}.