Vite · Part 2 — vite.config.ts Anatomy
The Vite config top to bottom: defineConfig, plugins, root and base, resolve.alias, the dev server (port, open, proxy), define, css, and build options — plus config as a function of command and mode. With a live config builder.
Vite’s config is famously small — most projects need a dozen lines {Config của Vite nổi tiếng nhỏ — đa số dự án cần chục dòng}. But knowing what each field does is the difference between copy-pasting and understanding {Nhưng hiểu mỗi field làm gì là khác biệt giữa copy-paste và thông hiểu}. Let’s go through the whole object {Cùng đi qua cả object}.
Toggle options below to build a real vite.config.ts field by field {Bật/tắt tùy chọn bên dưới để xây vite.config.ts thật từng field}:
1. defineConfig and why {defineConfig và vì sao}
import { defineConfig } from "vite";
export default defineConfig({
// ...options
});
defineConfig does nothing at runtime — it’s purely for TypeScript IntelliSense and type-checking {defineConfig không làm gì lúc runtime — nó thuần cho IntelliSense và kiểm tra kiểu TypeScript}. You could export default {}, but you’d lose autocomplete on every field {Bạn có thể export default {}, nhưng mất autocomplete trên mọi field}. Always use it {Luôn dùng nó}.
2. plugins — the main extension point {plugins — điểm mở rộng chính}
Almost everything beyond plain JS/TS comes from a plugin {Hầu hết mọi thứ ngoài JS/TS thuần đều đến từ plugin}:
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
});
Framework support (React Fast Refresh, Vue SFCs, Svelte) is a plugin; so are legacy-browser support, PWA, and most integrations {Hỗ trợ framework (React Fast Refresh, Vue SFC, Svelte) là plugin; hỗ trợ trình duyệt cũ, PWA, và đa số tích hợp cũng vậy}. Order can matter — we cover the plugin API and enforce in Part 9 {Thứ tự có thể quan trọng — ta bàn API plugin và enforce ở Phần 9}.
3. root and base {root và base}
Two often-confused fields {Hai field hay nhầm}:
root— the project directory containingindex.html(defaults tocwd) {thư mục dự án chứaindex.html(mặc địnhcwd)}.base— the public path your app is served under {đường dẫn công khai app được phục vụ dưới đó}.
export default defineConfig({
base: "/app/", // app lives at https://example.com/app/
});
If you deploy to a sub-path (GitHub Pages project sites, a /app mount), set base or every asset URL will 404 {Nếu deploy vào sub-path (GitHub Pages project, mount /app), đặt base nếu không mọi URL asset sẽ 404}. This is the single most common deploy bug {Đây là lỗi deploy phổ biến nhất}.
4. resolve.alias {resolve.alias}
Stop writing ../../../components {Thôi viết ../../../components}:
import { fileURLToPath, URL } from "node:url";
export default defineConfig({
resolve: {
alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) },
},
});
Now import Button from "@/components/Button" works from anywhere {Giờ import Button from "@/components/Button" chạy từ bất cứ đâu}. As in Webpack, if you use TypeScript you must mirror this in tsconfig.json paths so the type-checker agrees {Như Webpack, nếu dùng TypeScript bạn phải phản chiếu trong paths của tsconfig.json để type-checker đồng ý}.
5. The dev server {server dev}
export default defineConfig({
server: {
port: 3000,
open: true, // open the browser on start
proxy: {
// forward /api to your backend, sidestepping CORS in dev
"/api": { target: "http://localhost:8080", changeOrigin: true },
},
},
});
server.proxy is the everyday hero: it lets your frontend call /api/... in dev and forwards to a separate backend, so you never fight CORS locally {server.proxy là người hùng hằng ngày: cho frontend gọi /api/... khi dev và chuyển tới backend riêng, nên bạn không bao giờ vật lộn CORS cục bộ}.
6. define, css, and build {define, css, và build}
export default defineConfig({
// compile-time constant replacement (string-replace, must be JSON-serializable)
define: { __APP_VERSION__: JSON.stringify("1.0.0") },
css: {
modules: { localsConvention: "camelCase" }, // .my-class → styles.myClass
},
build: {
outDir: "dist",
sourcemap: true,
rollupOptions: {
output: { manualChunks: { vendor: ["react", "react-dom"] } },
},
},
});
definedoes a literal text replacement at build time — values must be JSON-stringified {thay thế văn bản nguyên văn lúc build — giá trị phải JSON-stringify}.cssconfigures CSS Modules, preprocessors, PostCSS (Part 6) {cấu hình CSS Modules, preprocessor, PostCSS (Phần 6)}.buildcontrols the Rolldown production output (Part 10) {điều khiển output production của Rolldown (Phần 10)}.
7. Config as a function {Config dưới dạng hàm}
You often need different settings per command (dev vs build) or mode {Bạn thường cần thiết lập khác theo lệnh (dev vs build) hoặc mode}. Export a function instead of an object {Xuất một hàm thay vì object}:
export default defineConfig(({ command, mode }) => {
const isProd = command === "build";
return {
define: { __DEV__: !isProd },
build: { sourcemap: isProd ? "hidden" : true },
};
});
command is "serve" (dev) or "build"; mode is "development", "production", or a custom mode (Part 8) {command là "serve" (dev) hoặc "build"; mode là "development", "production", hoặc mode tùy chỉnh (Phần 8)}.
8. Exercises {Bài tập}
1. Your app deploys to https://acme.dev/dashboard/ but all JS/CSS/image URLs 404 in production while working in dev. What’s the fix? {App deploy tới https://acme.dev/dashboard/ nhưng mọi URL JS/CSS/ảnh 404 ở production trong khi dev vẫn chạy. Sửa gì?}
Solution {Lời giải}
Set base: "/dashboard/" — assets are otherwise resolved from the server root {Đặt base: "/dashboard/" — nếu không asset bị phân giải từ gốc server}.
2. In dev your frontend on :3000 calls /api/users but gets CORS errors hitting a backend on :8080. How do you fix it without touching the backend? {Khi dev frontend :3000 gọi /api/users nhưng lỗi CORS tới backend :8080. Sửa sao mà không động backend?}
Solution {Lời giải}
server.proxy: { "/api": { target: "http://localhost:8080", changeOrigin: true } } — Vite proxies same-origin requests to the backend {Vite proxy request cùng origin tới backend}.
3. You need build.sourcemap on but only for production builds, and a __DEV__ flag. How do you structure the config? {Bạn cần build.sourcemap chỉ cho production, và cờ __DEV__. Cấu trúc config sao?}
Solution {Lời giải}
Export defineConfig(({ command, mode }) => ({ ... })) and branch on command === "build" {Xuất hàm và rẽ nhánh theo command === "build"}.
Stretch {Nâng cao}: in the builder, enable the React plugin, the @ alias, a proxy, and a manual vendor chunk, then read how defineConfig nests server and build separately {trong builder, bật plugin React, alias @, proxy, và vendor chunk thủ công, rồi đọc cách defineConfig lồng server và build riêng}.
Key takeaways {Điểm chính}
- Wrap config in
defineConfigfor full TypeScript support {Bọc config trongdefineConfigđể có hỗ trợ TypeScript đầy đủ}. pluginsis the main extension point — frameworks included {pluginslà điểm mở rộng chính — gồm cả framework}.basematters whenever you deploy to a sub-path {basequan trọng khi deploy vào sub-path}.resolve.aliaskills../../imports (mirror intsconfig.paths) {resolve.aliasdiệt import../../(phản chiếu trongtsconfig.paths)}.server.proxysidesteps CORS in dev; config as a function lets you branch on command/mode {server.proxyné CORS khi dev; config dạng hàm cho rẽ nhánh theo command/mode}.
Next up {Tiếp theo}
Part 3 — The dev server & native ESM: how Vite serves source on demand, rewrites bare imports, and transforms each file as the browser asks for it {Phần 3 — Dev server & ESM native: cách Vite phục vụ source theo yêu cầu, viết lại bare import, và biến đổi từng file khi trình duyệt hỏi}.