· svelte / svelte-5 / runes

Svelte 5 với runes — 6 tháng thực chiến: nhận xét thẳng thắn

Runes là API reactivity tốt nhất trong hệ sinh thái JS hiện tại, dành cho các team chấp nhận pool nhỏ hơn React. Hai bẫy cụ thể cần biết trước khi migrate.

Bởi

1.998 từ · 10 phút đọc

Hệ thống runes là API reactivity tốt nhất trong hệ sinh thái JavaScript hiện tại. Sáu tháng trước, nhận định này sẽ là khiêu khích. Sau sáu tháng Svelte 5 ổn định, nó vẫn đúng. Các lưu ý là có thật nhưng cụ thể: codebase nặng TypeScript sẽ gặp vấn đề verbose khi khai báo kiểu cho props, và $state không thể vượt ranh giới server/client của SvelteKit như các team mong đợi. Biết trước hai bẫy này trước khi migrate và bạn sẽ ổn.

Dành cho ai

Các developer đang chạy Svelte 4 trên production và cân nhắc có nên migrate không, hoặc các frontend engineer đang đánh giá framework cho dự án mới khi độ rộng hệ sinh thái của React không phải yêu cầu bắt buộc.

Runes thực sự là gì

Bạn đã đọc bài thông báo. Bạn không cần tutorial ở đây. Điều quan trọng là cảm giác thay đổi tư duy ra sao khi bạn sống trong đó.

Reactivity của Svelte 4 là compiler magic: khai báo biến với let, mutate nó, và compiler sẽ theo dõi. Nó hoạt động cho đến khi không hoạt động nữa. Các object lồng nhau, cross-file state, và reactive class instance đều có các trường hợp biên khó đoán. Các giải pháp là một mớ escape hatch — $: cho derived state, writable store cho bất kỳ thứ gì cần chia sẻ, import get/set để đọc store bên ngoài component.

Runes thay thế mớ đó bằng bảy primitive rõ ràng. Năm cái bạn sẽ dùng hàng ngày:

RuneThay thế cái gì
$statelet cho reactive var có thể thay đổi
$derived$: tính giá trị
$effect$: với side effect
$propsexport let cho component props
$bindableexport let ràng buộc hai chiều

Cùng một reactive counter trong Svelte 4 và Svelte 5:

<!-- Svelte 4 -->
<script>
  let count = 0;
  $: doubled = count * 2;
</script>
<button on:click={() => count++}>{count} → {doubled}</button>
<!-- Svelte 5 -->
<script>
  let count = $state(0);
  const doubled = $derived(count * 2);
</script>
<button onclick={() => count++}>{count} → {doubled}</button>

Phiên bản Svelte 5 nhiều dòng hơn. Đó chính là điểm mấu chốt. Reactivity là tường minh. Bạn có thể đọc component Svelte 5 và biết chính xác cái gì là reactive mà không cần chạy compiler trong đầu. Khi mở component Svelte 4 nhận từ người khác, thường bạn không thể nói điều đó.

$state trên array và object sử dụng proxy, nên các mutation sâu được theo dõi: items.push(x) là reactive. Trong Svelte 4, bạn cần items = [...items, x] để trigger update. Một cải tiến duy nhất này loại bỏ toàn bộ một nhóm bug khó phát hiện.

$effect là primitive nguy hiểm nhất. Nó chạy lại bất cứ khi nào dependencies thay đổi. Nếu bạn mutate state bên trong nó, bạn sẽ có vòng lặp vô tận:

<script>
  let items = $state([]);
  let filtered = $state([]);

  // Vòng lặp vô tận — $effect thấy filtered thay đổi, chạy lại, lại thay đổi filtered
  $effect(() => {
    filtered = items.filter(i => i.active);
  });
</script>

Cách sửa: dùng $derived cho bất cứ thứ gì là pure transformation của state. Dành $effect cho side effect thực sự — DOM manipulation, analytics call, tích hợp thư viện bên thứ ba. Nếu có thể thay thế $effect bằng $derived, hãy làm vậy.

Quá trình migrate: đánh giá thực tế

Công cụ tự động — npx sv migrate svelte-5 — xử lý khoảng 80% công việc. Với codebase Svelte 4 có 30.000 dòng, script mất 40 phút cho các thay đổi tự động và khoảng hai ngày fixup thủ công. Tỷ lệ này thuận lợi hơn hầu hết các lần migrate framework.

Những gì công cụ xử lý được: let$state, export let$props, $: đơn giản → $derived hoặc $effect, đổi tên event directive (on:clickonclick).

Những gì cần xử lý thủ công:

createEventDispatcher — được thay thế bằng callback props. Script đánh dấu các chỗ dùng nhưng không thể tự convert vì không biết API contract của component bạn:

<!-- Svelte 4 -->
<script>
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();
</script>
<button on:click={() => dispatch('save', data)}>Save</button>

<!-- Svelte 5 -->
<script>
  let { onsave } = $props();
</script>
<button onclick={() => onsave(data)}>Save</button>

beforeUpdate/afterUpdate — các lifecycle hook này không còn nữa. Bạn tái tạo hành vi của chúng bằng $effect và cleanup function, thường là cải thiện hơn, nhưng cần suy nghĩ cho từng trường hợp.

Các chuỗi $: phức tạp — migration script convert các trường hợp rõ ràng. Các chuỗi reactive nhiều bước có điều kiện cần người thực sự phán đoán xem intent là derived state, side effect, hay guard.

Bẫy TypeScript — khai báo kiểu cho props trong Svelte 5 verbose hơn export let. Đây là thứ sẽ làm bạn chậm lại nếu codebase nặng TypeScript:

<!-- Svelte 4: ngắn gọn -->
<script lang="ts">
  export let title: string;
  export let count = 0;
</script>

<!-- Svelte 5: cần interface rõ ràng -->
<script lang="ts">
  interface Props {
    title: string;
    count?: number;
  }
  let { title, count = 0 }: Props = $props();
</script>

Với component có nhiều props, điều này cộng dồn. Component library với 40 component sẽ tốn cả ngày thêm interface mà migration script không làm giúp được. Kết quả dễ đọc hơn, nhưng migrate không miễn phí.

Bẫy load function$state không thể được trả về từ SvelteKit load function. Reactive state không vượt qua ranh giới server/client:

// +page.ts
export function load() {
  // Không hoạt động — $state chỉ dành cho component
  return { items: $state([]) }; // ❌

  // Trả về data thuần; gán vào $state bên trong component
  return { items: [] }; // ✅
}

Nếu bạn cần reactivity từ load output, bạn vẫn cần store. Tài liệu Svelte 5 không làm rõ điều này. Store không bị deprecated cho pattern đó — runes không phải thay thế trực tiếp trong mọi ngữ cảnh.

Hiệu năng: benchmark có nghĩa gì (và không có nghĩa gì)

Svelte 5 dẫn đầu krausest/js-framework-benchmark trên các DOM-operation test và có runtime nhỏ hơn đáng kể so với React hay Vue — sự khác biệt đo lường được, không phải lời quảng cáo.

Con số đó có nghĩa gì trong thực tế: với ứng dụng điển hình từ 50–200 component, sự khác biệt hiệu năng DOM là không cảm nhận được. Khoảng cách benchmark chuyển thành vài millisecond trong frame budget 200ms. Bạn sẽ không thấy nó trên phần cứng thực với workload thực.

Những chỗ con số thực sự quan trọng:

  • Giao diện danh sách lớn hiển thị hàng nghìn hàng đồng thời — khoảng cách là thật ở quy mô đó
  • Triển khai nhạy cảm với bundle size — runtime nhỏ hơn của Svelte 5 quan trọng trên kết nối mobile chậm, nơi mỗi KB trong điều kiện 3G ảnh hưởng đến điểm PageSpeed
  • Widget nhúng — component tải trên trang bên thứ ba được hưởng lợi từ runtime footprint nhỏ hơn

Với dashboard nội bộ hay sản phẩm SaaS có user xác thực trên băng thông rộng, sự khác biệt hiệu năng giữa Svelte 5 và React không phải điểm nghẽn của bạn. Sự quen thuộc với framework, tính sẵn có của component library, và pipeline tuyển dụng mới là điều đáng quan tâm hơn.

Kiểm tra hệ sinh thái: đã đủ ổn chưa?

Trạng thái hệ sinh thái cuối 2025:

  • shadcn-svelte — v1.0 ra mắt tháng 6 năm 2024, hiện đang ở v1.2+; ổn định cho production với coverage component tốt
  • Ark UI — thư viện accessible primitive, đã có hỗ trợ Svelte 5
  • Storybook 9 — component Svelte 5 hoạt động; runes và snippet được hỗ trợ trong định dạng CSF story
  • VS Code extension — nhận biết runes
  • eslint-plugin-svelte — cập nhật, hỗ trợ đầy đủ runes

Câu chuyện về IDE không còn là vấn đề nữa. Đó là câu hỏi sáu tháng trước. Bây giờ thì không còn.

The New York Times dùng Svelte trên production — tín hiệu đáng tin cậy rằng các tổ chức kỹ thuật lớn đã thấy nó đủ ổn định cho sản phẩm nghiêm túc.

Khoảng cách hệ sinh thái so với React là có thật và không thu hẹp nhanh. 2.7 triệu lượt tải npm mỗi tuần so với React ở mức ~190 triệu có nghĩa là lựa chọn component library hẹp hơn và bề mặt “google lỗi này” nhỏ hơn. Nếu bạn cần virtualized table sẵn sàng cho production với mười hai tùy chọn cấu hình, bạn sẽ tìm thấy nó trong hệ sinh thái React trước Svelte. Để so sánh trực tiếp hai framework, xem React vs Svelte.

State of JS 2024: Svelte đứng đầu bảng xếp hạng về tổng quan ý kiến tích cực trong số các framework frontend. Đây là chỉ số hài lòng, không phải chỉ số sử dụng, nhưng chúng nói lên rằng các team chọn Svelte có xu hướng gắn bó.

Hosting: Tích hợp SvelteKit của Vercel là mục tiêu triển khai tham chiếu hiện tại — đối tác chính thức, zero-config deploy, hỗ trợ edge function. Netlify hoạt động tương đương. Cả hai không có lợi thế hệ sinh thái nào ảnh hưởng đến quyết định chọn framework. Nếu Next.js cũng nằm trong danh sách cân nhắc, SvelteKit vs Next.js đi sâu vào sự khác biệt về routing và triển khai.

Khi nào Svelte 5 là lựa chọn đúng — và khi nào không

Chọn Svelte 5 nếu:

  • Bạn đang bắt đầu dự án mới và độ rộng hệ sinh thái của React không phải yêu cầu bắt buộc
  • Team của bạn nhỏ (1–5 kỹ sư) và việc làm quen với framework diễn ra nhanh chóng
  • Bundle size trên kết nối mobile chậm là mối quan tâm thực sự của sản phẩm
  • Bạn đang xây dựng thư viện hoặc embeddable widget nơi runtime footprint ảnh hưởng đến bên thứ ba

Tiếp tục dùng React nếu:

  • Tuyển dụng là mối lo ngại trước mắt — pool developer React lớn hơn đáng kể so với Svelte (State of JS 2025)
  • Bạn cần component cụ thể (enterprise data grid, video SDK, chart library) chỉ có React bindings
  • Codebase hiện tại là React và không có vấn đề cụ thể mà migrate sẽ giải quyết
  • RSC-first SSR với server data fetching phức tạp là cốt lõi sản phẩm — React Server Components và Next.js 16 có lợi thế hệ sinh thái đáng kể ở đây

Nếu bạn đang dùng Svelte 4: hãy migrate. Công cụ xử lý phần lớn. Mô hình runes tốt hơn hẳn so với reactivity ngầm của Svelte 4, và hai bẫy — verbose TypeScript props và load function store — đều có thể biết trước và vượt qua được. Càng chờ lâu, hệ sinh thái Svelte càng xa rời codebase của bạn.

Tài liệu tham khảo

  1. Svelte 5 is alive — thông báo phát hành chính thức, ngày 22 tháng 10 năm 2024
  2. Runes — RFC runes gốc và lý do ra đời
  3. Svelte 5 migration guide — tài liệu migrate chính thức
  4. What are runes? — API reference cho cả bảy primitive
  5. Svelte 5 runes in practice — ghi chú thực tế từ một lần migrate production
  6. Svelte 5 stores revisited — khi nào store vẫn là công cụ phù hợp
  7. Mixed signals with Svelte 5 — nhìn lại trung thực từ cộng đồng
  8. Svelte discussion #13277 — kinh nghiệm migrate và các trường hợp biên từ cộng đồng
  9. State of JS 2024 — dữ liệu retention và satisfaction