jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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">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}
devnative 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}
buildbundle 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.html is the entry, at the project root {index.html là entry, ở gốc dự án}.
  • dev / build / preview are the three commands you’ll use daily {dev / build / preview là 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ứ}.