jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Webpack · Part 6 — Code Splitting & Lazy Loading

Stop shipping one giant bundle: dynamic import() for route-level lazy loading, splitChunks for vendor and shared code, runtimeChunk, and magic comments for naming and prefetching. With an interactive code-splitting visualizer.

Mặc định Webpack đóng mọi thứ vào một bundle. Ổn cho app nhỏ, nhưng nghĩa là user tải code của mọi trang trước khi thấy bất kỳ trang nào. Code splitting chia bundle thành các chunk tải theo yêu cầu.

So sánh monolith với bản tách, rồi điều hướng để lazy-load route:


1. Ba cách tách

  1. import() động — bạn đánh dấu điểm tách rõ ràng; module được import thành chunk riêng.
  2. splitChunks — Webpack tự trích code chung/vendor thành chunk riêng.
  3. Nhiều entry — tách thủ công theo trang (Phần 2).

Hai cái đầu là cái bạn dùng thực tế.


2. Dynamic import — tải lười

Một import tĩnh luôn ở trong bundle ban đầu. Một import() động trả về promise và bảo Webpack tách module đó thành chunk tải riêng được:

// static — bundled up front
import { Editor } from "./Editor";

// dynamic — its own chunk, fetched only when this runs
button.addEventListener("click", async () => {
  const { Editor } = await import("./Editor");
  new Editor().mount();
});

Cách dùng kinh điển là tách theo route: component của mỗi route chỉ tải khi user điều hướng tới:

// React
const Editor = React.lazy(() => import("./routes/Editor"));

Trong demo, chuyển sang split mode và vào /editor — xem chunk 220 KB của nó tải chỉ ở lần đầu.


3. splitChunks — chunk vendor/chung tự động

Code node_modules (React, lodash…) hiếm khi đổi nhưng to. Trích nó thành chunk vendors để nó cache độc lập với code app:

module.exports = {
  optimization: {
    splitChunks: {
      chunks: "all", // split both sync and async imports
    },
  },
};

chunks: "all" là cài đặt duy nhất làm 90% công việc. Webpack giờ kéo phụ thuộc chung và node_modules vào chunk riêng, và nếu hai route cùng import một util, nó được khử trùng lặp vào chunk chung.

Bạn có thể tinh chỉnh nhóm:

splitChunks: {
  chunks: "all",
  cacheGroups: {
    vendor: { test: /[\\/]node_modules[\\/]/, name: "vendors", priority: 10 },
  },
}

4. runtimeChunk — cache ổn định

Webpack tiêm một runtime nhỏ (bộ tải module) vào bundle. Nếu nó nằm trong chunk vendor, mọi thay đổi app có thể làm vô hiệu hash vendor. Trích nó ra một lần:

optimization: {
  runtimeChunk: "single", // one shared runtime.js across all entries
}

Giờ hash của chunk vendor chỉ đổi khi phụ thuộc thực sự đổi — caching dài hạn tốt hơn (Phần 8).


5. Magic comment

Webpack đọc các comment đặc biệt trong import() để đặt tên và gợi ý chunk:

import(
  /* webpackChunkName: "editor" */
  /* webpackPrefetch: true */
  "./routes/Editor"
);
  • cho chunk một tên đọc được thay vì src_routes_Editor_js.123.js.
  • bảo trình duyệt tải chunk lúc rảnh, để sẵn sàng trước khi user bấm.
  • tải song song với chunk cha (dùng tiết kiệm).

6. Đánh đổi

Tách không miễn phí — mỗi chunk là một request HTTP thêm và chút overhead. Mục tiêu là một bundle ban đầu nhỏ (first paint nhanh) cộng chunk theo yêu cầu cho tính năng nặng, ít ghé. Đừng tách mọi module nhỏ — tách ở ranh giới route và quanh phụ thuộc nặng (thư viện biểu đồ, editor rich-text). Số liệu của demo cho thấy lợi ích initial-download rõ ràng.


7. Bài tập

1. App tải chậm vì một editor rich-text 220 KB dùng ở một trang nằm trong bundle chính. Làm sao hoãn nó?

Lời giải

Tải bằng import("./Editor") động để nó thành chunk riêng chỉ tải ở trang đó.

2. React và lodash bị tải lại mỗi khi bạn đổi code app. Một dòng config để sửa caching?

Lời giải

optimization.splitChunks = { chunks: "all" } để trích node_modules vào chunk vendors cache riêng.

3. Bạn muốn chunk của một route tải lúc rảnh để điều hướng tức thì. Magic comment nào?

Lời giải

/* webpackPrefetch: true */ bên trong import().

Nâng cao:trong trình trực quan, so số “initial download” giữa monolith và split, rồi vào mọi route ở split mode và xem “downloaded so far” chỉ tăng khi bạn đi tới.


Điểm chính

  • import() động tạo chunk theo yêu cầu — nền tảng của tải lười.
  • splitChunks: { chunks: "all" } tự trích code vendor và chung.
  • runtimeChunk: "single" ổn định caching dài hạn.
  • Magic comment đặt tên chunk và kích prefetch/preload.
  • Tách ở ranh giới route và quanh phụ thuộc nặng, không phải khắp nơi.

Tiếp theo

Phần 7 — Tree shaking & production mode: cách Webpack bỏ export không dùng, vai trò của ES module và sideEffects, usedExports, minify với Terser, và vì sao mode: "production" quan trọng.