Bundler là một công cụ xây dựng kết hợp nhiều module JavaScript thành một hoặc nhiều tệp để sử dụng trong trình duyệt hoặc ứng dụng di động. Các bundler hiện đại (Webpack, Metro, Vite) không chỉ hợp nhất các tệp mà còn thực hiện biên dịch, rút gọn và tối ưu hóa tài nguyên. Theo Webpack Concepts (2026), cấu hình bundler đúng cách giúp giảm kích thước bản dựng từ 40–60% mà không mất chức năng.
Những điểm chính
Bundler (trình đóng gói module) là một công cụ dòng lệnh nhận đầu vào là điểm vào (entry point) — tệp chính của ứng dụng — và duyệt đệ quy tất cả các phụ thuộc của nó, xây dựng một đồ thị module. Đầu ra, bundler tạo ra một hoặc nhiều tệp có thể được nhúng vào trang HTML hoặc chạy trong ứng dụng di động.
Trong một thời gian dài, trình duyệt không hỗ trợ hệ thống module JavaScript ở cấp nền tảng. Bundler đã giải quyết vấn đề này bằng cách biến hàng trăm import và require thành một tệp duy nhất. Ngày nay, ngay cả với hỗ trợ module ES gốc trong trình duyệt, bundler vẫn thực hiện các tác vụ bổ sung: biên dịch JSX và TypeScript, rút gọn mã, chia tách mã để tải chậm và thay thế module nóng (HMR) để tăng tốc phát triển.
Trong phát triển di động, React Native sử dụng Metro làm bundler mặc định, trong khi Flutter sử dụng hệ thống xây dựng riêng dựa trên Dart. Việc chọn bundler ảnh hưởng trực tiếp đến tốc độ phát triển, kích thước ứng dụng và hiệu suất thời gian chạy.
Đóng gói trải qua một số giai đoạn: phân tích tệp đầu vào, xây dựng đồ thị phụ thuộc, biến đổi module và tạo tệp đầu ra. Ở mỗi giai đoạn, bundler áp dụng loader để biến đổi mã nguồn và plugin để tối ưu hóa.
Bundler bắt đầu từ điểm vào, đọc tệp và xây dựng AST (Cây cú pháp trừu tượng). Từ AST, tất cả các câu lệnh import và require được trích xuất. Với mỗi import tìm thấy, bundler lặp lại quy trình một cách đệ quy cho đến khi tất cả các phụ thuộc được thu thập vào một đồ thị duy nhất.
// Triển khai đơn giản hóa của một bundler
const fs = require('fs');
const path = require('path');
function buildGraph(entry) {
const content = fs.readFileSync(entry, 'utf-8');
const imports = content.match(/require\(['"](.+?)['"]\)/g);
const modulePath = path.resolve(path.dirname(entry), imports[0]);
return { entry, content, deps: [buildGraph(modulePath)] };
}
Sau khi xây dựng đồ thị, bundler áp dụng loader — các phép biến đổi chuyển đổi tệp thành JavaScript: TypeScript → JS, SCSS → CSS, JSX → hàm JSX. Sau đó, plugin thực hiện các biến đổi bổ sung: rút gọn, nhúng hình ảnh, tạo service worker.
Bundler được chia thành ba thế hệ: cổ điển, chuyên biệt và thế hệ mới dựa trên module ES. Mỗi loại có kiến trúc và phạm vi ứng dụng riêng.
Webpack là bundler phổ biến nhất với hệ sinh thái khổng lồ. Ưu điểm chính của nó là tính linh hoạt: hàng nghìn loader và plugin bao phủ hầu hết mọi kịch bản xây dựng. Parcel cung cấp phương pháp zero-config, tự động phát hiện các biến đổi cần thiết dựa trên phần mở rộng tệp. Cả hai bundler đều hỗ trợ chia tách mã và HMR.
Metro được tạo riêng cho React Native và chỉ hoạt động với JavaScript và TypeScript. Kiến trúc của nó được tối ưu hóa cho phát triển di động: hỗ trợ inline requires, tải module không đồng bộ và tích hợp với công cụ Hermes. Rollup tập trung vào thư viện và gói npm, tạo đầu ra module ES sạch mà không có wrapper không cần thiết.
Việc chọn bundler phụ thuộc vào nền tảng và yêu cầu dự án. Webpack linh hoạt nhưng yêu cầu cấu hình chi tiết. Metro là lựa chọn duy nhất cho React Native. Vite cung cấp trải nghiệm phát triển nhanh nhất nhờ module ES gốc.
| Đặc điểm | Webpack | Metro | Vite |
|---|---|---|---|
| Nền tảng | Web, phổ quát | React Native | Web, phổ quát |
| Tốc độ xây dựng dev | Trung bình | Cao | Rất cao |
| Chia tách mã | Có | Có | Có |
| HMR | Có | Fast Refresh | HMR tức thì |
| Hệ sinh thái plugin | Rộng lớn | Hạn chế | Đang phát triển |
| Cấu hình | Phức tạp | Đơn giản | Đơn giản |
| Hỗ trợ TypeScript | Qua loader | Tích hợp sẵn | Tích hợp sẵn |
Vite sử dụng esbuild để đóng gói trước phụ thuộc và Rollup cho bản dựng production, mang lại cải thiện tốc độ gấp 5–10 lần so với Webpack ở chế độ phát triển. Metro không hỗ trợ mục tiêu web nhưng cung cấp tích hợp liền mạch với cầu nối React Native và Turbo Modules.
Cấu hình bundler xác định cách xử lý các loại tệp khác nhau, nơi tạo bản dựng và tối ưu hóa nào được áp dụng. Một tệp cấu hình điển hình bao gồm điểm vào, quy tắc cho module, plugin và cài đặt đầu ra.
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: { loader: 'babel-loader', options: { presets: ['@babel/preset-react'] } },
},
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
],
},
plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
devServer: { port: 3000, hot: true },
};
Metro sử dụng các tệp riêng biệt metro.config.js và babel.config.js để cấu hình. Không giống Webpack, cấu hình của Metro đơn giản hơn nhiều vì bundler được chuyên biệt hóa cho React Native và không yêu cầu cấu hình loader cho kiểu dáng hay hình ảnh.
Ngay cả các nhà phát triển giàu kinh nghiệm cũng gặp lỗi đóng gói. Duplicate module là một trong những vấn đề phổ biến nhất, khi cùng một phụ thuộc xuất hiện nhiều lần trong bundle do không tương thích phiên bản trong package.json. Module not found xảy ra khi đường dẫn import không đúng hoặc gói chưa được cài đặt.
Metro bị giới hạn bởi kích thước bundle cho thiết bị di động. Khi vượt quá giới hạn 2–5 MB, lỗi Unable to resolve module xảy ra do thiếu bộ nhớ trên thiết bị. Giải pháp là sử dụng inline requires và RAM bundles để tải chậm module. Metro cũng nhạy cảm với symlink, do đó monorepo yêu cầu cấu hình bổ sung watchFolders.
Đối với Webpack, vấn đề điển hình là Module parse failed, xảy ra khi tệp có định dạng không mong đợi hoặc thiếu loader cần thiết. Kiểm tra phần mở rộng tệp và thêm quy tắc phù hợp trong module.rules giải quyết vấn đề. Sử dụng source-map-explorer giúp tìm các module trùng lặp và phụ thuộc dư thừa trong bundle cuối cùng.
Xây dựng chậm là một trong những vấn đề chính khi làm việc với bundler. Để tăng tốc, hãy sử dụng bộ nhớ đệm liên tục (persistent caching), lưu kết quả biến đổi module giữa các lần chạy. Webpack 5 hỗ trợ bộ nhớ đệm tệp qua cache: { type: 'filesystem' }, Vite sử dụng esbuild để đóng gói trước và Metro dựa vào bộ nhớ đệm module trong bộ nhớ. Ngoài ra, chỉ định exclude cho node_modules trong loader để tránh xử lý lại các gói đã sẵn sàng.
Chia tách mã (Code splitting) chia bundle thành các phần được tải theo yêu cầu. Các bundler khác nhau triển khai chiến lược này theo cách riêng. Webpack hỗ trợ import động import(), tạo các phần riêng biệt cho mỗi module. Metro sử dụng inline requires để tải trì hoãn. Vite tự động chia tách vendor chunks và import động mà không cần cấu hình bổ sung. Việc chọn chiến lược phụ thuộc vào yêu cầu dự án: đối với ứng dụng di động, bundle ban đầu nhỏ hơn với tải chậm được ưu tiên.
Để code splitting hoạt động chính xác trong React Native, cần cấu hình bổ sung: Metro phải được cấu hình cho các phần không đồng bộ qua inlineRequires. Trong Webpack và Vite, code splitting hoạt động ngay khi sử dụng cú pháp động import(), tự động tạo điểm chia tách. Để đạt hiệu quả tối đa, kết hợp code splitting với gợi ý tải trước qua <link rel="preload"> cho các phần quan trọng.
Câu hỏi thường gặp
Đối với các dự án đơn giản không có JSX, TypeScript hoặc module CSS, bạn có thể sử dụng module ES gốc trong trình duyệt. Tuy nhiên, đối với bản dựng production, bundler cung cấp tính năng rút gọn, tree shaking và chia tách mã, rất quan trọng cho hiệu suất. Không có bundler, rất khó duy trì kiến trúc module trong các dự án lớn.
Đối với React Native — Metro (lựa chọn duy nhất được hỗ trợ). Đối với ứng dụng web — Vite cho dự án mới (nhờ tốc độ) hoặc Webpack cho dự án hiện có với hệ sinh thái phong phú. Đối với thư viện và gói npm — Rollup, vì nó tạo đầu ra module ES sạch.
Bundler trực tiếp xác định kích thước bundle JS cuối cùng. Webpack với tree shaking có thể giảm kích thước 30–50%. Metro cho React Native hỗ trợ bytecode Hermes, giúp giảm kích thước 20–30% so với JavaScript thông thường. Code splitting cho phép tải module theo yêu cầu, giảm bundle ban đầu.
Đối với web, bundler tạo mã tương thích với các trình duyệt khác nhau và hỗ trợ CSS, hình ảnh và phông chữ. Đối với nền tảng di động, Metro tạo mã cho công cụ JavaScript (Hermes hoặc JSC), không xử lý CSS hay HTML và tối ưu hóa bản dựng cho tài nguyên hạn chế của thiết bị.
Có, một số dự án sử dụng Webpack cho phần web và Metro cho React Native — ví dụ, trong monorepo với cơ sở mã dùng chung. Tuy nhiên, điều này làm phức tạp cấu hình CI/CD và yêu cầu đồng bộ hóa phiên bản phụ thuộc. Khuyến nghị sử dụng một bundler duy nhất cho tất cả mục tiêu nếu có thể.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm