agentleFS
full-stack-skillsfull-stack-skills/svelte-skills/skills/svelte-legacy-apis/SKILL.md

svelte-legacy-apis

Skill4 starsChanged 56 days agoApache-2.0View on GitHub

Svelte 5 Legacy API 技能。当用户需要维护或迁移 Svelte 4 代码,理解 Reactive let/$:/export let/Slots/createEventDispatcher/$$props 等已废弃但仍可用的 Legacy API 时使用。

  • Installs packages

The file

Files
SKILL.md

Svelte Legacy APIs Reference (Svelte 4 → Svelte 5)

本技能覆盖 Svelte 4 的 Legacy API,这些 API 在 Svelte 5 中仍可用(Legacy Mode),但推荐迁移到 Runes。本技能仅用于理解和迁移 Legacy 代码,新代码应使用 Runes。

When to use this skill

当用户需要维护 Svelte 4 项目、理解 $: reactive 语句、export let props、createEventDispatcher、Slots、或需要将 Legacy 代码迁移到 Svelte 5 Runes Mode 时使用本技能。

Critical: Reactive let declarations

在 Legacy Mode(未使用 Runes 的组件),顶层 let 声明自动成为响应式:

<script>
  let count = 0; // 自动响应式
</script>

<button on:click={() => count++}>
  {count}
</button>

Svelte 5 的 Runes Mode:let count = $state(0)。

数组/对象变更注意

Legacy 响应式基于赋值,数组方法(.push()、.splice())不触发更新:

<script>
  let items = [1, 2, 3];

  function addItem() {
    items.push(4);    // ❌ 不触发更新
    items = items;    // ✅ 触发
    // 或
    items = [...items, 4]; // ✅ 触发
  }
</script>

Critical: Reactive $: statements

在 Legacy Mode,$: 标签语句创建响应式语句:

<script>
  let a = 1;
  let b = 2;

  $: sum = a + b;              // 依赖 a, b
  $: console.log(`sum = ${sum}`); // 依赖 sum
</script>

拓扑排序

$: 语句按依赖拓扑排序(顺序无关):

$: z = y;    // z 依赖 y
$: y = x;    // y 依赖 x
$: x = 5;    // x 无依赖

块级语句

$: {
  total = 0;
  for (const item of items) total += item.value;
}

Critical: export let

Legacy Mode 中用 export let 声明 props:

<script>
  export let name;              // 必需 prop
  export let age = 18;          // 带默认值
  export let items = [];         // 数组默认值
</script>

Svelte 5 Runes:let { name, age = 18 } = $props()

重新导出(API)

<script>
  export function greet(name) {
    alert(`Hello ${name}!`);
  }
  export const VERSION = '1.0';
</script>

父组件通过 bind:this 调用:

<script>
  let greeter;
</script>
<Greeter bind:this={greeter} />
<button on:click={() => greeter.greet('world')}>greet</button>

prop 重命名

<script>
  export { foo as bar }; // 对外叫 bar,内部叫 foo
</script>

Critical: $$props / $$restProps

$$props(获取所有传入 props)

<script>
  let { foo, ...rest } = $$props;
  // foo 是显式 prop,rest 是其余所有 props
</script>

Svelte 5 Runes:let { foo, ...rest } = $props()

$$restProps(仅剩余 props)

<script>
  export let required;
  let { ...rest } = $$props;
</script>
<!-- required 不在 rest 中 -->

Critical: createEventDispatcher

Legacy 的事件分发机制(已被 Svelte 5 callback props 取代):

<script>
  import { createEventDispatcher } from 'svelte';

  const dispatch = createEventDispatcher();

  function handleClick() {
    dispatch('change', { value: 42 });
    dispatch('complete'); // 无数据
  }
</script>

<button on:click={handleClick}>发送</button>

使用事件

<Component on:change={(e) => console.log(e.detail)} />

类型化(TypeScript)

<script lang="ts">
  const dispatch = createEventDispatcher<{
    change: { value: number };
    complete: null;
  }>();
</script>

Svelte 5 推荐:直接用 callback props: <Component onChange={(detail) => handle(detail)} />

Critical: Slots

<slot> 是 Legacy 的内容传递机制(Svelte 5 用 Snippets 替代):

默认 slot

<!-- Component.svelte -->
<div class="card">
  <slot />
</div>

<!-- 使用 -->
<Component>
  <p>内容</p>
</Component>

命名 slot

<!-- Component.svelte -->
<slot name="footer" />
<slot name="header" />

<!-- 使用 -->
<Component>
  <p slot="header">头部</p>
  <p slot="footer">尾部</p>
</Component>

Slot props(向 slot 传数据)

<!-- Component.svelte -->
<slot value={count} onClick={handleClick} />

<!-- 使用 -->
<Component let:value let:onClick>
  <p>{value}</p>
  <button on:click={onClick}>click</button>
</Component>

注意:Svelte 5 中,default slot props 需要 let: 前缀;named slots 的 slot props 不暴露给 named slots。

$$slots(检查 slot 是否存在)

<script>
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();
  $: if (!$$slots.footer) {
    // footer slot 未被提供
  }
</script>

Critical: svelte:component / svelte:self

<svelte:component>(动态组件)

<script>
  let Current = FirstComponent;
  $: Current = showSecond ? SecondComponent : FirstComponent;
</script>

<svelte:component this={Current} />

Svelte 5 推荐:<svelte:element this={Current} />

<svelte:self>(递归组件)

<script>
  import { svelte:self } from 'svelte';
</script>

<svelte:self />

Critical: svelte:fragment

<!-- Component.svelte -->
<slot name="title" />
<div>
  <slot name="body" />
</div>

<!-- 使用 -->
<Component>
  <h1 slot="title">Title</h1>
  <svelte:fragment slot="body">
    <p>多元素</p>
    <p>不会被包裹</p>
  </svelte:fragment>
</Component>

Critical: v5 Migration Quick Reference

Legacy (Svelte 4)Modern (Svelte 5)
let count = 0(顶层响应式)let count = $state(0)
$: x = exprlet x = $derived(expr)
$: { if (cond) x = v }$effect(() => { if (cond) x = v })
export let proplet { prop } = $props()
$$propslet { ...rest } = $props()
<slot />{@render children()}
createEventDispatchercallback props
on:click={fn}onclick={fn}
<svelte:component this={x}><svelte:element this={x}>
<svelte:self>import Self from './This.svelte' + <Self>
class:cool={isCool}class={{ cool: isCool }}
let:slotProp={value}Snippets 自动传递

Gotchas

  1. Legacy $: 基于赋值 — 数组 .push() 等方法不触发更新
  2. Slot props 行为不同 — default slot 可访问 slot props,named slots 不可以
  3. createEventDispatcher 已废弃 — Svelte 5 不推荐,新代码用 callback props
  4. $$slots 是编译时注入 — 可在 script 中直接使用 $: 中引用
  5. <svelte:self> 需要导入 — import { svelte:self } from 'svelte'

Critical: Svelte 4 Migration (Svelte 3 → Svelte 4)

如果你正在维护 Svelte 3 项目并升级到 Svelte 4,下面这些破坏性变更必须逐项核对。详细对照与自动化脚本见 examples/migration-v4.md 与 references/migration-v4-reference.md,源码基线在 /tmp/svelte-llms.txt:6182。

推荐起点:npx svelte-migrate@latest svelte-4 —— 脚本会自动处理多数条目;其余需要手动介入。

最低版本要求

{
  "engines": { "node": ">=16" },
  "devDependencies": {
    "svelte": "^4.0.0",
    "@sveltejs/kit": ">=1.20.4",
    "vite-plugin-svelte": ">=2.4.1",
    "svelte-loader": ">=3.1.8",
    "rollup-plugin-svelte": ">=7.1.5",
    "typescript": ">=5"
  }
}

主要破坏性变更速查

类别Svelte 3Svelte 4
Bundler browser condition可选Rollup browser: true / Webpack conditionNames: ['browser']
CJS 输出支持移除(编译器、svelte/register、runtime)
createEventDispatcher 类型可选 payload严格:T | undefined 可选;T 必填;null 禁 detail
Action / ActionReturnparams 隐式 any必须显式 Action<HTMLElement, T>
onMount 异步清理不报错返回 Promise 的清理函数会被忽略(TS 报错)
Custom Element<svelte:options tag="x" /><svelte:options customElement="x" />(字符串或对象)
SvelteComponentTyped与 SvelteComponent 并存弃用,统一为 SvelteComponent
Transitions 默认范围任意祖先块触发仅直接父块触发,加 |global 恢复旧行为
Default slot bindingsnamed slot 也可见仅 default slot 可见
Preprocessor 顺序markup×N, script×N, style×Nmarkup, script, style ×N(每个 preprocessor 内部三段)
eslint-plugin-svelte3推荐弃用,改 eslint-plugin-svelte
inert on outroing不应用默认应用(无障碍改进)
classList.toggle兼容老浏览器需要 polyfill
CustomEvent兼容老浏览器需要 polyfill
derived falsy 值警告抛错
svelte/internal 类型暴露已移除
svelte.JSX 命名空间用改 svelteHTML / svelte/elements
库作者 peerDependencies^3^3 || ^4

自动化脚本能处理的

  • tag= → customElement=(字符串形式)
  • SvelteComponentTyped → SvelteComponent
  • Action / ActionReturn 泛型参数
  • Transitions 默认行为(添加 |global 修饰符)

需要手动处理的

  • Bundler browser condition 配置(Rollup / Webpack)
  • 严格类型错误的修复(createEventDispatcher payload)
  • onMount 异步清理改同步
  • Preprocessor name 属性 + 顺序调整
  • ESLint 插件替换 + 配置迁移
  • derived falsy 值边界条件

Custom Element 升级

<!-- v3 --><svelte:options tag="my-counter" />
<!-- v4 --><svelte:options customElement={{ tag: 'my-counter', props: { initial: { type: Number } } }} />

onMount 异步清理反模式

// ❌ v4 报错且永远不清理
onMount(async () => { const data = await fetch('/api'); setup(data); return () => teardown(); });
// ✅ 同步返回值
onMount(() => { fetch('/api').then(setup); return () => teardown(); });

Preprocessor 顺序:MDsveX 必须最前

// svelte.config.js
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { mdsvex } from 'mdsvex';

export default {
  preprocess: [
    mdsvex({ name: 'mdsvex' }),         // 先 markup 再 script
    vitePreprocess({ name: 'vite' })
  ]
};

v3:所有 markup → 所有 script → 所有 style。v4:每个 preprocessor 内部 markup → script → style,再下一个。每个 preprocessor 必须声明 name。

库作者的 peerDependencies

{
  "peerDependencies": { "svelte": "^3.0.0 || ^4.0.0" },
  "devDependencies": { "svelte": "^4.0.0" }
}

Run-it-Yourself Checklist

npm install svelte@^4                    # 1. 升级依赖
npx svelte-migrate@latest svelte-4       # 2. 跑迁移脚本
npx svelte-check                          # 3. 看 TypeScript
npm test                                   # 4. 跑测试

如大量出现 Action 泛型错误,说明 migration 脚本漏掉;手动补 <HTMLElement, YourType>。

进一步 v5 迁移在 svelte-runes / svelte-misc 中;本技能只覆盖 Svelte 4 升级。

Quick Fixes

问题解决
数组 .push() 不更新items = [...items, newItem] 或 items = items
slot 样式不生效样式隔离 scoped,不影响 slot 内容
$$restProps 类型错误确认是 export let 而非 let
EventDispatcher 类型问题升级 TypeScript 到 5+

FAQ

Q: 什么时候必须用 Legacy API? A: 维护未迁移的 Svelte 4 项目时。新项目应使用 Runes。

Q: 可以混合使用 Legacy 和 Runes 吗? A: 可以。一个组件使用 $state 即进入 Runes Mode,未使用的部分用 Legacy 语法。

Q: $: 和 $derived 的区别? A: 功能相似,但 $derived 是 Svelte 5 的 Runes API,更显式和可控。$: 在 Legacy Mode 可用。

Q: Slot 和 Snippet 哪个更好? A: Snippets 更强大灵活。Slot 适合简单内容传递;Snippet 可接收参数、可在定义处使用、类型安全更好。

Cross-References

本技能是 Svelte Legacy 维护专用,与以下技能协同:

  • Svelte 5 入口与学习路径 → hand off to svelte-awesome skill. Install: npx skills add full-stack-skills/svelte-skills --skill svelte-awesome.
  • Svelte 4 → Svelte 5 迁移对照 → hand off to svelte-misc skill(migration 章节). Install: npx skills add full-stack-skills/svelte-skills --skill svelte-misc.
  • Runes 响应式($state / $derived / $effect)→ hand off to svelte-runes skill. Install: npx skills add full-stack-skills/svelte-skills --skill svelte-runes.
  • Stores(writable / readable / derived)→ hand off to svelte-lifecycle skill. Install: npx skills add full-stack-skills/svelte-skills --skill svelte-lifecycle.
  • 模板语法(snippet / event / bind)→ hand off to svelte-template-syntax skill. Install: npx skills add full-stack-skills/svelte-skills --skill svelte-template-syntax.
  • 调试响应式不更新 / $inspect → hand off to svelte-lifecycle skill. Install: npx skills add full-stack-skills/svelte-skills --skill svelte-lifecycle.
  • 把组件编译为 Web Component → hand off to svelte-misc skill(Custom Elements 章节). Install: npx skills add full-stack-skills/svelte-skills --skill svelte-misc.
  • 初始化与迁移脚本 → hand off to svelte-cli skill(sv migrate svelte-5). Install: npx skills add full-stack-skills/svelte-skills --skill svelte-cli.

Examples

Practical examples for working with Legacy APIs:

  • examples/README.md - Examples entry point
  • examples/migration-scripts.md - Migration workflows (svelte-migrate, let→$state, $:→$derived, etc.)
  • examples/legacy-mode.md - Legacy mode usage, mixing legacy and runes
  • examples/store-comparison.md - Store patterns in Svelte 4 and Svelte 5
  • examples/migration-v4.md - Svelte 3 → Svelte 4 迁移 12 个实战示例(bundler 配置、严格类型、Custom Element、transition、slot、preprocessor 等)

References

Complete reference documentation:

  • references/README.md - References entry point
  • references/migration-reference.md - Complete migration reference with before/after code
  • references/legacy-mode-reference.md - Legacy mode behavior and limitations
  • references/store-reference.md - Store API reference (writable, readable, derived, custom stores)
  • references/migration-v4-reference.md - Svelte 3 → Svelte 4 完整对照参考(最低版本、bundler、严格类型、Custom Element、slot、preprocessor、其他 breaking change、库作者指引)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.