jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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 {rootbase}

Two often-confused fields {Hai field hay nhầm}:

  • root — the project directory containing index.html (defaults to cwd) {thư mục dự án chứa index.html (mặc định cwd)}.
  • 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"] } },
    },
  },
});
  • define does 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}.
  • css configures CSS Modules, preprocessors, PostCSS (Part 6) {cấu hình CSS Modules, preprocessor, PostCSS (Phần 6)}.
  • build controls 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"serve" (dev) hoặc "build"; mode"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 serverbuild riêng}.


Key takeaways {Điểm chính}

  • Wrap config in defineConfig for full TypeScript support {Bọc config trong defineConfig để có hỗ trợ TypeScript đầy đủ}.
  • plugins is the main extension point — frameworks included {plugins là điểm mở rộng chính — gồm cả framework}.
  • base matters whenever you deploy to a sub-path {base quan trọng khi deploy vào sub-path}.
  • resolve.alias kills ../../ imports (mirror in tsconfig.paths) {resolve.alias diệt import ../../ (phản chiếu trong tsconfig.paths)}.
  • server.proxy sidesteps CORS in dev; config as a function lets you branch on command/mode {server.proxy né 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}.