Frontend Build Tools & Package Managers in 2026 — The Complete Comparison
A bilingual comparison of frontend build tools (Webpack, Vite, Rollup, esbuild, Turbopack, SWC, Bun) and package managers (npm, Yarn, pnpm, Bun) — architecture, speed, disk usage, ecosystem, and when to use each.
Bối cảnh Build Tool đã bùng nổ
Năm 2020, lựa chọn rất đơn giản: Webpack cho app, Rollup cho thư viện. chúng ta có hơn 10 ứng viên nghiêm túc, viết bằng JavaScript, Go, và Rust.
Bài viết này bao gồm mọi build tool chính, chúng giỏi gì, và khi nào nên chọn chúng. Sau đó Phần 2 làm điều tương tự cho lớp bên dưới — các package manager (npm, Yarn, pnpm, Bun) cài đặt dependency mà build tool của bạn đem đi bundle.
Hai công việc khác nhau, hay bị nhầm: một build tool biến source của bạn thành output xuất xưởng; một package manager tải và liên kết dependency vào
node_modules. Bạn cần mỗi loại một cái.
Bảng tổng quan nhanh
| Tool | Ngôn ngữ | Phân loại | Tốt nhất cho |
|---|---|---|---|
| Webpack 5 | JavaScript | Bundler | App lớn cũ, config phức tạp |
| Vite 6 | JavaScript + Rust (via Rolldown) | Dev server + Bundler | Modern web apps (React, Vue, Svelte, Astro) |
| Rollup 4 | JavaScript | Bundler | Libraries, ES modules |
| Rolldown | Rust | Bundler | Bundler production của Vite |
| esbuild | Go | Bundler + Transpiler | Ultra-fast builds, simple apps, tooling infra |
| Turbopack | Rust | Bundler | Next.js (exclusive) |
| Parcel 2 | Rust + JavaScript | Bundler | Zero-config projects, prototypes |
| SWC | Rust | Transpiler + Minifier | Replacing Babel/Terser in other tools |
| Bun | Zig + C++ | Runtime + Bundler | Full-stack JS, fast scripts |
| tsup | JavaScript (wraps esbuild) | Library bundler | Thư viện TypeScript |
| unbuild | JavaScript (wraps Rollup) | Library bundler | Universal libraries (CJS + ESM) |
Khái niệm cốt lõi cần nắm trước
Người ta gọi “build tool” cho nhiều việc rất khác nhau. Tách chúng ra thì mọi so sánh bên dưới sẽ rõ ngay:
Your source code (.tsx, .ts, .css, .svg ...)
│
▼
┌──────────────┐ 1. TRANSPILE — strip types, JSX → JS, modern → target JS
│ Transpiler │ (Babel, SWC, esbuild, tsc)
└──────┬───────┘
▼
┌──────────────┐ 2. BUNDLE — resolve imports, build one dependency graph,
│ Bundler │ tree-shake, code-split into chunks
└──────┬───────┘ (Webpack, Rollup, Rolldown, esbuild, Parcel, Turbopack)
▼
┌──────────────┐ 3. MINIFY — shrink output (rename vars, drop whitespace,
│ Minifier │ dead-code elimination) (Terser, SWC, esbuild)
└──────┬───────┘
▼
dist/ output
Separate concern → DEV SERVER + HMR: serve files while you code and
hot-swap only what changed (Vite, Webpack dev server, Turbopack).
Một tool có thể làm một việc hoặc tất cả. esbuild làm 1+2+3; SWC làm 1+3; Rollup làm 2; Vite điều phối tất cả cộng thêm dev server.
ESM vs CJS — vì sao quan trọng
Bundler rất quan tâm tới định dạng module. ESM phân tích tĩnh được, nên bundler biết chính xác cái gì được dùng. CJS động, nên chặn tree-shaking.
ESM (static) CJS (dynamic)
import { a } from './m'; const { a } = require('./m');
→ bundler sees "only a is used" → could require anything at runtime
→ b, c, d are tree-shaken away → whole module kept "just in case"
Tree-shaking trong một hình
Tree-shaking loại bỏ export không ai import:
utils.js exports: [ format, parse, debounce, throttle ]
app.js imports: [ format ]
│
▼ tree-shake
final bundle keeps: [ format ] ← parse/debounce/throttle dropped
Dev server & HMR
HMR thay module đã đổi mà không reload toàn trang, giữ nguyên state. Đây là yếu tố DX hằng ngày lớn nhất, và là chỗ tool tốc-độ-native thắng đậm nhất.
Dòng thời gian ngắn
Làm sao ta đi từ “một lựa chọn hiển nhiên” tới “mười ứng viên”:
2012 ──● Webpack — "everything is a module", loaders/plugins era
│
2015 ──● Rollup — ESM-first, tree-shaking, the library bundler
│
2016 ──● Babel everywhere — JS transpilation standard (but slow)
│
2020 ──● esbuild (Go) — 10-100x faster, sparks the native rewrite wave
│● Snowpack/Vite — "no-bundle" dev with native ESM
│
2021 ──● SWC (Rust) — Babel/Terser killer, adopted by Next.js
│
2022 ──● Turbopack (Rust) — Vercel's incremental bundler for Next.js
│
2023 ──● Bun (Zig) — runtime + bundler + package manager in one
│
2024 ──● Rolldown (Rust) — Rollup rewrite, becomes Vite's prod engine
│
2026 ──● Convergence — Vite/Rolldown is the default; JS-based tools fade
Mạch xuyên suốt: mọi tool thắng cuộc 5 năm qua đều viết bằng ngôn ngữ hệ thống (Go, Rust, Zig).
Webpack 5
Kiến trúc
Vua bundler nguyên thuỷ. Webpack xem mọi thứ là module — và xây dựng đồ thị phụ thuộc.
Entry → Loaders (transform) → Plugins (optimize) → Output chunks
↑ ↑
babel-loader TerserPlugin
css-loader MiniCssExtractPlugin
file-loader HtmlWebpackPlugin
Điểm mạnh
- Hệ sinh thái: hàng nghìn loader và plugin cho mọi trường hợp
- trưởng thành và tuỳ chỉnh được nhất
- kiến trúc micro-frontend tích hợp sẵn
- Ổn định: đã thử nghiệm trong hàng triệu app production
- hoạt động với mọi framework
Điểm yếu
- Tốc độ: bundler chậm nhất 2026. Project lớn: 30-120s build lạnh
- Cấu hình: phức tạp khét tiếng. Config thường 100-300 dòng
- Trải nghiệm dev: khởi động chậm, HMR chậm trên codebase lớn
- Mô hình tư duy: chuỗi loader, hook plugin — đường cong học dốc
Khi nào dùng
- Bảo trì app lớn đã dùng Webpack
- Cần Module Federation cho micro-frontend
- Pipeline build tuỳ chỉnh phức tạp mà không tool nào khác hỗ trợ
Khi nào KHÔNG dùng
- Project mới năm 2026 — Vite hoặc Turbopack (cho Next.js) tốt hơn hoàn toàn
- Nếu tốc độ build quan trọng (luôn quan trọng)
// webpack.config.js — typical setup
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'swc-loader', // replaced babel-loader
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader'],
},
],
},
plugins: [new HtmlWebpackPlugin({ template: './index.html' })],
resolve: { extensions: ['.tsx', '.ts', '.js'] },
};
Vite 6
Kiến trúc
Vite có cách tiếp cận khác căn bản: không bundle khi development. Nó phục vụ ES module native trực tiếp cho browser, dùng hệ thống import của chính browser.
Development:
Source files → esbuild (dep pre-bundling) → Native ESM → Browser
HMR: only re-serves the changed module
Production:
Source files → Rolldown (Rust bundler) → Optimized chunks
Điểm mạnh
- Khởi động dev tức thì: không bundle, chỉ serve file. App hơn 10K module cũng khởi động dưới 500ms
- HMR siêu nhanh: thường dưới 50ms bất kể app size
- Cấu hình đơn giản: 10-30 10-30 dòng cho hầu hết project
- Hệ sinh thái plugin: tương thích plugin Rollup
- Không phụ thuộc framework: đều dùng Vite
- tất cả tích hợp sẵn
Điểm yếu
- Đồng nhất dev/prod: trước đây, dev và prod có thể hoạt động khác nhau. Tích hợp Rolldown sửa vấn đề này
- Pre-bundling dependency lớn: Lần chạy lạnh đầu có thể chậm nếu nhiều dep cần pre-bundle
- Phức tạp SSR: Pipeline SSR ít trưởng thành hơn Next.js/Webpack
Khi nào dùng
- Hầu hết project mới năm 2026 — đây là lựa chọn mặc định
- React (with React Router or TanStack Router), Vue, Svelte, Solid, Astro
- Bất kỳ project nào coi trọng trải nghiệm developer
Khi nào KHÔNG dùng
- Project Next.js (dùng Turbopack)
- Cần Module Federation
// vite.config.ts — typical setup
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
export default defineConfig({
plugins: [react()],
build: {
sourcemap: true,
rollupOptions: {
output: { manualChunks: { vendor: ['react', 'react-dom'] } },
},
},
});
Rollup 4
Kiến trúc
Rollup là bundler native ES module. Nó là bundler đầu tiên thiết kế xung quanh import/export thay vì xem module là thứ phụ.
ES Module sources → Tree-shaking → Scope hoisting → Output (ESM/CJS/UMD)
Điểm mạnh
- Tree-shaking tốt nhất: thiết kế từ đầu cho loại bỏ code chết
- Output nhỏ nhất: scope hoisting tạo bundle nhỏ gọn nhất
- Nhiều định dạng output: từ cùng source
- Plugin API sạch: đơn giản và có thể kết hợp hơn Webpack
- Đã thử nghiệm cho thư viện: hầu hết package npm được build bằng Rollup
Điểm yếu
- Chậm cho app lớn: Dựa trên JavaScript, không song song hoá tốt
- Không có dev server: cần công cụ thêm (vì vậy Vite bọc nó)
- không tích hợp sẵn
- cần plugin cho CommonJS, JSON
Khi nào dùng
- Xây dựng thư viện npm
- Cần output ESM + CJS đồng thời
- Dùng Vite (sử dụng Rollup bên dưới cho production)
Khi nào KHÔNG dùng
- Bundle app (dùng Vite thay thế)
- Cần lặp nhanh
// rollup.config.js — a typical library setup
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import esbuild from 'rollup-plugin-esbuild'; // fast TS transpile
export default {
input: 'src/index.ts',
output: [
{ file: 'dist/index.cjs', format: 'cjs' },
{ file: 'dist/index.mjs', format: 'esm' },
],
external: ['react'], // don't bundle peer deps into a library
plugins: [resolve(), commonjs(), esbuild({ minify: true })],
};
Rolldown
Kiến trúc
Rolldown là Rollup viết lại bằng Rust, được tạo bởi team Vite. Nó nhắm thay thế Rollup hoàn toàn với performance tốt hơn đáng kể.
Rollup API (JavaScript) → Rolldown Core (Rust) → Same output quality
10-30x faster
Điểm mạnh
- API tương thích Rollup: hầu hết plugin Rollup hoạt động không thay đổi
- Nhanh hơn Rollup 10-30 lần: Song song Rust + phân tích zero-copy
- Transform tích hợp: không cần plugin thêm
- Đồng nhất dev/prod: cùng bundler cho cả hai trong Vite
- chất lượng như Rollup
Điểm yếu
- Hệ sinh thái mới: chưa phải tất cả plugin Rollup đều tương thích
- Gắn với Vite: chủ yếu thiết kế cho Vite, dùng độc lập là phụ
- API đang phát triển: một số edge case vẫn đang giải quyết
Khi nào dùng
- Bạn dùng Vite → Rolldown đã là bundler production của bạn
- Xây thư viện trước dùng Rollup và cần build nhanh hơn
Bạn hiếm khi cài Rolldown trực tiếp — bạn chọn nó thông qua Vite:
# Use the Rolldown-powered Vite (the default engine going forward)
npm install vite@latest
# or, to try it standalone as a Rollup drop-in:
npm install -D rolldown
// rolldown.config.js — note the API mirrors Rollup almost 1:1
import { defineConfig } from 'rolldown';
export default defineConfig({
input: 'src/index.ts',
output: { dir: 'dist', format: 'esm' },
// TypeScript/JSX transforms are built in — no extra plugins
});
esbuild
Kiến trúc
Viết bằng Go, esbuild là tool bắt đầu cuộc cách mạng “bundler nhanh”. Nó bundle, transpile, và minify trong một lượt duy nhất.
Source → Parse (parallel) → Link → Tree-shake → Code-gen → Output
All in Go, all in memory, all parallel
Điểm mạnh
- Tốc độ cực nhanh: 10-100nhanh hơn Webpack 10-100 lần. Bundle React trong 40ms
- Không config cho case đơn giản
- TypeScript + JSX tích hợp: không cần babel
- minifier nhanh nhất (thay thế Terser)
- cơ bản nhưng nhanh
- Vai trò hạ tầng: cung cấp sức mạnh cho Vite dep pre-bundling, tsup, nhiều tool khác
Điểm yếu
- Plugin API hạn chế: không làm được mọi thứ plugin Webpack/Rollup có thể
- Không có HMR: nó là bundler, không phải dev server
- Code splitting cơ bản: ít tinh vi hơn Webpack hoặc Rollup
- Không kiểm tra type: xoá type nhưng không xác thực
- Giới hạn CSS: không có PostCSS, CSS Modules
Khi nào dùng
- App/script đơn giản không cần transform phức tạp
- Hạ tầng build tooling: làm transpiler/minifier nhanh bên trong tool khác
- Pipeline CI: khi tốc độ build quan trọng
- Thư viện TypeScript qua tsup
Khi nào KHÔNG dùng
- Cần HMR/dev server
- Pipeline CSS phức tạp
- Cần code splitting nâng cao
# esbuild — one command does it all
esbuild src/index.tsx --bundle --minify --sourcemap --outdir=dist --format=esm
# Finishes in ~50ms for most projects
Turbopack
Kiến trúc
Được Vercel xây bằng Rust, là bundler chỉ dành cho Next.js thiết kế để thay thế Webpack trong hệ sinh thái Next.js.
Source → Turbo Engine (incremental computation) → Optimized output
Function-level caching, maximal parallelism
Only recomputes what changed
Điểm mạnh
- Thiết kế tăng dần: cache cấp hàm nghĩa là chỉ code thay đổi được xử lý lại
- HMR nhanh: giữ state tốt hơn Webpack cho Next.js
- Tối ưu cho React Server Component
- Tích hợp sâu Next.js:
- ổn định cho cả dev và prod
Điểm yếu
- Chỉ Next.js: không thể dùng với framework khác
- Không độc lập: không có CLI, không có package npm riêng cho sử dụng chung
- Ít tuỳ chỉnh: có chủ đích opinionated cho Next.js
- Không tương thích plugin Webpack: hầu hết plugin Webpack không hoạt động
- Hệ sinh thái đóng: gắn với lộ trình Vercel
Khi nào dùng
- Bạn dùng Next.js → Turbopack là mặc định từ v16
Khi nào KHÔNG dùng
- Bất kỳ project không phải Next.js
Parcel 2
Kiến trúc
Triết lý của Parcel là không cần cấu hình. Nó tự động phát hiện mọi thứ từ file source: framework, preprocessor, target.
Source → Asset Graph (auto-detected) → Transforms (parallel, Rust) → Packager → Output
HTML entry points SWC for JS/TS
package.json targets PostCSS for CSS
browserslist for compat Image optimization
Điểm mạnh
- Thực sự không cần config: trỏ vào file HTML, nó tự tìm ra phần còn lại
- dùng Rust (SWC) cho transform, xử lý song song
- có thể output cho nhiều môi trường từ một build
- Mọi thứ tích hợp:
- Điểm vào HTML: khác hầu hết bundler bắt đầu từ JS
Điểm yếu
- Hệ sinh thái nhỏ hơn: ít plugin hơn Webpack hoặc Vite
- Ít được sử dụng: hầu hết framework chọn Vite làm mặc định
- Config khi cần: khi cần tuỳ chỉnh, giải pháp thay thế ít tài liệu hơn
- Ít dự đoán được: phép thuật tự phát hiện có thể gây bất ngờ
Khi nào dùng
- Prototype, demo, thử nghiệm nhanh
- Project vanilla JS/TS không dùng framework
- Khi bạn muốn không mất thời gian setup
Khi nào KHÔNG dùng
- Project team lớn
- App dựa trên framework — framework ưu tiên Vite
# Parcel — literally just this
parcel src/index.html
# Auto-detects TypeScript, React, PostCSS, everything
SWC
Kiến trúc
SWC không phải bundler — nó là compiler dựa trên Rust thay thế Babel và Terser. Nó làm transpilation, minification, và sắp thêm nữa.
Source → SWC Core (Rust) → Transformed output
TypeScript → JavaScript
JSX → createElement calls
Modern JS → Target-compatible JS
Minification (replacing Terser)
Điểm mạnh
- Nhanh hơn Babel 20-70 lần: đơn luồng; nhanh hơn nữa với song song
- Thay thế Babel hoàn toàn: hỗ trợ hầu hết transform của Babel
- Dùng khắp nơi: đều dùng SWC
- nhanh hơn Terser 7 lần với output size tương đương
Điểm yếu
- Không phải bundler: không resolve import, không code splitting
- Hạn chế plugin: API plugin Rust tồn tại nhưng ít truy cập được hơn API JS của Babel
- Thiếu một số plugin Babel: transform rất đặc thù có thể không có
Khi nào dùng
- Bạn đã đang dùng nó
- Cần thay Babel trong pipeline tuỳ chỉnh để nhanh hơn
- Cần minification nhanh thay thế Terser
// .swcrc — drop-in Babel replacement config
{
"jsc": {
"parser": { "syntax": "typescript", "tsx": true },
"transform": { "react": { "runtime": "automatic" } },
"target": "es2022",
"minify": { "compress": true, "mangle": true }
}
}
Bun (Bundler)
Kiến trúc
Bun là JavaScript runtime (như Node.js) bao gồm bundler như phần của chuỗi công cụ. Viết bằng Zig và C++ cho performance tối đa.
Bun Runtime
├── Package manager (bun install) — faster than npm/pnpm
├── Bundler (bun build) — esbuild-class speed
├── Test runner (bun test)
├── Transpiler (native TS/JSX support)
└── Runtime (replaces Node.js)
Điểm mạnh
- Tất cả trong một: runtime + bundler + quản lý package + test runner
- Cực nhanh: tương đương esbuild cho bundle, cài
node_modulesnhanh hơn - TypeScript native: chạy file
.tstrực tiếp không cần build - Tương thích Node.js: hầu hết package npm hoạt động
Điểm yếu
- Bundler cơ bản: ít trưởng thành hơn Vite/Webpack cho app production
- Bundler không có HMR/dev server
- Hỗ trợ framework: framework React/Vue/Svelte vẫn ưu tiên Vite
- Khoảng cách tương thích: một số API Node.js chưa được implement
- Độ trưởng thành production: ít deployment production để học từ
Khi nào dùng
- Script, tool CLI, service backend
- Project muốn một tool cho mọi thứ
- Cần cài
node_modulesnhanh nhất có thể
Khi nào KHÔNG dùng
- App frontend phức tạp với framework
- Cần code splitting cấp production, pipeline CSS
# Bun bundler
bun build ./src/index.tsx --outdir=./dist --minify --sourcemap=external
# Similar to esbuild but with Bun's runtime features
tsup
Kiến trúc
tsup là bundler thư viện có chủ kiến bọc esbuild. Nó được thiết kế cho một trường hợp sử dụng: bundle thư viện TypeScript với config tối thiểu.
Điểm mạnh
- Tập trung thư viện: tạo CJS + ESM + types trong một lệnh
- Nhanh: được esbuild cung cấp sức mạnh
- Tạo DTS: tự động tạo file khai báo
.d.ts - Không cần config: hoạt động chỉ với entry point
package.json
Điểm yếu
- Không cho app: không dev server, HMR, xử lý HTML
- Transform hạn chế: kế thừa hạn chế của esbuild
- DTS có thể chậm: kiểm tra type TypeScript vẫn tốc độ JavaScript
Khi nào dùng
- Xuất bản thư viện TypeScript lên npm
- Cần output CJS/ESM đồng thời với types
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
clean: true,
sourcemap: true,
});
unbuild
Kiến trúc
Được tạo bởi team UnJS (cùng team Nuxt). bọc Rollup cho bundling và mkdist cho build dạng stub.
Điểm mạnh
- Chế độ build thụ động: “chế độ “stub” liên kết với source khi dev, chỉ bundle khi publish
- Config tự suy luận: đọc exports
package.jsonđể xác định entry point - CJS/ESM đồng thời: phát hiện format tự động
Điểm yếu
- Chuyên biệt: thiết kế riêng cho thư viện JS universal
- Ít được biết: cộng đồng nhỏ hơn tsup
- Chậm hơn tsup: dùng Rollup (JS) thay vì esbuild (Go)
Khi nào dùng
- Xây thư viện JS/TS universal
- Muốn workflow dev dạng stub
- Package hệ sinh thái UnJS/Nuxt
// build.config.ts — entries are often auto-inferred from package.json
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
entries: ['src/index'],
declaration: true, // emit .d.ts
clean: true,
rollup: { emitCJS: true }, // dual CJS + ESM
});
Chế độ “stub” là tính năng đắt giá nhất: unbuild --stub ghi một file re-export nhỏ trỏ về source thay vì bundle, nên thay đổi thư viện phản ánh tức thì khi dev trong monorepo.
So sánh tốc độ
Benchmark: bundle app React trung bình (500 module, 50K dòng):
| Tool | Build lạnh | Rebuild nóng | Khởi động dev |
|---|---|---|---|
| Webpack 5 | ~18s | ~2s | ~8s |
| Vite 6 (Rolldown) | ~1.2s | ~0.1s | ~300ms |
| Rollup 4 | ~8s | ~3s | N/A (no dev server) |
| esbuild | ~0.4s | ~0.05s | N/A |
| Turbopack | ~1.5s | ~0.08s | ~400ms |
| Parcel 2 | ~3s | ~0.3s | ~1.5s |
| Bun build | ~0.5s | ~0.06s | N/A |
Thời gian build lạnh, vẽ theo tỉ lệ (mỗi █ ≈ 0.5s):
Cold build (lower = faster)
esbuild █ 0.4s
Bun █ 0.5s
Vite 6 ██ 1.2s
Turbopack ███ 1.5s
Parcel 2 ██████ 3.0s
Rollup 4 ████████████████ 8.0s
Webpack 5 ████████████████████████████████ 18.0s
└── native (Go/Rust/Zig) ──┘ └── JavaScript ──┘
Rebuild nóng — con số bạn cảm nhận hàng trăm lần mỗi ngày:
Hot rebuild (lower = faster)
esbuild ▏ 0.05ms-class (instant)
Bun ▏ ~0.06s
Turbopack ▏ ~0.08s ← incremental, function-level cache
Vite 6 ▏ ~0.1s
Parcel 2 ██ ~0.3s
Webpack 5 ████████████████ ~2s
Rollup 4 ██████████████████████ ~3s (no incremental dev)
Nhận thức quan trọng: tool native nhanh hơn tool JavaScript 10-40 lần. Đây là lý do chính cho sự di chuyển.
Kích thước output cũng quan trọng
Tốc độ không phải tất cả — với thư viện và bundle gửi tới user, output nhỏ hơn thắng. Cùng app React, minify + gzip:
Final bundle size (lower = better)
Rollup 4 ████████████████ ~142 KB ← best scope hoisting
Rolldown ████████████████▏ ~143 KB
Vite 6 ████████████████▏ ~144 KB (Rolldown under the hood)
esbuild ██████████████████ ~158 KB (looser minify/splitting)
Webpack 5 ███████████████████ ~165 KB (runtime overhead)
Nhóm Rollup vẫn cho output gọn nhất nhờ scope hoisting và tree-shaking trưởng thành; esbuild đánh đổi vài KB lấy tốc độ.
Mối quan hệ hệ sinh thái
Hiểu tool nào dùng tool nào là rất quan trọng:
┌─────────────────────────────────────────────────────────────────────────┐
│ Framework Layer │
│ Next.js (Turbopack) │ Nuxt (Vite) │ Astro (Vite) │ SvelteKit (Vite) │
├─────────────────────────────────────────────────────────────────────────┤
│ Bundler Layer │
│ Webpack 5 │ Vite 6 │ Turbopack │ Parcel 2 │
├─────────────────────────────────────────────────────────────────────────┤
│ Engine Layer │
│ Rolldown (Rust) │ esbuild (Go) │ SWC (Rust) │
│ └─ Vite prod │ └─ Vite deps │ └─ Next.js, Vite, Parcel │
│ │ └─ tsup │ └─ Transpile + Minify │
└─────────────────────────────────────────────────────────────────────────┘
- Vite uses esbuild (dep pre-bundling) + Rolldown (production bundling) + SWC (transpilation via plugin)
- Next.js uses Turbopack (bundling) + SWC (transpilation)
- Parcel uses SWC (transpilation) + custom Rust core (bundling)
- tsup uses esbuild (everything)
Sơ đồ quyết định
What are you building?
{Bạn đang xây gì?}
├── Web application {Ứng dụng web}
│ ├── Using Next.js? → Turbopack (default, no choice needed)
│ ├── Using other framework (React Router, Vue, Svelte, Astro)?
│ │ └── Vite (industry standard)
│ ├── Vanilla JS/TS, want zero config?
│ │ └── Parcel (auto-detects everything)
│ └── Legacy app with Webpack?
│ └── Keep Webpack (migration cost > benefit for now)
│
├── npm library {Thư viện npm}
│ ├── TypeScript, simple output?
│ │ └── tsup (fast, minimal config)
│ ├── Need tree-shaking + multiple formats?
│ │ └── Rollup or Rolldown
│ └── Universal library (UnJS ecosystem)?
│ └── unbuild
│
├── Script / CLI tool {Script / tool CLI}
│ ├── Want all-in-one runtime?
│ │ └── Bun (runtime + bundler + test)
│ └── Just need fast bundling?
│ └── esbuild (one command)
│
└── Custom build pipeline {Pipeline build tuỳ chỉnh}
├── Need transpilation speed?
│ └── SWC (replace Babel)
└── Need everything + existing Webpack ecosystem?
└── Webpack 5 (last resort)
Hướng dẫn di chuyển
Webpack sang Vite
Di chuyển phổ biến nhất năm 2026:
| Webpack concept | Vite equivalent |
|---|---|
webpack.config.js | vite.config.ts |
entry | HTML file with <script type="module"> |
Loaders (babel-loader, css-loader) | Built-in transforms + plugins |
MiniCssExtractPlugin | Built-in CSS handling |
DefinePlugin | define in config |
devServer | Built-in dev server |
HtmlWebpackPlugin | Use HTML file as entry |
resolve.alias | resolve.alias (same concept) |
require.context | import.meta.glob() |
Các bước di chuyển:
- Cài Vite + plugin framework
- Tạo
index.htmllà điểm vào - Chuyển biến env sang
import.meta.env - Thay
require()bằngimport - Thay cú pháp riêng Webpack
- Test, sửa, lặp
Phần 2 — Package Manager
Build tool vô dụng nếu không có dependency, và package manager là thứ đặt chúng lên disk. Nó làm ba việc:
- Resolve đồ thị dependency từ
package.json+ lockfile - Fetch package từ registry (mặc định npm registry) vào cache
- Link chúng thành layout mà runtime/bundler resolve được (
node_moduleshoặc Plug’n’Play)
Toàn bộ cuộc đua giữa npm, Yarn, pnpm và Bun nằm ở cách chúng làm bước 3 — và tốc độ làm cả ba.
Vấn đề node_modules
npm thời đầu lồng dependency theo đúng nghĩa đen, tạo ra lỗi vượt độ dài path khét tiếng trên Windows. Cách sửa là hoisting: làm phẳng mọi thứ lên đầu node_modules. Việc đó sửa độ sâu nhưng tạo hai vấn đề mới:
Flat / hoisted node_modules (npm, Yarn Classic)
{node_modules phẳng / hoisted}
node_modules/
├── react/ ← your direct dep
├── lodash/ ← HOISTED — a transitive dep of something else
└── ... ← thousands of packages, all flat
Problem 1 — Phantom dependencies {Dependency ma}:
your code can `import lodash` even though it is NOT in your
package.json. It works by accident, breaks when the hoist changes.
Problem 2 — Doppelgangers / duplication {Trùng lặp}:
app needs lodash@4, a dep needs lodash@3 → BOTH copied to disk,
sometimes many times across the tree.
pnpm và Yarn Berry sinh ra để diệt đúng hai vấn đề này.
Tổng quan Package Manager
| Manager | Ngôn ngữ | node_modules Chiến lược | Lockfile | Có từ |
|---|---|---|---|---|
| npm | JavaScript | Phẳng / hoisted | package-lock.json | Đi kèm Node.js |
| Yarn Classic (v1) | JavaScript | Phẳng / hoisted | yarn.lock | 2016 chế độ bảo trì) |
| Yarn Berry (v2-v4) | JavaScript | PnP hoặc linker | yarn.lock + .pnp.cjs | 2020 |
| pnpm | TypeScript + Rust (parts) | Symlink → store | pnpm-lock.yaml | 2017 |
| Bun | Zig + C++ | Hoisted + cache hardlink | bun.lock văn bản | 2023 |
Ba cách bố trí dependency
1. FLAT / HOISTED (npm, Yarn Classic, Bun)
node_modules/{react, lodash, …} ← one big flat folder, copied per project
✓ simple resolution · ✗ phantom deps · ✗ disk duplication across projects
2. CONTENT-ADDRESSABLE STORE + SYMLINKS (pnpm)
~/.pnpm-store/<hash>/… ← every version stored ONCE, globally
node_modules/.pnpm/… ← real files (hardlinked from store)
node_modules/react → symlink ← only your DIRECT deps are visible
✓ no phantom deps · ✓ near-zero extra disk per project · ✓ fast
3. PLUG'N'PLAY (Yarn Berry)
.pnp.cjs ← a resolver map; NO node_modules at all
.yarn/cache/*.zip ← deps stay zipped on disk
✓ instant installs · ✓ strict · ✗ needs PnP-aware tooling
npm
Kiến trúc
npm là mặc định — nó đi kèm Node.js, nên là chuẩn để mọi tool khác đối chiếu. Nó cài node_modules phẳng, hoisted và ghi version chính xác vào package-lock.json.
package.json + package-lock.json
│ npm install
▼
resolve graph → fetch to ~/.npm cache → hoist & copy into node_modules/
Điểm mạnh
- Không cần cài đặt: đã có sẵn trên mọi máy có Node.js
- Tương thích tối đa: mọi tool, CI, tutorial đều mặc định npm
npx: chạy binary của package mà không cài global- Workspaces: hỗ trợ monorepo tích hợp từ v7
- Cache + offline: cache nội dung dùng chung
Điểm yếu
- Chậm nhất trong bốn: vẫn copy file theo từng project
- Dependency ma: hoisting cho phép import package chưa khai báo
- Trùng lặp disk: 10 10 project = 10 bản copy đầy đủ
- Không nghiêm ngặt: layout lỏng lẻo giấu lỗi dependency đến khi vỡ
Khi nào dùng
- Mặc định cho project nhỏ/đơn giản và thử nghiệm nhanh
- Môi trường cần tương thích tối đa
npm install # install from package-lock.json
npm ci # clean, reproducible install for CI {cài sạch cho CI}
npm install react # add a dependency
npx vite # run a binary without a global install
Yarn (Classic + Berry)
Kiến trúc
Yarn có hai thế hệ rất khác nhau. Classic (v1) là bản npm clone nhanh hơn, xác định hơn, cùng node_modules phẳng. Berry (v2-v4) là bản viết lại quanh Plug’n’Play (PnP): không có node_modules luôn — thay vào đó .pnp.cjs ánh xạ mọi import tới package nén trong .yarn/cache.
Yarn Classic: yarn.lock → flat node_modules (like npm, but faster)
Yarn Berry: yarn.lock + .pnp.cjs → NO node_modules
imports resolved through PnP map → .yarn/cache/*.zip
Điểm mạnh
- PnP = tức thì + nghiêm ngặt: không copy file, và dependency ma là không thể
- Zero-install: commit
.yarn/cachethì clone về chạy luôn không cần install - Hệ thống plugin: CLI mở rộng được
- Workspaces tốt: tooling monorepo trưởng thành
- Classic vẫn phổ biến: lượng cài đặt lớn, rất ổn định
Điểm yếu
- Ma sát tương thích PnP: tool quét trực tiếp
node_modulescó thể cần vá - Hai thế giới: Classic và Berry như hai tool khác nhau — migrate là việc thật
- Classic đã ngừng phát triển: không thêm tính năng mới
- Linker
node-modules: nhiều team tắt PnP và mất nửa lợi ích
Khi nào dùng
- Berry khi muốn nghiêm ngặt + zero-install và toolchain sẵn sàng PnP
- Classic chỉ để bảo trì repo v1 cũ
yarn # install (Classic or Berry)
yarn add react # add a dependency
yarn dlx vite # Berry's npx equivalent {tương đương npx}
yarn workspaces foreach run build # run a script across the monorepo
pnpm
Kiến trúc
pnpm giữ một store địa chỉ-theo-nội-dung toàn cục duy nhất nơi mỗi version package được lưu đúng một lần. node_modules của mỗi project dựng từ hardlink tới store, cộng symlink chỉ phơi bày dependency bạn đã khai báo.
~/.local/share/pnpm/store/ ← global store: react@19 stored ONCE
│ hardlink (no copy, no extra disk)
▼
project/node_modules/.pnpm/react@19/… ← real files
project/node_modules/react → symlink ───┘ ← only DIRECT deps visible
Điểm mạnh
- Tiết kiệm disk: 10 10 project chia sẻ một store — tổng dung lượng gần như không đổi
- Nghiêm ngặt mặc định: layout symlink khiến dependency ma là không thể
- Nhanh: hardlink nhanh hơn copy; install nóng rất nhanh
- Monorepo hạng nhất:
pnpm-workspace.yaml, lọc và chạy task theo--filter - Tương thích npm: cùng lệnh, cùng registry,
node_modulesthật
Điểm yếu
- Symlink làm vài tool bất ngờ: vài bundler hiếm không theo symlink cần config
- Nghiêm ngặt = sửa nhiều lúc đầu: migrate lộ ra dependency ma từng bị ẩn
- Ít mặc định hơn npm: phải cài/bật (qua Corepack)
Khi nào dùng
- Hầu hết team năm 2026 — điểm cân bằng giữa tốc độ, disk và đúng đắn
- Monorepo mọi kích thước
- CI nơi disk và thời gian install bị tính tiền
corepack enable # activate pnpm/yarn shipped with Node {bật pnpm/yarn}
pnpm install # install using the global store
pnpm add react # add a dependency
pnpm -r run build # run "build" in every workspace package
pnpm --filter web dev # target a single workspace {nhắm một workspace}
Bun (Package Manager)
Kiến trúc
Ngoài bundler đã nói ở Phần 1, bun install là một package manager độc lập viết bằng Zig. Nó là installer nhanh nhất hiện có, dùng cache hardlink toàn cục và lockfile. Từ Bun 1.2 lockfile mặc định là bun.lock dạng văn bản (bun.lockb nhị phân cũ là legacy).
bun install
→ resolve → global cache (~/.bun/install/cache, hardlinked)
→ hoisted node_modules (npm-compatible layout) → bun.lock (text)
Often 10-25x faster than npm on a cold install.
Điểm mạnh
- Install nhanh nhất, cách biệt lớn: thường nhanh hơn npm 10-25 lần khi cold
- Thay thế trực tiếp: đọc
package.json, tạonode_modulestương thích npm - Tất cả trong một: cùng binary là runtime, bundler, test runner và PM
- Workspaces: hỗ trợ monorepo tích hợp
bunx: tương đươngnpxnhưng nhanh
Điểm yếu
- Hệ sinh thái non hơn: ít năm tôi luyện edge-case hơn npm/Yarn
- Layout hoisted: chia sẻ sự lỏng lẻo dependency-ma của npm (không nghiêm ngặt như pnpm)
- Tốt nhất trong thế giới Bun: vài lợi thế giả định bạn cũng dùng Bun runtime
- Đôi khi có khoảng cách tương thích: vài edge case native/postinstall hiếm
Khi nào dùng
- Bạn muốn install nhanh nhất có thể ở dev và CI
- Project đã dùng Bun runtime/bundler
- App mới sẵn sàng dùng tool mới hơn
bun install # blazing-fast install → bun.lock
bun add react # add a dependency
bun run build # run a package.json script
bunx vite # npx equivalent {tương đương npx}
Tốc độ Install & Disk
Ý nghĩa benchmark: app trung bình (~1.200 dependency). Con số mang tính bậc độ lớn minh hoạ, không phải chính xác phòng lab — kết quả thật thay đổi theo mạng, disk và trạng thái cache.
Install lạnh — không cache, không lockfile tắt:
Cold install (lower = faster)
Bun ███ ~8s
Yarn Berry ████████ ~20s (PnP, no node_modules copy)
pnpm ██████████ ~25s
Yarn Classic ████████████ ~30s
npm ████████████████ ~40s
Install nóng — đã có cache + lockfile (con số hằng ngày):
Warm install (lower = faster)
Bun ▏ ~1s
Yarn Berry █ ~2s (zero-installs → can be ~0s)
pnpm ██ ~3s
Yarn Classic ██████ ~6s
npm ██████████ ~10s
Dung lượng disk qua 10 project chia sẻ phần lớn dependency — đây là nơi chiến lược layout quyết định:
| Manager | 1 project | 10 10 project | Vì sao |
|---|---|---|---|
| npm | ~1.0x | ~10x | Copy đầy đủ mỗi project |
| Yarn Classic | ~1.0x | ~10x | Copy đầy đủ mỗi project |
| Bun | ~1.0x | ~1-2x | Cache hardlink toàn cục |
| pnpm | ~1.0x | ~1-2x | Một store toàn cục, hardlink |
| Yarn Berry (PnP) | nhỏ | nhỏ | Cache nén, không node_modules |
Điểm rút ra: Bun thắng tốc độ thô; pnpm và Yarn Berry thắng về disk và đúng đắn; npm thắng về phổ biến.
So sánh Lockfile
Lockfile ghim chính xác cây đã resolve để install tái lập được. Đừng sửa tay; hãy commit nó.
| Manager | Lockfile | Format | Ghi chú |
|---|---|---|---|
| npm | package-lock.json | JSON | Dài dòng nhưng phổ quát |
| Yarn Classic | yarn.lock | Văn bản riêng | Đọc được |
| Yarn Berry | yarn.lock (+ .pnp.cjs) | YAML-ish | Đi cùng map PnP |
| pnpm | pnpm-lock.yaml | YAML | Gọn, dễ diff |
| Bun | bun.lock | Văn bản (từ 1.2) | Thay bun.lockb nhị phân |
Quy tắc vàng: commit đúng một lockfile; dùng các lệnh frozen trong CI để build tái lập; đừng trộn hai manager trong một repo.
Monorepo & Workspaces
Cả bốn đều hỗ trợ workspaces — nhiều package trong một repo dùng chung một lần install — nhưng độ trưởng thành khác nhau:
| Manager | Workspace config | Task lọc | Kết luận |
|---|---|---|---|
| npm | workspaces in package.json | -w, --workspace | Chạy được, cơ bản |
| Yarn Berry | workspaces field | workspaces foreach, constraints | Mạnh |
| pnpm | pnpm-workspace.yaml | --filter, -r | Tốt nhất |
| Bun | workspaces field | --filter | Nhanh, mới hơn |
Với monorepo, pnpm là mặc định phổ biến năm 2026 nhờ cô lập nghiêm ngặt + công thái học --filter, thường ghép với Turborepo hoặc Nx để cache task.
Sơ đồ quyết định Package Manager
Which package manager?
{Dùng package manager nào?}
├── Brand-new project, want a safe modern default? {Project mới, muốn mặc định hiện đại an toàn?}
│ └── pnpm (fast, disk-efficient, strict, great monorepos)
│
├── A monorepo / many packages? {Monorepo / nhiều package?}
│ └── pnpm (--filter) ─ optionally + Turborepo/Nx for caching
│
├── Absolute fastest installs, OK with a newer tool? {Install nhanh tuyệt đối, chấp nhận tool mới?}
│ └── Bun (especially if already using the Bun runtime)
│
├── Want strict + zero-installs and your tooling is PnP-ready? {Muốn nghiêm ngặt + zero-install?}
│ └── Yarn Berry (PnP)
│
├── Maximum compatibility / locked CI / tiny project? {Tương thích tối đa / CI khoá / project nhỏ?}
│ └── npm (it's already there)
│
└── Maintaining an existing repo? {Bảo trì repo có sẵn?}
└── Keep its current manager — don't mix lockfiles {Giữ manager hiện tại — đừng trộn lockfile}
Mẹo: dùng Corepack (đi kèm Node.js) để ghim package manager cho từng project qua field
packageManagertrongpackage.json— cả team dùng đúng cùng một version.
Tương lai (2026-2027)
Xu hướng
- Tooling JavaScript chết: tất cả tool quan trọng performance bây giờ là Rust/Go/Zig
- Hội tụ bundler: Vite xử lý hơn 80% project
- Bundler thuộc framework: mỗi framework sở hữu bundler riêng
- Không config trở thành chuẩn: không ai viết config 200 dòng nữa
- Đồng nhất dev/prod: cùng bundler cho cả hai
- Package manager hội tụ: npm vẫn là mặc-định-của-mặc-định, nhưng pnpm đã thành chuẩn của team và Bun tiếp tục đẩy tốc độ install; Corepack ghim lựa chọn cho từng repo
Nên học gì
Nếu bạn bắt đầu hôm nay:
- tiêu chuẩn phổ quát. Hiểu sâu.
- cho script, CLI, và hiểu cách tool nhanh hoạt động
- không để dùng, mà để hiểu khi gặp project cũ
Thẻ tham khảo nhanh
| Tình huống | Chọn | Vì sao |
|---|---|---|
| New React/Vue/Svelte app | Vite | Nhanh, đơn giản, chuẩn hệ sinh thái |
| New Next.js app | Turbopack | Mặc định |
| Publish TS library to npm | tsup | DX nhanh nhất cho tác giả thư viện |
| Quick prototype | Parcel | Không config, khởi động tức thì |
| Large legacy app | Webpack (keep) | Chi phí di chuyển quá cao |
| Full-stack script | Bun | Tất cả trong một |
| Replace Babel in pipeline | SWC | 20-70Nhanh hơn, tương thích |
| Custom fast bundling | esbuild | CLI đơn giản nhất, tốc độ Go |
| Universal JS library | unbuild | Chế độ stub + auto config |
Chọn package manager
| Tình huống | Chọn | Vì sao |
|---|---|---|
| New project, modern default | pnpm | Nhanh, tiết kiệm disk, nghiêm ngặt |
| Monorepo / many packages | pnpm (+ Turborepo) | --filter + cô lập tốt nhất |
| Fastest installs | Bun | 10-25Nhanh hơn 10-25 lần khi cold |
| Strict + zero-installs | Yarn Berry (PnP) | Không node_modules, cache commit được |
| Maximum compatibility | npm | Đi kèm Node, mặc định khắp nơi |
| Existing repo | Giữ hiện tại | Đừng trộn lockfile |