Add Svelte vào Astro
Add Svelte vào Astro
Section titled “Add Svelte vào Astro”Astro hỗ trợ tích hợp nhiều UI framework khác nhau, trong đó có Svelte. Bài viết này hướng dẫn bạn cách thêm Svelte vào Astro project một cách chi tiết nhất.
Tại sao dùng Svelte với Astro?
Section titled “Tại sao dùng Svelte với Astro?”Cài đặt @astrojs/svelte
Section titled “Cài đặt @astrojs/svelte”Bước 1: Install packages
Section titled “Bước 1: Install packages”npm install svelte @astrojs/svelteHoặc dùng các package manager khác:
# Yarnyarn add svelte @astrojs/svelte
# pnpmpnpm add svelte @astrojs/svelteBước 2: Cập nhật astro.config.mjs
Section titled “Bước 2: Cập nhật astro.config.mjs”import { defineConfig } from 'astro/config';import svelte from '@astrojs/svelte'; // Import Svelte integration
export default defineConfig({ integrations: [ svelte(), // Thêm vào integrations array ],});Bước 3: Tạo Svelte component
Section titled “Bước 3: Tạo Svelte component”Tạo folder src/components/ (nếu chưa có) và file component:
src/components/Counter.svelte:
<script> let count = 0;
function increment() { count += 1; }
function decrement() { count -= 1; }</script>
<div class="counter"> <button on:click={decrement}>-</button> <span>{count}</span> <button on:click={increment}>+</button></div>
<style> .counter { display: flex; gap: 1rem; align-items: center; }
button { padding: 0.5rem 1rem; font-size: 1.2rem; cursor: pointer; }
span { font-size: 1.5rem; font-weight: bold; min-width: 3rem; text-align: center; }</style>Bước 4: Sử dụng component trong Astro
Section titled “Bước 4: Sử dụng component trong Astro”Trong file .astro:
---import Counter from '../components/Counter.svelte';---
<h1>Counter Example</h1>
<!-- Dùng client:load để component hoạt động --><Counter client:load />Trong file .mdx:
import Counter from '../components/Counter.svelte';
# My Article
Some text here...
<Counter client:load />Client Directives quan trọng
Section titled “Client Directives quan trọng”| Directive | Khi nào dùng | Mô tả |
|---|---|---|
client:load | Component cần JS ngay | Hydrate ngay khi page load |
client:idle | Component không gấp | Hydrate khi browser idle |
client:visible | Component dưới fold | Hydrate khi scroll vào viewport |
client:media | Responsive | Hydrate ở certain media query |
client:only | No SSR | Chỉ chạy ở client, không server-render |
TypeScript với Svelte
Section titled “TypeScript với Svelte”Cấu hình tsconfig.json
Section titled “Cấu hình tsconfig.json”{ "extends": "astro/tsconfigs/strict", "compilerOptions": { "strict": true, "types": ["astro/client", "svelte"] }}Sử dụng TypeScript trong component
Section titled “Sử dụng TypeScript trong component”<script lang="ts"> interface Props { title: string; count?: number; }
export let title: string; export let count: number = 0;
function increment(): void { count += 1; }</script>
<h2>{title}</h2><p>Count: {count}</p><button on:click={increment}>+</button>Styling trong Svelte Components
Section titled “Styling trong Svelte Components”Scoped CSS tự động
Section titled “Scoped CSS tự động”<style> /* CSS này chỉ apply cho component này */ .my-class { color: blue; }</style>Global CSS
Section titled “Global CSS”<style> :global(body) { /* Apply cho toàn bộ page */ background: #f0f0f0; }</style>CSS Variables
Section titled “CSS Variables”<style> .card { background: var(--theme-bg, white); color: var(--theme-text, black); }</style>Lỗi thường gặp
Section titled “Lỗi thường gặp”Lỗi 1: “Cannot find module”
Section titled “Lỗi 1: “Cannot find module””Error: Cannot find module './Counter.svelte'Fix: Thêm extension vào import
import Counter from './Counter.svelte';Lỗi 2: Hydration mismatch
Section titled “Lỗi 2: Hydration mismatch”Hydration failed because the initial UI does not matchFix: Dùng client:only hoặc kiểm tra typeof window
<script> import { onMount } from 'svelte';
let data;
onMount(() => { // Code dùng window ở đây data = window.localStorage.getItem('key'); });</script>Lỗi 3: CSS không apply
Section titled “Lỗi 3: CSS không apply”Fix: Đảm bảo không bị override bởi global styles
<style> /* Tăng specificity */ div.my-component { color: red; }
/* Hoặc dùng :global nếu cần */ :global(.external-class) { color: blue; }</style>Example: Todo List Component
Section titled “Example: Todo List Component”<script lang="ts"> interface Todo { id: number; text: string; done: boolean; }
let todos: Todo[] = []; let newTodo = '';
function addTodo() { if (newTodo.trim()) { todos = [...todos, { id: Date.now(), text: newTodo, done: false }]; newTodo = ''; } }
function toggleTodo(id: number) { todos = todos.map(t => t.id === id ? { ...t, done: !t.done } : t ); }
function removeTodo(id: number) { todos = todos.filter(t => t.id !== id); }</script>
<div class="todo-list"> <div class="add-todo"> <input bind:value={newTodo} placeholder="Add new todo..." on:keypress={(e) => e.key === 'Enter' && addTodo()} /> <button on:click={addTodo}>Add</button> </div>
<ul> {#each todos as todo (todo.id)} <li class:done={todo.done}> <input type="checkbox" checked={todo.done} on:change={() => toggleTodo(todo.id)} /> <span>{todo.text}</span> <button on:click={() => removeTodo(todo.id)}>×</button> </li> {/each} </ul></div>
<style> .todo-list { max-width: 400px; }
.add-todo { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
input[type="text"] { flex: 1; padding: 0.5rem; }
ul { list-style: none; padding: 0; }
li { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; border-bottom: 1px solid #eee; }
li.done span { text-decoration: line-through; opacity: 0.6; }
button { background: #ff4444; color: white; border: none; padding: 0.25rem 0.5rem; cursor: pointer; }</style>