full-stack-skills/svelte-skills

svelte-runtime

Svelte 5 运行时技能。当用户需要使用 Stores、Context、生命周期钩子(onMount/onDestroy/tick)、命令式组件 API(mount/unmount/render/hydrate)、Hydratable Data、Best Practices 或 Vitest 测试时使用。

First seen Jul 13, 2026

Installation

$ npx skills add full-stack-skills/svelte-skills --skill svelte-runtime

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from full-stack-skills/svelte-skills · top by installs.

npx skills add full-stack-skills/svelte-skills

Browse all from full-stack-skills/svelte-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 3
License LICENSE
Default branch main
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 14,392 B
  • docs SUMMARY.md 257 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 36 installs

SKILL.md

Svelte Runtime Reference (Svelte 5)

本技能覆盖 Svelte 5 的运行时 API,包括跨组件状态(Stores/Context)、生命周期、命令式 API、SSR 和测试。

When to use this skill

当用户需要跨组件共享状态、处理组件生命周期、使用 mount/render 等命令式 API、或编写 Vitest 组件测试时使用本技能。

Critical: Stores (svelte/store)

Store 是一种跨组件共享响应式状态的老方式(Svelte 5 推荐用 .svelte.js 中的 $state 对象替代)。

writable

import { writable } from 'svelte/store';
const count = writable(0);

count.subscribe(v => console.log(v)); // 订阅
count.set(1);                         // 设置值
count.update(n => n + 1);            // 更新

readable(不可从外部设置)

import { readable } from 'svelte/store';
const time = readable(new Date(), (set) => {
  const interval = setInterval(() => set(new Date()), 1000);
  return () => clearInterval(interval);
});

derived

import { derived } from 'svelte/store';
const doubled = derived(count, $c => $c * 2);
const combined = derived([a, b], ([$a, $b]) => $a + $b);

readonly

import { readonly, writable } from 'svelte/store';
const readOnlyStore = readonly(writableStore);

在组件中使用

<script>
  import { count } from './store.js';
  console.log($count); // $ 前缀订阅
  $count = 2;          // 自动调用 .set()
</script>

Store 必须在组件顶层声明;不要在变量前加 $ 前缀(那是 $count,不是 count)。

Svelte 5 推荐替代方案

// state.svelte.js
export const userState = $state({ name: '', count: 0 });
// 其他组件直接 import

Critical: Context (svelte)

跨组件层级传递数据,无需 props 层层透传(prop-drilling):

setContext / getContext

// Parent.svelte
<script>
  import { setContext } from 'svelte';
  setContext('key', 'hello');
</script>

// Child.svelte
<script>
  import { getContext } from 'svelte';
  const msg = getContext('key');
</script>

createContext(推荐,类型安全)

// context.ts
import { createContext } from 'svelte';
export const [getUserContext, setUserContext] = createContext<User>();
// Parent.svelte
<script> setUserContext({ name: 'Ada' }); </script>

// Child.svelte
<script> const user = getUserContext(); </script>

响应式 Context

$state 对象存入 Context:

setContext('counter', counterState);
// ❌ 不要重新赋值整个对象
// counterState = { count: 0 }
// ✅ 直接修改属性
counterState.count = 0;

Critical: Lifecycle Hooks

Mental model: Svelte 5 的生命周期只有两个阶段 —— 创建销毁。中间的"状态更新"由 effect 处理,因为响应式的最小单位是 effect,不是组件。所以没有 beforeUpdate/afterUpdate 钩子。

onMount

组件挂载到 DOM 后执行(SSR 时不运行):

<script>
  import { onMount } from 'svelte';
  onMount(() => {
    console.log('mounted');
    return () => console.log('unmounted'); // 返回清理函数
  });
</script>

返回的函数在组件卸载时调用;如果传入 async 函数则清理函数不会被调用(async 函数总是返回 Promise)。需要异步任务 + 清理时,用 IIFE 包裹,外部箭头函数同步返回清理函数。

onDestroy

组件卸载前执行(SSR 时也运行):

<script>
  import { onDestroy } from 'svelte';
  onDestroy(() => cleanup());
</script>

tick

等待 DOM 更新完成后再继续:

<script>
  import { tick } from 'svelte';
  $effect.pre(() => {
    // DOM 更新前
    tick().then(() => {
      // DOM 已更新完成
    });
  });
</script>

生命周期与 mount/unmount/hydrate 的配合

阶段 mount() hydrate() render() (SSR) unmount()
同步:组件 script、state、模板
同步:插入 DOM / 输出字符串 ✓ 替换 target ✓ 复用 SSR DOM ✓ 返回 head+body
微任务:$effect / onMount 触发
同步:清理 effect + onDestroy ✓(render 完成后)
异步:过渡 (outro) outro:true 时等待

mount() 返回时 effect 和 onMount 都还没跑;要立刻让它们跑就 flushSync()hydrate() 同理。
tick()flushSync() 在服务端是 no-op(没有响应式调度)。

Critical: Imperative Component API

mount(客户端挂载)

import { mount } from 'svelte';
import App from './App.svelte';

const app = mount(App, {
  target: document.querySelector('#app'),
  props: { name: 'world' }
});

unmount(卸载)

import { mount, unmount } from 'svelte';
unmount(app, { outro: true }); // 播放过渡后卸载

render(服务端渲染)

import { render } from 'svelte/server';
const { head, body } = await render(App, { props: { data } });

hydrate(客户端水合)

复用 SSR HTML 并激活为交互式:

import { hydrate } from 'svelte';
const app = hydrate(App, { target: document.querySelector('#app') });

Critical: Hydratable Data

SSR 时序列化数据,客户端水合时复用(避免重复请求):

<script>
  import { hydratable } from 'svelte';
  const user = await hydratable('user', () => getUser());
</script>
<h1>{user.name}</h1>

CSP 支持

hydratable() 会在 render() 返回的 head 中嵌入一段 inline <script>。CSP 默认会拦截它,需要用 nonce 或 hash 放行:

// 动态 SSR:每请求一个 nonce
const nonce = crypto.randomUUID();
const { head, body } = await render(App, {
  csp: { nonce }
});
// 响应头同步加上:
// Content-Security-Policy: script-src 'self' 'nonce-${nonce}'
// 静态 SSG:使用 hash
const { head, body, hashes } = await render(App, {
  csp: { hash: true }
});
// Content-Security-Policy: script-src ${hashes.script.map(h => `'${h}'`).join(' ')}

重要

  • 没用 hydratable()render() 不输出 inline <script>,CSP 严格模式不会拦截任何东西。
  • inline 事件处理器(<button onclick={...}>)不是 <script> 标签,不需要 nonce。
  • 推荐 nonce 模式,hash 模式将来会和 streaming SSR 冲突。
  • 详见 [references/csp-reference.md](./references/csp-reference.md)。

Critical: Best Practices

$state

// ✅ 只对需要响应式的变量用 $state
let count = $state(0);

// ✅ 大数组/不可变数据用 $state.raw
let apiData = $state.raw([]);
apiData = await fetchData(); // 重新赋值触发更新

$derived

// ✅ 派生值用 $derived
let doubled = $derived(count * 2);

// ❌ 不要用 $effect 同步派生
let square;
$effect(() => { square = num * num; }); // ❌
let square = $derived(num * num);         // ✅

$effect

// ✅ 仅用于副作用
$effect(() => { myChart.resize(); });

// ❌ 不要同步状态
$effect(() => { left = total - spent; }); // ❌

Props

// ✅ 依赖 props 的值用 $derived
let { type } = $props();
let color = $derived(type === 'danger' ? 'red' : 'green');

事件

<!-- ✅ 现代事件属性 -->
<button onclick={handleClick}>click</button>

<!-- ❌ 旧式 on:click -->
<button on:click={handleClick}>click</button>

<!-- ✅ 监听 window 事件 -->
<svelte:window onkeydown={handleKey} />

Snippets

<!-- ✅ 推荐:使用 snippet -->
{#snippet footer()}
  <p>footer</p>
{/snippet}

Each blocks

<!-- ✅ 始终使用 key -->
{#each items as item (item.id)}
  <li>{item.name}</li>
{/each}

Context

// ✅ 用 createContext 提供类型安全
import { createContext } from 'svelte';
const [getCtx, setCtx] = createContext<MyCtx>();

Avoid Legacy Features

// ✅ Runes mode 全是新项目的默认
let count = $state(0);     // ✅
$: count++;                  // ❌ legacy
export let name;            // ❌
let { name } = $props();   // ✅
on:click={fn}              // ❌
onclick={fn}               // ✅
<slot />                   // ❌
{@render children()}        // ✅
<svelte:component this={x} /> // ❌
<svelte:element this={x} />   // ✅

Critical: Testing (Vitest)

安装配置

npm install -D vitest
// vite.config.js
export default defineConfig({
  resolve: process.env.VITEST
    ? { conditions: ['browser'] }
    : undefined
});

单元测试

// multiplier.svelte.js
export function multiplier(initial, k) {
  let count = $state(initial);
  return {
    get value() { return count * k; },
    set: (c) => { count = c; }
  };
}

// test
import { multiplier } from './multiplier.svelte.js';
test('multiplier', () => {
  let double = multiplier(0, 2);
  expect(double.value).toEqual(0);
  double.set(5);
  expect(double.value).toEqual(10);
});

组件测试

import { mount, unmount } from 'svelte';
import { expect, test } from 'vitest';

test('MyComponent', () => {
  const comp = mount(MyComponent, { target: document.body, props: { name: 'Ada' } });
  expect(document.body.innerHTML).toContain('Ada');
  unmount(comp);
});

flushSync

强制同步执行所有待处理效果(测试中常用):

import { flushSync } from 'svelte';
flushSync();

Quick Fixes

问题 解决方案
状态在组件间不同步 用 Context(跨层级)或 .svelte.js(全局)
onMount 清理不执行 确保传入同步函数(不是 async),需要异步时用 IIFE 包裹
SSR 后组件不交互 hydrate 而非 mount
测试中状态不更新 flushSync() 强制同步执行
Context 断开链接 不要重新赋值整个 Context 对象
mount 后 effect 没运行 mount() 同步返回,但 effect 在下一个 microtask 才跑;需要立刻跑就 flushSync()
CSP 阻止 hydratable 脚本 csp: { nonce }(动态)或 csp: { hash: true }(静态),并在响应头里同步声明
异步 onMount 拿不到清理 外部箭头同步 return () => controller.abort(),async 逻辑放 IIFE 里面

Gotchas

  1. Store $ 前缀是订阅语法$store 在模板和 script 中都是订阅,非 $ 前缀的变量是普通值
  2. Context 链接断开 — 重新赋值整个对象会断开响应式链接,改用 .count = 0 而非 obj = {count:0}
  3. mount 不运行 $effect — effects 在组件挂载后由 microtask 触发
  4. 测试中 $effect.root — 涉及 effects 的测试需要 $effect.root() 包裹
  5. render 只在 SSR 环境可用 — 客户端用 mount
  6. async onMount 的清理会被忽略 — async 函数永远返回 Promise,Svelte 拿不到清理函数;用 IIFE 模式
  7. hydrate() 不会同步运行 effect — 跟 mount() 一样,hydration 后要用 flushSync() 让 effect 跑起来
  8. CSP nonce 是一次性的 — 每请求一个新 nonce;hash 模式与未来的 streaming SSR 冲突
  9. hydration 随机值会不匹配Math.random() / Date.now() 在 SSR 和 CSR 不同;用 hydratable() 包裹

FAQ

Q: 什么时候用 Store vs Context vs .svelte.js? A: Store:跨全应用共享的简单值;Context:组件树局部共享;.svelte.js $state:推荐的新方式,可替代 Store。

Q: tickawait Promise.resolve() 有什么区别? A: tick() 保证在 DOM 更新后解析,Promise.resolve() 不等待 Svelte 的更新周期。

Q: tick()flushSync() 怎么选? A: await tick() 适合异步用户代码;flushSync() 适合测试和需要立即同步 DOM 的外部库同步场景。

Q: 如何测试带有 $effect 的代码? A: 用 flushSync() 强制执行,使用 $effect.root() 包裹测试代码。

Q: SSR 后 hydration 报错? A: 确保 SSR 和 CSR 的 HTML 结构一致;用 hydrate 而非 mount

Q: 什么时候用 onMount vs $effect A: onMount 是"挂载后跑一次"的副作用;$effect 是"依赖变化就重跑"的反应式副作用。

Q: hydrate 的 inline 脚本被 CSP 阻止了怎么办? A: 用 csp: { nonce } 在 render 时给脚本打上 nonce,并在响应头同步声明;静态站点用 csp: { hash: true }hashes.script

Examples

Practical examples demonstrating Svelte 5's runtime APIs.

File Description
[examples/README.md](./examples/README.md) Entry point with quick reference
[examples/mount-hydrate.md](./examples/mount-hydrate.md) 9 examples: mount, unmount, hydrate, render, flushSync, tick
[examples/ssr-patterns.md](./examples/ssr-patterns.md) SSR patterns: async render, streaming, hydration, CSP
[examples/onmount-tick.md](./examples/onmount-tick.md) 15 lifecycle examples: onMount (sync/async/cleanup), onDestroy, tick for focus/measure/scroll, $effect.pre pattern
[examples/csp-examples.md](./examples/csp-examples.md) 8 CSP examples: nonce (Express/SvelteKit/Cloudflare), hash (SSG), diagnostic tips

References

In-depth technical references for Svelte 5's runtime APIs.

File Description
[references/README.md](./references/README.md) Entry point with quick reference
[references/mount-unmount-reference.md](./references/mount-unmount-reference.md) Deep dive: mount/unmount/hydrate lifecycle, flushSync, tick
[references/ssr-reference.md](./references/ssr-reference.md) SSR deep dive: render, async SSR, CSP, hydration
[references/lifecycle-runtime-reference.md](./references/lifecycle-runtime-reference.md) Lifecycle in mount/hydrate runtime: when effects run, onMount timing, tick vs flushSync, SSR phase matrix
[references/csp-reference.md](./references/csp-reference.md) CSP nonce vs hash, runtime configuration, streaming compatibility