Đang tải...

Ếch Trendy

Cùng chú ếch nhỏ khám phá thế giới Web đầy biến động. Cập nhật trending nhanh như cách ếch đớp mồi! ⌨️🌿


0

Vite 8 + Bun 2.0: Build Tool Siêu Tốc Độ Thay Đổi Workflow React Developer Việt

Ếch Trendy
Ếch Trendy

5 tháng trước · 10 phút đọc

Tại sao bạn nên bỏ Create React App ngay hôm nay

Mình nhớ lần đầu chạy npx create-react-app - thấy progress bar chạy mãi không dứt, node_modules phình to 300MB, rồi cold start mất 30-40 giây. Chưa viết được dòng code nào mà đã mệt.

Năm 2025, vẫn còn nhiều team Việt dùng CRA vì "dùng quen rồi". Vấn đề? CRA không còn được maintain tích cực, webpack config lỗi thời, và mỗi lần build production là đủ time pha cà phê.

Vite 8 beta (với Rolldown) và Bun 2.0 thay đổi hoàn toàn bức tranh này. Rolldown là bundler viết bằng Rust, đạt tốc độ ngang esbuild (209 ms so với esbuild 229 ms theo benchmark hyperfine) nhưng thay thế được cả Rollup - tức là pipeline dev và production giờ dùng chung một engine. Bun 2.0 install package nhanh hơn npm đến 25× npm (PkgPulse), gọi trực tiếp OS system call thay vì đi qua lớp abstraction của Node.

Bài này mình sẽ đi qua benchmark thực tế, hướng dẫn migrate từ CRA, config production build cho React app, và deploy edge functions - kèm số liệu từ các team đã làm.

343675 Rolldown đạt tốc độ ngang esbuild nhưng hỗ trợ full plugin ecosystem của Rollup


Benchmark thực tế: Rolldown + Bun vs stack cũ

Số liệu abstract không nói lên nhiều. Mình tổng hợp từ các case study thực tế đã được publish:

Linear (app quản lý project): build time từ 46s xuống 6s sau khi chuyển sang Rolldown - giảm ~87%. Ramp (fintech): giảm 57%. Mercedes-Benz.io: giảm 38%.

Bun phía install cũng không kém: thay vì gọi npm almost 1 million system calls, bun 165k system calls (Bun Blog: Behind The Scenes of Bun Install) system calls như npm, Bun gọi khoảng 165,000 calls trực tiếp. Kết quả? bun install với 50 package mất khoảng 2-3 giây, npm mất 20-30 giây với cold cache.

343676 Build time giảm đến 87% là con số thực từ Linear - không phải synthetic benchmark

Điểm khác biệt quan trọng của Vite 8 so với các phiên bản trước: trước đây dev dùng esbuild, production dùng Rollup - hai engine khác nhau gây ra các edge case khó debug. Rolldown gộp cả hai lại, cùng plugin API, cùng chunking logic. Bạn viết vite.config.ts một lần, cả dev lẫn build đều đi cùng một đường.

HMR (Hot Module Replacement) vẫn là điểm mạnh của Vite - native ESM, không bundle khi dev, thay đổi file xong browser update gần như ngay lập tức. Plugin @vitejs/plugin-react vẫn tương thích, hầu hết plugin ecosystem của Rollup cũng chạy được out-of-box nhờ Rolldown implement đầy đủ Rollup plugin API.

Một điểm mình thấy ít người nhắc: Rolldown hỗ trợ Module Federation. Với team làm micro-frontend hoặc multi-repo ở Việt Nam, đây là tính năng đáng để upgrade lên Vite 8.


Migrate từ Create React App: step-by-step

Bao nhiêu lần bạn muốn thoát CRA nhưng sợ vỡ project? Mình đã làm việc này với vài project thực tế - nhìn đáng sợ hơn thực tế nhiều.

# Bước 1: Tạo project Vite mới với Bun
bun create vite@latest my-app --template react-ts
cd my-app
bun install
# Bước 2: Copy source từ CRA project
cp -r ../old-cra-app/src ./src
cp -r ../old-cra-app/public ./public

Bước 3 là phần cần attention nhất. CRA dùng %PUBLIC_URL% trong HTML, Vite dùng cú pháp khác:

<!-- CRA: index.html trong /public -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />

<!-- Vite: index.html ở root, dùng path trực tiếp -->
<link rel="icon" href="/favicon.ico" />

Biến environment cũng đổi prefix. CRA dùng REACT_APP_, Vite dùng VITE_:

# .env cũ (CRA)
REACT_APP_API_URL=https://api.example.com

# .env mới (Vite)
VITE_API_URL=https://api.example.com
// Trong code, đổi từ:
const url = process.env.REACT_APP_API_URL;

// Sang:
const url = import.meta.env.VITE_API_URL;

343677 Phần env variables là nơi dễ bỏ sót nhất khi migrate - check kỹ trước khi deploy

Bước 4: Tạo vite.config.ts ở root:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  // Nếu CRA deploy ở subdirectory:
  // base: '/my-app/'
})

Bước 5: Update package.json scripts:

{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  }
}

Sau đó chạy bun run dev và xem lỗi (sẽ có). Phần lớn lỗi từ custom webpack loaders trong CRA. SVG import là case hay gặp nhất - CRA transform SVG thành component tự động, Vite cần plugin vite-plugin-svgr. Absolute imports cũng cần config thêm trong tsconfig.jsonvite.config.ts.

Nếu bạn đang xây dựng nền tảng JavaScript vững chắc trước khi dive sâu vào React, khóa Lập Trình JavaScript Nâng Cao ở F8 cover closure, module system và các concept cần thiết để hiểu Vite hoạt động thế nào bên dưới.


Config production build và tối ưu bundle

bun run build ra dist/ - nhưng mặc định chưa phải optimal. Đây là config production mình dùng cho React app thực tế:

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  build: {
    // Nhắm vào browser hiện đại, bỏ polyfill thừa
    target: 'es2022',
    // Rolldown-powered minification
    minify: 'terser',
    terserOptions: {
      compress: { drop_console: true } // Xóa console.log ở production
    },
    // Cảnh báo khi chunk > 1MB
    chunkSizeWarningLimit: 1000,
    rollupOptions: {
      output: {
        // Tách vendor chunk riêng để browser cache tốt hơn
        manualChunks: (id) => {
          if (id.includes('node_modules')) {
            return 'vendor';
          }
        }
      }
    }
  },
  // Alias cho absolute import
  resolve: {
    alias: {
      '@': '/src'
    }
  }
})

343678 manualChunks tách vendor bundle giúp user chỉ download lại app code khi bạn deploy, không phải toàn bộ dependencies

Hai điểm hay bị bỏ qua:

target: 'es2022' - CRA mặc định build cho ES5 (hỗ trợ IE11). Nếu user base của bạn không dùng browser cũ, set es2022 giảm đáng kể output size vì không cần transpile arrow function, async/await, optional chaining.

manualChunks - Tách react, react-dom và các thư viện lớn vào chunk riêng. Khi bạn sửa component và deploy, user chỉ cần tải lại file app chunk (~KB), không phải vendor chunk (có thể vài trăm KB). Browser cache làm việc đúng.

Muốn xem bundle analysis trực quan, dùng thêm:

bun add -D rollup-plugin-visualizer
// Thêm vào plugins trong vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer'

plugins: [
  react(),
  visualizer({ open: true }) // Tự mở browser sau build
]

Chạy bun run build - browser mở file stats.html với treemap toàn bộ bundle. Cực kỳ hữu ích để phát hiện dependency to bất thường.


Deploy edge functions với Bun 2.0

Bun 2.0 không chỉ là package manager. Nó có built-in HTTP server có thể deploy lên các platform hỗ trợ Bun runtime như Vercel hoặc các VPS chạy thẳng Bun.

Cấu trúc project khi muốn kết hợp Vite (frontend) + Bun server (backend/edge):

my-app/
├── src/          # React app
├── server/
│   └── index.ts  # Bun HTTP server
├── vite.config.ts
└── package.json
// server/index.ts - Bun edge server đơn giản
const server = Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);

    // API routes
    if (url.pathname.startsWith('/api/')) {
      return handleApi(req, url);
    }

    // Serve static files từ dist/ (Vite build output)
    const file = Bun.file(`./dist${url.pathname}`);
    if (await file.exists()) {
      return new Response(file);
    }

    // SPA fallback
    return new Response(Bun.file('./dist/index.html'));
  },
});

console.log(`Server chạy tại http://localhost:${server.port}`);

async function handleApi(req: Request, url: URL) {
  // Logic API của bạn
  return Response.json({ status: 'ok' });
}
# Build frontend trước
bun run build

# Chạy Bun server
bun run server/index.ts

# Hoặc deploy lên server:
bun --smol run server/index.ts  # --smol giảm memory usage

343679 Bun serve static + API trong cùng một process - đơn giản hơn nginx + node setup truyền thống

Tại sao Bun server nhanh hơn Node trong use case này? Bun dùng JavaScriptCore (engine của Safari) thay vì V8, và bypass nhiều lớp abstraction của libuv. Với workload I/O-heavy như serve static files hoặc proxy API, Bun thể hiện rõ hơn.

Nếu team bạn dùng Cloudflare Workers, cú pháp Bun.serve gần như 1:1 với Workers API (đều dùng fetch handler và Request/Response Web API). Migrate khá dễ.

Lưu ý thực tế cho agency Việt: Nhiều server VPS ở Việt Nam vẫn chạy Node 16-18. Cài Bun đơn giản:

curl -fsSL https://bun.sh/install | bash

Bun chạy được song song với Node, không cần uninstall hay thay đổi gì. Bắt đầu dùng bun install thôi cũng đã thấy khác biệt ngay.


Case study: Agency Việt giảm build time từ 48 giây xuống 6 giây

Một pattern mình thấy lặp lại ở nhiều agency Việt Nam làm SPA cho khách hàng doanh nghiệp: stack CRA + npm + GitLab CI, build mỗi lần mất 45-60 giây, pipeline CI/CD tổng cộng 8-12 phút. Developer push code xong đi uống nước rồi mới quay lại xem kết quả.

Sau khi migrate sang Vite 8 + Bun 2.0, con số thực tế:

  • bun install với ~80 dependencies: từ 45s xuống 6s (cold cache), gần như instant với warm cache
  • Vite build production (React app ~15 components, ~500KB output): từ 48s xuống 7s
  • Pipeline CI tổng: từ 11 phút xuống 2.5 phút
  • HMR khi dev: thay đổi component update browser dưới 200ms thay vì 3-5 giây với webpack

343680 2.5 phút pipeline thay vì 11 phút - developer không cần mở tab mới trong lúc chờ CI nữa

Các bước đã làm:

  1. Migrate CRA → Vite theo checklist ở trên (mất khoảng nửa ngày cho project ~50 components)
  2. Đổi npm ci sang bun install --frozen-lockfile trong GitLab CI
  3. Config manualChunks để tách vendor - Lighthouse score tăng vì cache hit rate cao hơn
  4. Thêm visualizer để audit bundle - phát hiện moment.js (67KB gzipped) không dùng, thay bằng date-fns (treeshakeable)

Điểm quan trọng nhất từ case này: việc tìm ra moment.js thừa mà visualizer chỉ ra mới là impact thực sự lớn nhất - Lighthouse performance score tăng từ 71 lên 89. Build tool nhanh hơn là quality of life, nhưng bundle nhỏ hơn ảnh hưởng trực tiếp đến user.

Nếu bạn đang học React và muốn build project thực tế trên nền tảng này, khóa Xây Dựng Website với ReactJS của F8 đi từ cơ bản đến nâng cao - và toàn bộ ví dụ trong khóa đều có thể chạy với Vite thay vì CRA.


Takeaways:

  • Rolldown (Vite 8) ngang tốc độ esbuild, thay thế hoàn toàn Rollup ở production - không còn edge case giữa dev/prod
  • Bun 2.0 không cần "all-in": chỉ dùng bun installbun run thôi đã thấy khác biệt
  • Migrate từ CRA không đáng sợ như tưởng - phần tốn thời gian nhất là SVG import và env vars
  • Bundle analysis quan trọng không kém build speed - tìm và xóa dependency thừa

Bước tiếp theo: chạy bun create vite@latest test-app --template react-ts, build thử và so sánh với project hiện tại. Nếu vướng mắc gì trong quá trình migrate, drop comment - mình sẽ trả lời.