Vite · Part 1 — The Mental Model & Your First Project
Why Vite is fast: native ESM in dev means no bundling on startup, while production still bundles with Rolldown. Scaffold your first project, understand dev vs build, and see the two-strategy design. With a dev-startup race demo.
If you came from the Webpack series, you know the pain it solves — and the price: the dev server has to build your whole app before it can serve a single page {Nếu bạn đến từ series Webpack, bạn biết nỗi đau nó giải quyết — và cái giá: dev server phải build cả app trước khi phục vụ một trang}. Vite’s bet is simpler: in development, let the browser do the bundling via native ES modules, and only bundle for real in production {Cược của Vite đơn giản hơn: khi dev, để trình duyệt tự ghép qua ES module native, và chỉ bundle thật khi production}.
Drag the project size and race a bundler against Vite’s dev startup {Kéo kích thước dự án và đua một bundler với khởi động dev của Vite}:
1. The core insight {Nhận thức cốt lõi}
Modern browsers support ES modules natively — <script type="module"> and import/export just work {Trình duyệt hiện đại hỗ trợ ES module native — <script type="module"> và import/export chạy được luôn}. So in dev, Vite doesn’t bundle {Vậy khi dev, Vite không bundle}:
- It starts a dev server instantly, regardless of app size {Nó khởi động dev server tức thì, bất kể kích thước app}.
- When the browser requests a module, Vite transforms that one file (TS→JS, JSX, etc.) and sends it {Khi trình duyệt yêu cầu một module, Vite biến đổi đúng file đó rồi gửi đi}.
- Files you never load are never processed {File bạn không bao giờ tải sẽ không bao giờ được xử lý}.
A bundler’s startup grows with your codebase; Vite’s stays nearly flat {Khởi động của bundler tăng theo codebase; của Vite gần như phẳng}. That’s the gap you just saw in the demo {Đó là khoảng cách bạn vừa thấy trong demo}.
2. Two strategies, on purpose {Hai chiến lược, có chủ đích}
Here’s the part people miss: Vite uses different tools for dev and prod {Đây là phần nhiều người bỏ lỡ: Vite dùng công cụ khác nhau cho dev và prod}.
| Phase {Giai đoạn} | Strategy {Chiến lược} | Why {Vì sao} |
|---|---|---|
| dev | native ESM, no bundle {ESM native, không bundle} | instant startup, per-file transform {khởi động tức thì, biến đổi từng file} |
| build | bundle with Rolldown {bundle bằng Rolldown} | unbundled ESM = thousands of requests, too slow for real users {ESM không bundle = hàng nghìn request, quá chậm cho người dùng thật} |
Unbundled native ESM is great for you on localhost, but terrible for a user on a real network — each import is another round trip {ESM native không bundle tốt cho bạn trên localhost, nhưng tệ cho người dùng trên mạng thật — mỗi import là một vòng round trip}. So production gets a proper optimized bundle {Nên production nhận một bundle tối ưu đúng nghĩa}.
Vite 8 (March 2026) unified the toolchain: Rolldown (a Rust bundler) now powers production builds, and Oxc handles JS/TS transforms — replacing the old esbuild + Rollup pair {Vite 8 (3/2026) hợp nhất chuỗi công cụ: Rolldown (bundler viết bằng Rust) lo build production, và Oxc lo biến đổi JS/TS — thay cặp esbuild + Rollup cũ}.
3. Scaffold your first project {Dựng dự án đầu tiên}
npm create vite@latest my-app
# pick a framework (Vanilla, React, Vue, Svelte, Solid, Qwik…)
cd my-app
npm install
npm run dev
The dev server is up in well under a second {Dev server lên trong chưa tới một giây}. The scripts you’ll live in {Các script bạn sẽ sống cùng}:
// package.json
{
"scripts": {
"dev": "vite", // start the dev server (native ESM)
"build": "vite build", // production bundle with Rolldown
"preview": "vite preview" // serve the built dist/ locally to sanity-check
}
}
preview is important: it serves the real production output so you test what users get, not the dev experience {preview quan trọng: nó phục vụ output production thật để bạn test thứ người dùng nhận, không phải trải nghiệm dev}.
4. index.html is the entry {index.html là entry}
Unlike Webpack, where the entry is a JS file, in Vite index.html is the entry point and lives at the project root {Khác Webpack nơi entry là file JS, ở Vite index.html là điểm vào và nằm ở gốc dự án}:
<!doctype html>
<html>
<body>
<div id="app"></div>
<!-- a normal module script — Vite takes it from here -->
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Vite treats index.html as source: it rewrites that src="/src/main.ts" and follows the import graph from there {Vite coi index.html là source: nó viết lại src="/src/main.ts" và lần theo đồ thị import từ đó}. No HtmlWebpackPlugin needed — HTML-first is built in {Không cần HtmlWebpackPlugin — HTML-first là tích hợp sẵn}.
5. The project shape {Hình dạng dự án}
my-app/
├─ index.html ← entry (root, not in public/)
├─ public/ ← static files copied as-is (favicon, robots.txt)
├─ src/
│ ├─ main.ts ← app bootstrap
│ └─ ...
├─ vite.config.ts ← config (Part 2)
└─ package.json
Two folders trip up newcomers {Hai thư mục làm người mới vấp}: public/ is for files served verbatim at the root (/favicon.svg), while everything imported from src/ goes through Vite’s transform pipeline {public/ cho file phục vụ nguyên văn ở gốc (/favicon.svg), còn mọi thứ import từ src/ đi qua pipeline biến đổi của Vite}. We cover assets fully in Part 7 {Chúng ta bàn kỹ asset ở Phần 7}.
6. Exercises {Bài tập}
1. Why does a bundler’s dev startup get slower as your app grows, while Vite’s stays roughly constant? {Vì sao khởi động dev của bundler chậm dần khi app lớn lên, còn Vite gần như không đổi?}
Solution {Lời giải}
A bundler builds the entire dependency graph before serving anything (work ∝ app size). Vite serves source over native ESM and only transforms files the browser actually requests {Bundler build cả đồ thị phụ thuộc trước khi phục vụ (công việc ∝ kích thước app). Vite phục vụ source qua ESM native và chỉ biến đổi file trình duyệt thật sự yêu cầu}.
2. If native ESM is so fast in dev, why does Vite still bundle for production? {Nếu ESM native nhanh thế khi dev, vì sao Vite vẫn bundle cho production?}
Solution {Lời giải}
Unbundled ESM means one network request per module — fine on localhost, far too many round trips for real users. Production needs a bundled, minified, code-split output (Rolldown) {ESM không bundle nghĩa là mỗi module một request mạng — ổn trên localhost, quá nhiều round trip cho người dùng thật. Production cần output bundle, minify, tách code (Rolldown)}.
3. Where is the entry point in a Vite project, and what is the role of npm run preview? {Entry trong dự án Vite ở đâu, và vai trò của npm run preview?}
Solution {Lời giải}
index.html at the project root is the entry. preview serves the built dist/ locally so you test the real production output, not the dev server {index.html ở gốc dự án là entry. preview phục vụ dist/ đã build cục bộ để bạn test output production thật, không phải dev server}.
Stretch {Nâng cao}: in the demo, set the project to 3000 modules and note the ratio, then drop it to 50 — Vite’s time barely moves while the bundler’s collapses toward it {trong demo, đặt 3000 module và để ý tỉ lệ, rồi hạ xuống 50 — thời gian Vite gần như không đổi trong khi bundler tiến lại gần}.
Key takeaways {Điểm chính}
- In dev, Vite serves source over native ESM — no bundling, instant startup {Khi dev, Vite phục vụ source qua ESM native — không bundle, khởi động tức thì}.
- In production, Vite bundles with Rolldown (Vite 8) for an optimized result {Khi production, Vite bundle bằng Rolldown (Vite 8) cho kết quả tối ưu}.
- Dev startup stays nearly flat as the app grows {Khởi động dev gần như phẳng khi app lớn lên}.
index.htmlis the entry, at the project root {index.htmllà entry, ở gốc dự án}.dev/build/previeware the three commands you’ll use daily {dev/build/previewlà ba lệnh bạn dùng hằng ngày}.
Next up {Tiếp theo}
Part 2 — vite.config.ts anatomy: the config object top to bottom — root, base, plugins, resolve.alias, server, define, and how modes shape everything {Phần 2 — Giải phẫu vite.config.ts: object config từ trên xuống — root, base, plugins, resolve.alias, server, define, và cách mode định hình mọi thứ}.