jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Vite · Part 10 — Production Builds with Rolldown

What vite build produces: the dist/ structure, automatic code splitting, manualChunks and advancedChunks for vendor splitting, asset hashing, source maps, target and minification. With a build output explorer.

6 MIN READ

Everything so far has been about the dev server {Mọi thứ tới giờ là về dev server}. Now the other half: vite build {Giờ tới nửa kia: vite build}. In Vite 8 this hands off to Rolldown, a Rust bundler that’s 10–30× faster than the old Rollup path while keeping the same plugin API {Trong Vite 8 nó giao cho Rolldown, một bundler Rust nhanh hơn 10–30× đường Rollup cũ trong khi giữ cùng API plugin}.

Toggle build options and watch the dist/ output and chunking change {Bật/tắt tùy chọn build và xem output dist/ và chunk đổi}:


1. What vite build produces {vite build tạo gì}

vite build
dist/
├─ index.html                  ← entry, with hashed asset links injected
├─ assets/
│  ├─ index-a1b2c3.js          ← your app entry chunk
│  ├─ index-c3d4e5.css         ← extracted, hashed CSS
│  ├─ vendor-e5f6a7.js         ← dependencies (if split)
│  ├─ Dashboard-789012.js      ← a lazy route chunk
│  └─ logo-1234.svg            ← hashed asset

Every JS/CSS/asset filename carries a content hash for safe long-term caching — change a file, its hash changes, the cache busts {Mỗi tên file JS/CSS/asset mang một hash nội dung để cache dài hạn an toàn — đổi file, hash đổi, cache bị bust}. index.html is rewritten to point at the hashed files {index.html được viết lại để trỏ tới file đã hash}. This is the long-term caching strategy from the Webpack series, automatic here {Đây là chiến lược cache dài hạn từ series Webpack, tự động ở đây}.


2. Automatic code splitting {Tách code tự động}

Vite splits code at dynamic import boundaries with zero config {Vite tách code tại ranh giới dynamic import không cần config}:

const Dashboard = lazy(() => import("./routes/Dashboard"));

That import() becomes its own chunk (Dashboard-789012.js), fetched only when the route loads {import() đó thành chunk riêng (Dashboard-789012.js), chỉ tải khi route được mở}. Its CSS is split out too (Part 6) {CSS của nó cũng được tách (Phần 6)}. In the explorer, toggling the lazy route shows it move out of the initial load {Trong explorer, bật route lazy cho thấy nó rời khỏi tải ban đầu}.


3. Vendor splitting {Tách vendor}

By default your dependencies may bundle into the main chunk {Mặc định dependency của bạn có thể bundle vào chunk chính}. Splitting them into a separate vendor chunk means the browser caches them across deploys (they change less often than your code) {Tách chúng vào chunk vendor riêng nghĩa là trình duyệt cache chúng qua các lần deploy (chúng đổi ít hơn code của bạn)}.

The classic, still-supported way — manualChunks {Cách cổ điển, vẫn hỗ trợ — manualChunks}:

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: { vendor: ["react", "react-dom"] },
      },
    },
  },
});

Vite 8 / Rolldown also adds advancedChunks for more flexible, rule-based splitting (by size, by module path patterns) — webpack-style chunk groups {Vite 8 / Rolldown cũng thêm advancedChunks cho tách linh hoạt hơn, theo luật (theo kích thước, theo pattern đường dẫn module) — nhóm chunk kiểu webpack}:

export default defineConfig({
  build: {
    rolldownOptions: {
      output: {
        advancedChunks: {
          groups: [{ name: "vendor", test: /node_modules/ }],
        },
      },
    },
  },
});

Don’t over-split — too many tiny chunks adds request overhead. Split vendor and lazy routes; let Vite handle the rest {Đừng tách quá — quá nhiều chunk nhỏ thêm overhead request. Tách vendor và route lazy; để Vite lo phần còn lại}.


4. Target & minification {Target & minify}

export default defineConfig({
  build: {
    target: "baseline-widely-available", // modern default; or e.g. "es2020"
    minify: "esbuild", // default, fast; or "terser" for max compression
  },
});
  • target — the JS feature level of the output. Lower targets = more transpilation + bigger output {mức tính năng JS của output. Target thấp = transpile nhiều + output to hơn}.
  • minify — Vite minifies by default in production; terser squeezes a bit more at the cost of build time {Vite minify mặc định khi production; terser ép thêm chút đổi lấy thời gian build}.

For old browsers, add @vitejs/plugin-legacy to emit fallback bundles {Cho trình duyệt cũ, thêm @vitejs/plugin-legacy để phát bundle fallback}.


5. Source maps & analysis {Source map & phân tích}

export default defineConfig({
  build: {
    sourcemap: true, // "hidden" to emit maps without the //# comment
  },
});

Source maps make production stack traces readable (upload them to your error tracker, don’t serve them publicly if the code is sensitive) {Source map làm stack trace production đọc được (upload lên error tracker, đừng phục vụ công khai nếu code nhạy cảm)}. To see what’s in your bundle, use rollup-plugin-visualizer for a treemap — the same measure-first discipline from the Webpack series {Để xem gì trong bundle, dùng rollup-plugin-visualizer cho treemap — cùng kỷ luật đo-trước từ series Webpack}.


6. Always preview before shipping {Luôn preview trước khi ship}

vite build && vite preview

preview serves the real dist/ so you catch issues that only appear in the production bundle: a wrong base, a missing env var, a dynamic import that didn’t resolve {preview phục vụ dist/ thật để bạn bắt vấn đề chỉ xuất hiện trong bundle production: base sai, env var thiếu, dynamic import không phân giải}. Never assume “works in dev” means “works built” {Đừng giả định “chạy khi dev” nghĩa là “chạy khi build”}.


7. Exercises {Bài tập}

1. Why does every file in dist/assets have a hash in its name, and what would break if they didn’t? {Vì sao mọi file trong dist/assets có hash trong tên, và sẽ hỏng gì nếu không?}

Solution {Lời giải}

Content hashes enable safe long-term caching: when content changes the URL changes, busting stale caches. Without them, users could get stale JS/CSS after a deploy {Hash nội dung cho cache dài hạn an toàn: khi nội dung đổi URL đổi, bust cache cũ. Không có chúng, user có thể nhận JS/CSS cũ sau deploy}.

2. Your react/react-dom rebuild and re-download on every deploy even though they didn’t change. How do you fix the caching? {react/react-dom build lại và tải lại mỗi lần deploy dù không đổi. Sửa caching sao?}

Solution {Lời giải}

Split them into a vendor chunk (manualChunks or advancedChunks) so they get a stable hash and stay cached across deploys {Tách vào chunk vendor để có hash ổn định và giữ cache qua các deploy}.

3. A teammate ships straight from vite build output without checking it. What command should be in the deploy routine and why? {Đồng đội ship thẳng từ output vite build mà không kiểm tra. Lệnh nào nên có trong quy trình deploy và vì sao?}

Solution {Lời giải}

vite preview — it serves the real production bundle locally so issues like a wrong base or missing env var surface before users hit them {vite preview — nó phục vụ bundle production thật cục bộ để vấn đề như base sai hoặc env var thiếu lộ ra trước khi user gặp}.

Stretch {Nâng cao}: in the explorer, enable vendor split + lazy route and note how the initial-load KB drops as code moves into separately-cached and on-demand chunks {trong explorer, bật tách vendor + route lazy và để ý KB tải-ban-đầu giảm khi code chuyển vào chunk cache-riêng và theo-yêu-cầu}.


Key takeaways {Điểm chính}

  • vite build produces a hashed, optimized dist/ via Rolldown {vite build tạo dist/ đã hash, tối ưu qua Rolldown}.
  • Code splits automatically at dynamic-import boundaries; CSS splits too {Tách code tự động tại ranh giới dynamic-import; CSS cũng tách}.
  • Split a vendor chunk (manualChunks / advancedChunks) for cross-deploy caching {Tách chunk vendor để cache qua deploy}.
  • Tune target and minify; add plugin-legacy for old browsers {Chỉnh targetminify; thêm plugin-legacy cho trình duyệt cũ}.
  • Always vite preview the real output before shipping {Luôn vite preview output thật trước khi ship}.

Next up {Tiếp theo}

Part 11 — Library mode + SSR & the Environment API: building a publishable library with Vite, generating types, and an overview of SSR and the new multi-environment build model {Phần 11 — Library mode + SSR & Environment API: xây thư viện publish được với Vite, sinh type, và tổng quan SSR cùng mô hình build đa-môi-trường mới}.