jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Vite · Part 3 — The Dev Server & Native ESM

How the dev server actually works: serving source over native ESM, rewriting bare and relative imports into fetchable URLs, transforming each file on request, and why navigation only fetches the new modules. With an on-demand request demo.

Ở Phần 1 ta nói trình duyệt tự ghép khi dev. Nhưng ESM native có luật trình duyệt áp đặt — và dev server của Vite tồn tại để bắc cầu giữa source của bạnthứ trình duyệt thật sự chạy được.

Bấm qua một lần tải route và xem các request hiện ra từng cái:


1. Luật của trình duyệt

ESM native chỉ chấp nhận import là URL hợp lệ — tuyệt đối (/src/x.js) hoặc tương đối (./x.js). Nhưng code của bạn đầy bare import như import React from "react". Trình duyệt không biết "react" ở đâu.

Vite chặn mọi request module và viết lại import trước khi gửi file:

// you wrote
import { useState } from "react";
import Home from "./components/Home";

// Vite serves (rewritten)
import { useState } from "/node_modules/.vite/deps/react.js";
import Home from "/src/components/Home.tsx";

Bare specifier thành URL thật (trỏ tới dep đã pre-bundle — Phần 4), và import tương đối được phân giải đuôi.


2. Biến đổi theo yêu cầu

Khi trình duyệt yêu cầu /src/App.tsx, Vite:

  1. Đọc file.
  2. Biến đổi bằng Oxc (TS→JS, JSX→JS) — nhanh, viết bằng Rust.
  3. Viết lại import của nó.
  4. Gửi ESM hợp lệ trình duyệt thực thi được.

Quan trọng: việc này xảy ra từng file, theo yêu cầu. File không route nào chạm tới không bao giờ được đọc hay biến đổi.

Vite không kiểm tra kiểu khi biến đổi — Oxc chỉ bóc kiểu đi. Chạy tsc --noEmit (hoặc vue-tsc) riêng trong CI để an toàn kiểu.


3. Thác nước, và vì sao ổn

ESM native nghĩa là import lồng tạo thác nước request: main.tsx import App.tsx, import Home.tsx…. Trên mạng thật sẽ chậm, nhưng khi dev mọi thứ cục bộ và HTTP/2 ghép kênh, nên gần như tức thì.

Để tránh thác nước sâu cho dependency, Vite pre-bundle chúng thành file đơn (Phần 4) — đó là vì sao react là một request, không phải hàng trăm.


4. Điều hướng chỉ tải cái mới

Đây là phần thưởng. Khi điều hướng tới /dashboard, trình duyệt chỉ yêu cầu module route đó thêm vàoDashboard.tsx, Chart.tsx. Module đã tải được cache; không gì bị build lại. So với bundler chạy lại khi đồ thị đổi.

Trong demo, tải home rồi điều hướng — để ý số request chỉ tăng đúng hai file mới.


5. 304 và cache module

Vite gửi header cache mạnh:

  • File source304 Not Modified khi không đổi (revalidate).
  • Dep đã pre-bundle → cache cứng (đã hash).

Nên reload trang sẽ re-request module source nhưng trả về tức thì dạng 304, còn dep không bị re-request.


6. Bài tập

1. Vì sao trình duyệt không chạy trực tiếp import React from "react", và Vite làm gì?

Lời giải

ESM native cần URL hợp lệ, không phải bare specifier. Vite viết lại "react" thành URL thật trỏ tới dependency đã pre-bundle.

2. Đồng đội nói “Vite không bắt lỗi TypeScript khi dev.” Đúng không, vì sao?

Lời giải

Đúng — Vite/Oxc chỉ bóc kiểu cho nhanh; không kiểm tra kiểu. Chạy tsc --noEmit riêng (editor + CI).

3. Vì sao điều hướng tới route mới khi dev cảm giác tức thì so với bundler?

Lời giải

Vite chỉ biến đổi và phục vụ module mới route đó import; phần còn lại đã cache và không gì bị bundle lại.

Nâng cao:trong demo, tải home, rồi dashboard, rồi reset và tải lại home — để ý dep (react) đến từ pre-bundle, không phải cây source của bạn.


Điểm chính

  • Trình duyệt chỉ chạy import URL hợp lệ; Vite viết lại bare và relative import.
  • Mỗi file được biến đổi theo yêu cầu bằng Oxc — không kiểm tra kiểu.
  • Thác nước import khi dev ổn vì cục bộ + ghép kênh, và dep được pre-bundle.
  • Điều hướng chỉ tải module mới — không gì bị bundle lại.
  • Cache mạnh (304 + dep immutable) giữ reload nhanh.

Tiếp theo

Phần 4 — Pre-bundle dependency: vì sao node_modules được pre-bundle vào .vite/deps, cách Vite chuyển CommonJS sang ESM, và khi nào cache mất hiệu lực.