Svelte 5 双模态运行机制:Legacy 与 Runes 模式的区别、源码判定逻辑与渐进式迁移指南
本文基于 Svelte 官方文档中的 Legacy 概述页,结合当前仓库编译器源码,解释 Svelte 5 中「legacy mode(遗留模式)」与「runes mode(符文模式)」的双轨运行机制:为什么 Svelte 3/4 的旧语法在 Svelte 5 中依然可用、编译器是如何判定一个组件处于哪种模式的、哪些旧 API 已被弃用但仍受支持,以及如何进行渐进式迁移。读完之后,你将能够准确判断项目中每个组件所处的响应式模式,并据此制定分阶段迁移策略。
为什么 Svelte 5 需要一份独立的 Legacy 文档
Svelte 5 对核心 API 做了一次显著升级,引入了三大新特性:
- runes(符文):如
$state、$derived、$effect、$props等,见 what-are-runes; - snippets(代码段):以
{@snippet}语法取代了原来的 slot 传值方式,见 snippet 语法; - event attributes(事件属性):用
onclick={handler}直接作为属性,取代了on:click指令。
作为结果,Svelte 3/4 中的一批特性被标记为弃用(deprecated)——它们目前仍然可用(除非文档另有说明),但未来会被移除。官方文档明确建议:不要一次性重写,而是增量迁移(incrementally migrate)现有代码,具体路径参见 v5 迁移指南。
Legacy 文档系列的受众非常清晰,正是两类人:
- 仍然在使用 Svelte 3/4 的开发者;
- 已经升级到 Svelte 5、但部分组件尚未迁移的开发者。
由于 Svelte 3/4 语法在 Svelte 5 中仍然工作,官方文档因此刻意区分了 legacy mode 与 runes mode 两个概念。这是理解 Svelte 5 兼容机制的钥匙:组件一旦进入 runes mode(通过使用符文,或显式设置 runes: true 编译选项),legacy mode 的特性便不再可用。
判定逻辑:编译器如何决定一个组件处于哪种模式
官方文档说「使用符文,或显式设置 runes: true 编译选项」即可进入 runes mode。这个判定并非简单的开关,而是有一套优先级链,可以从编译器源码中完整还原。
1. runes 选项的两种来源
runes 是一个可被覆盖的编译选项,默认值为 undefined(即自动检测),在 validate-options.js 中定义:
runes: parametric(() => /** @type {boolean | undefined} */ (undefined)),
它有两个来源,且在 compile() 入口 中被合并:
const combined_options = {
...validated, // 全局编译选项
...parsed_options, // <svelte:options> 中的组件级选项
css: 'css' in parsed_options ? () => parsed_options.css ?? 'external' : validated.css,
runes: 'runes' in parsed_options ? () => parsed_options.runes : validated.runes
};
即:组件内 <svelte:options runes={true|false}> 的声明优先级高于全局选项。<svelte:options> 标签的解析逻辑在 1-parse/read/options.js:
case 'runes': {
component_options.runes = get_boolean_value(attribute);
break;
}
这意味着你可以在单个组件内显式锁定模式——即使项目级配置设置了 runes: true,也能对个别遗留组件声明 runes={false} 强制保持 legacy 行为(源码中的注释「if they explicitly disabled runes, use the legacy behavior」印证了这一用途)。
2. 自动检测:未显式声明时的判定规则
当 runes 选项未显式给出时,2-analyze/index.js 执行自动检测:
const runes =
runes_option ??
(has_await || instance.has_await || Array.from(module.scope.references.keys()).some(is_rune));
从源码结构看,自动进入 runes mode 的触发条件是三者之一:
- 组件存在顶层
await(has_await); - 实例脚本中存在
await(instance.has_await,典型如await onMount(...)); - 模块作用域中引用了任意 rune 名称(
$state、$derived等)。
这与文档中「use runes 即进入 runes mode」的表述一致,并且解释了为什么顶层 await 只能在 runes 模式组件中使用——编译器会直接把这类组件划入 runes 阵营。
3. 边缘情况:maybe_runes 回退机制
组件级判定还有一个容易被忽略的分支。分析结果中记录了 maybe_runes 标志(2-analyze/index.js):
// if we are not in runes mode but we have no reserved references ($$props, $$restProps)
// and no `export let` we might be in a wannabe runes component that is using runes in an external
// module...we need to fallback to the runic behavior
maybe_runes: !runes && runes_option !== false && ...
可以推断:一个看似 legacy 的组件,如果既没有 export let 也没有 $$props/$$restProps 引用,它可能是在外部模块中使用 rune 的「准 runes 组件」(例如把 $state 封装在工具模块里)。对此类组件,编译器会回退到 runic 行为而不是套用 legacy 的响应式规则。这是双模态机制在「混合工程」中的关键容错设计。
4. 模式切换带来的编译差异
同一份源码在两种模式下并不只是语法可用性不同,编译器的默认行为也不同。从 analysis 对象构造 中可以看到两处直接由 runes 布尔值驱动的分支:
immutable: runes || options.immutable,
// ...
accessors: is_custom_element || (runes ? false : !!options.accessors) || ...
- immutable:runes 模式下默认按不可变语义处理数据,而 legacy 模式只有在显式传入
immutable: true时才如此; - accessors:
accessors编译选项只影响 legacy 模式,runes 模式下直接为false(<svelte:options>里出现accessors/immutable时还会触发告警,见 2-analyze/index.js 中的analysis.runes检查)。
这些细节说明:迁移不只是「换个写法」,还会改变编译器对属性访问器和数据可变性的默认假设。
弃用但仍在工作的 Legacy 特性清单
Legacy 文档目录(documentation/docs/99-legacy)逐条列出了在 Svelte 5 中被弃用、但当前仍受支持的 Svelte 3/4 特性。这是迁移时最需要对照的清单:
| 弃用特性 | 文档位置 | Svelte 5 中的替代方案 |
|---|---|---|
响应式 let/var 顶层声明 |
01-legacy-let.md | $state rune |
基于赋值(count += 1)的响应式赋值 |
02-legacy-reactive-assignments.md | 对 $state 的引用追踪 |
export let 接收 props |
03-legacy-export-let.md | $props() |
$$props / $$restProps |
04-legacy-$$props-and-$$restProps.md | $props() 解构与 rest |
on: 事件指令 |
10-legacy-on.md | 事件属性(onclick={...}) |
| slots 插槽 | 20-legacy-slots.md | snippets |
$$slots |
21-legacy-$$slots.md | snippets |
<svelte:fragment> |
22-legacy-svelte-fragment.md | snippet |
<svelte:component> |
30-legacy-svelte-component.md | @render 标签 |
<svelte:self> |
31-legacy-svelte-self.md | 组件内直接引用自身 |
旧版组件 API($$ 内部、旧版 $set 等) |
40-legacy-component-api.md | 新组件 API |
以响应式声明为例,01-legacy-let.md 展示了两种模式的语义差异:legacy 模式下,组件顶层变量自动被视为响应式,但响应式基于赋值触发,array.push() 这类方法调用本身不触发更新,需要一次「自赋值」(numbers = numbers)来通知编译器;runes 模式则用 $state 显式声明,配合代理实现,属性级别的深层变更也能被追踪。
双 AST 与 Legacy 运行时:兼容层的实现位置
兼容旧语法不止体现在模式判定上,编译器内部还维护着完整的 legacy 兼容层,迁移文档读者可以据此理解「Svelte 5 为什么还能编译 Svelte 3/4 代码」。
双 AST 输出。compiler/legacy.js 的注释直白地写着「Transform our nice modern AST into the monstrosity emitted by Svelte 4」——它负责把现代 AST 转换回 Svelte 4 风格的 legacy AST,供仍依赖旧 AST 结构的第三方工具(如 linter、preprocessor)使用。配套的 parse 选项在 compiler/index.js 的 JSDoc 中给出了明确的废弃时间线:
modern选项在 Svelte 5 中默认false(即默认返回 legacy AST);- Svelte 6 中
modern将默认变为true; - Svelte 7 中该选项将被移除。
Legacy 运行时入口。仓库中保留了独立的 legacy 运行时模块 legacy-client.js 与 legacy-server.js,供旧版组件 API 使用;测试套件 tests/runtime-legacy 下约 2880 个测试文件持续回归验证 legacy 特性的行为。这说明「弃用但受支持」是有测试保障的承诺,而非口头声明。
实践建议:如何组织渐进式迁移
综合原文档与源码证据,一个混合 Svelte 3/4 与 Svelte 5 语法的代码库可以按以下方式组织:
- 按组件粒度迁移。每个组件的模式是独立判定的(显式选项 > 自动检测),因此可以在同一个项目中长期共存 legacy 组件与 runes 组件,逐文件替换,而不需要「一次性切换开关」。
- 新组件优先 runes。新写组件直接使用
$state/$props/snippet 语法即可自动进入 runes mode;若项目通过<svelte:options runes={false}>或全局选项显式关闭了 runes,注意新组件会意外落回 legacy 模式,迁移时应检查这一显式声明是否仍然必要。 - 警惕跨模式引用的边界。一个 runes 组件调用 legacy 组件时,props 传递与事件机制走的是 legacy 组件的旧 API;
maybe_runes机制处理了「外部模块使用 rune」的灰区,但不建议刻意制造这种模糊结构。 - 对照弃用清单逐项替换。按上文表格中的对应关系处理
on:、slot、export let、$$props等写法,每项的具体替换示例见对应 legacy 文档页。 - 完整的迁移步骤(包括
svelte migrate迁移工具的用法)请参见 v5 迁移指南。
另外,如果只关心 Svelte 3/4 语法本身而不涉及 Svelte 5,官方另行提供了对应版本的文档站点(v4 文档),其中保留了完整的旧版 API 参考;而本仓库的 Legacy 文档系列则是面向「仍在旧版本」与「正在过渡」两类读者的桥梁。
小结
Svelte 5 的兼容机制核心是双模态:legacy mode 保留 Svelte 3/4 的全部语法与语义(基于赋值/export let/slot/on: 指令),runes mode 提供全新的显式响应式 API;模式判定遵循「组件级 <svelte:options> > 全局编译选项 > 内容自动检测(rune 引用或顶层 await)」的优先级链,且模式不同会连带改变 immutable、accessors 等默认编译行为。Legacy 特性当前仍受支持并有测试保障,但随 AST modern 选项在 Svelte 6/7 的默认值变化可以看出明确的收敛方向——按照 v5 迁移指南 增量迁移,是让项目平稳跨越这一代际的正确路径。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00