jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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 KBswitch to dayjs (~2 KB), or IgnorePlugin to drop locales {đổi sang dayjs, hoặc IgnorePlugin để bỏ locale}
full lodashimport { 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 polyfillstarget modern browsers; trim @babel/preset-env targets {nhắm trình duyệt hiện đại; cắt targets}
source maps in bundleuse 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/exclude so loaders run on as few files as possible {Thu hẹp include/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-analyzer before optimizing {Đo bằng webpack-bundle-analyzer trước khi tối ưu}.
  • Common bloat: moment locales, full lodash, duplicate deps, fat polyfills {Phình phổ biến: locale moment, 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}.