jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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

ToolNgôn ngữPhân loạiTốt nhất cho
Webpack 5JavaScriptBundlerApp lớn cũ, config phức tạp
Vite 6JavaScript + Rust (via Rolldown)Dev server + BundlerModern web apps (React, Vue, Svelte, Astro)
Rollup 4JavaScriptBundlerLibraries, ES modules
RolldownRustBundlerBundler production của Vite
esbuildGoBundler + TranspilerUltra-fast builds, simple apps, tooling infra
TurbopackRustBundlerNext.js (exclusive)
Parcel 2Rust + JavaScriptBundlerZero-config projects, prototypes
SWCRustTranspiler + MinifierReplacing Babel/Terser in other tools
BunZig + C++Runtime + BundlerFull-stack JS, fast scripts
tsupJavaScript (wraps esbuild)Library bundlerThư viện TypeScript
unbuildJavaScript (wraps Rollup)Library bundlerUniversal 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_modules nhanh hơn
  • TypeScript native: chạy file .ts trự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_modules nhanh 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):

ToolBuild lạnhRebuild nóngKhởi động dev
Webpack 5~18s~2s~8s
Vite 6 (Rolldown)~1.2s~0.1s~300ms
Rollup 4~8s~3sN/A (no dev server)
esbuild~0.4s~0.05sN/A
Turbopack~1.5s~0.08s~400ms
Parcel 2~3s~0.3s~1.5s
Bun build~0.5s~0.06sN/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 conceptVite equivalent
webpack.config.jsvite.config.ts
entryHTML file with <script type="module">
Loaders (babel-loader, css-loader)Built-in transforms + plugins
MiniCssExtractPluginBuilt-in CSS handling
DefinePlugindefine in config
devServerBuilt-in dev server
HtmlWebpackPluginUse HTML file as entry
resolve.aliasresolve.alias (same concept)
require.contextimport.meta.glob()

Các bước di chuyển:

  1. Cài Vite + plugin framework
  2. Tạo index.html là điểm vào
  3. Chuyển biến env sang import.meta.env
  4. Thay require() bằng import
  5. Thay cú pháp riêng Webpack
  6. 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:

  1. Resolve đồ thị dependency từ package.json + lockfile
  2. Fetch package từ registry (mặc định npm registry) vào cache
  3. Link chúng thành layout mà runtime/bundler resolve được (node_modules hoặ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

ManagerNgôn ngữnode_modules Chiến lượcLockfileCó từ
npmJavaScriptPhẳng / hoistedpackage-lock.jsonĐi kèm Node.js
Yarn Classic (v1)JavaScriptPhẳng / hoistedyarn.lock2016 chế độ bảo trì)
Yarn Berry (v2-v4)JavaScriptPnP hoặc linkeryarn.lock + .pnp.cjs2020
pnpmTypeScript + Rust (parts)Symlink → storepnpm-lock.yaml2017
BunZig + C++Hoisted + cache hardlinkbun.lock văn bản2023

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/cache thì 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_modules có 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_modules thậ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ạo node_modules tươ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 đương npx như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:

Manager1 project10 10 projectVì sao
npm~1.0x~10xCopy đầy đủ mỗi project
Yarn Classic~1.0x~10xCopy đầy đủ mỗi project
Bun~1.0x~1-2xCache hardlink toàn cục
pnpm~1.0x~1-2xMộ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ó.

ManagerLockfileFormatGhi chú
npmpackage-lock.jsonJSONDài dòng nhưng phổ quát
Yarn Classicyarn.lockVăn bản riêngĐọc được
Yarn Berryyarn.lock (+ .pnp.cjs)YAML-ishĐi cùng map PnP
pnpmpnpm-lock.yamlYAMLGọn, dễ diff
Bunbun.lockVă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:

ManagerWorkspace configTask lọcKết luận
npmworkspaces in package.json-w, --workspaceChạy được, cơ bản
Yarn Berryworkspaces fieldworkspaces foreach, constraintsMạnh
pnpmpnpm-workspace.yaml--filter, -rTốt nhất
Bunworkspaces field--filterNhanh, 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 packageManager trong package.json — cả team dùng đúng cùng một version.


Tương lai (2026-2027)

Xu hướng

  1. Tooling JavaScript chết: tất cả tool quan trọng performance bây giờ là Rust/Go/Zig
  2. Hội tụ bundler: Vite xử lý hơn 80% project
  3. Bundler thuộc framework: mỗi framework sở hữu bundler riêng
  4. Không config trở thành chuẩn: không ai viết config 200 dòng nữa
  5. Đồng nhất dev/prod: cùng bundler cho cả hai
  6. 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:

  1. tiêu chuẩn phổ quát. Hiểu sâu.
  2. cho script, CLI, và hiểu cách tool nhanh hoạt động
  3. không để dùng, mà để hiểu khi gặp project cũ

Thẻ tham khảo nhanh

Tình huốngChọnVì sao
New React/Vue/Svelte appViteNhanh, đơn giản, chuẩn hệ sinh thái
New Next.js appTurbopackMặc định
Publish TS library to npmtsupDX nhanh nhất cho tác giả thư viện
Quick prototypeParcelKhông config, khởi động tức thì
Large legacy appWebpack (keep)Chi phí di chuyển quá cao
Full-stack scriptBunTất cả trong một
Replace Babel in pipelineSWC20-70Nhanh hơn, tương thích
Custom fast bundlingesbuildCLI đơn giản nhất, tốc độ Go
Universal JS libraryunbuildChế độ stub + auto config

Chọn package manager

Tình huốngChọnVì sao
New project, modern defaultpnpmNhanh, tiết kiệm disk, nghiêm ngặt
Monorepo / many packagespnpm (+ Turborepo)--filter + cô lập tốt nhất
Fastest installsBun10-25Nhanh hơn 10-25 lần khi cold
Strict + zero-installsYarn Berry (PnP)Không node_modules, cache commit được
Maximum compatibilitynpmĐi kèm Node, mặc định khắp nơi
Existing repoGiữ hiện tạiĐừng trộn lockfile