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.
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;tersersqueezes 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 buildproduces a hashed, optimizeddist/via Rolldown {vite buildtạodist/đã 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
targetandminify; addplugin-legacyfor old browsers {Chỉnhtargetvàminify; thêmplugin-legacycho trình duyệt cũ}. - Always
vite previewthe real output before shipping {Luônvite previewoutput 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}.