Svelte 4 迁移实战指南:从 Svelte 3 到 4 的全部破坏性变更与升级要点
本文基于 Svelte 仓库官方文档 Svelte 4 migration guide 完整整理并深入解读从 Svelte 3 升级到 Svelte 4 时的所有破坏性变更:最低版本要求、打包器 browser 条件配置、CJS 输出移除、更严格的 TypeScript 类型、Custom Elements 重构、SvelteComponentTyped 弃用、过渡动效默认本地化、插槽绑定作用域收紧、预处理器执行顺序变化、ESLint 包更替以及一系列运行时行为调整。读完后,你可以对照仓库中对应的源码实现(如迁移工具、预处理器、选项校验)逐项核对自己的项目配置,确保平滑升级。
迁移工具:svelte-migrate
官方推荐的迁移方式是运行迁移脚本,它会自动处理其中一部分变更(例如 Action 泛型补全、customElement 选项替换、SvelteComponentTyped 替换、为过渡加 |global 等):
npx svelte-migrate@latest svelte-4
对于库(library)作者,文档给出了一条重要建议:先考虑是只支持 Svelte 4,还是同时兼容 Svelte 3。由于大部分破坏性变更影响面有限,同时支持两者往往是容易做到的;同时记得更新自己包 peerDependencies 中的版本范围。
补充(源码佐证):当前 Svelte 仓库中,迁移能力已内置到编译器中,入口位于 migrate 模块。该模块基于 MagicString 对源码做保映射改写,无法自动迁移的场景会抛出
MigrationError,并在原文件头部插入@migration-task注释提示人工处理(见 migrate 实现的错误处理逻辑)。配套的大量测试用例位于 tests/migrate/samples,例如impossible-migrate-前缀的目录覆盖了一组“无法自动迁移”的边界场景。
最低版本要求
Svelte 4 对运行环境和配套工具链提出了明确的最低版本要求:
- Node 16 或更高:更早的版本不再受支持。
- SvelteKit 用户:升级到 1.20.4 或更新版本。
- 不使用 SvelteKit 的 Vite 用户:升级
vite-plugin-svelte到 2.4.1 或更新版本。 - webpack 用户:升级到 webpack 5 或更高,且
svelte-loader3.1.8 或更高。更早版本不再支持。 - Rollup 用户:升级
rollup-plugin-svelte到 7.1.5 或更高。 - TypeScript 用户:升级到 TypeScript 5 或更高。更低版本可能仍然可用,但官方不作保证。
说明:以上“Node 16”是 Svelte 4 的历史要求。当前仓库的 package.json 中
engines.node已提升至>=18,如果你的项目同时使用新版工具链,按更高版本规划更稳妥。
打包器必须指定 browser 条件
Svelte 4 起,打包器在为浏览器构建前端包时必须显式指定 browser 模块解析条件;SvelteKit 和 Vite 会自动处理这一点。如果你使用其他打包方案且未配置,典型症状是 onMount 等生命周期回调不被调用——因为你解析到的是服务端版本的运行时入口。
配置方式:
- Rollup:在
@rollup/plugin-node-resolve插件选项中设置browser: true。 - webpack:将
"browser"加入conditionNames数组;如果设置了alias,可能还需要同步更新。
补充(源码佐证):这个要求的根源可以从当前仓库的包导出结构看到——package.json 的 exports 字段 中,
.入口区分了"browser": "./src/index-client.js"与"default": "./src/index-server.js"两个条件。打包器若不声明browser条件,就会落到服务端入口,生命周期自然不触发。svelte/legacy、svelte/internal/client等子路径同样遵循该条件划分,这解释了为什么该配置错误会表现为“组件能渲染但生命周期回调缺失”。
移除 CJS 相关输出
Svelte 4 不再支持 CommonJS(CJS)格式的编译产物,同时移除了 svelte/register 钩子和 CJS 运行时版本。如果你的项目必须停留在 CJS 输出格式,官方建议改用打包器在构建后(post-build)步骤把 Svelte 的 ESM 输出转成 CJS。
Svelte 函数的更严格类型
这一节是影响 TypeScript 用户最多的变更,涉及 createEventDispatcher、Action、ActionReturn 和 onMount。
createEventDispatcher 的 payload 校验
createEventDispatcher 现在能区分 payload 是可选、必选还是不存在,调用点会按声明逐一检查:
// 在 Svelte 3 中以下写法都不报错:
// dispatch('required'); // 可以漏掉 detail 参数
// dispatch('noArgument', 'surprise'); // 可以随意添加 detail 参数
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
optional: number | null;
required: string;
noArgument: null;
}>();
// Svelte 4(TypeScript strict 模式):
dispatch('optional');
dispatch('required'); // 报错:缺少参数
dispatch('noArgument', 'surprise'); // 报错:不允许传参数
也就是说,泛型声明成了事件契约:required: string 意味着必须传参,noArgument: null 意味着禁止传参。
Action 与 ActionReturn 的泛型参数
Action 和 ActionReturn 的参数默认类型现在是 undefined,如果你希望 action 接收参数,必须显式写出泛型。迁移脚本会自动完成这一步:
// Svelte 3:下面这行在使用 params 时实际上是错的,但类型检查不出来
// const action: Action = (node, params) => { ... }
// Svelte 4:显式指定元素类型与参数类型
const action: Action<HTMLElement, string> = (node, params) => {
// params 的类型为 string
};
onMount 的同步清理函数要求
onMount 现在会在你异步返回清理函数时报类型错误——因为 Svelte 只会对同步返回的函数调用清理逻辑,async 函数返回的 Promise 永远不会被当作清理函数执行,这通常意味着代码里藏着一个“销毁时清理不会执行”的 bug:
// 错误写法:函数是 async,返回的 Promise 不会触发 someCleanup()
onMount(
async () => {
const something = await foo();
}
);
// 正确写法:函数是同步的,在内部处理异步,并同步返回清理函数
onMount(
() => {
foo().then((something) => {
/* ... */
});
// ...
return () => someCleanup();
}
);
Custom Elements:tag 选项被 customElement 取代
Svelte 4 对 Custom Elements 支持做了重构和显著增强,tag 选项被弃用,改为新的 customElement 选项:
<!-- Svelte 3 -->
<svelte:options tag="my-component" />
<!-- Svelte 4 -->
<svelte:options customElement="my-component" />
这一改动是为了让高级用法拥有更强的可配置性(详见仓库文档 custom-elements 的 Component options 章节)。迁移脚本会自动调整你的代码。此外,属性(property)的更新时机也略有变化。
补充(源码佐证):在当前仓库中,
tag选项已被彻底移除而非仅仅弃用。validate-options.js 中对tag的定义是removed(...),错误信息明确提示:“The tag option has been removed in Svelte 5. Use<svelte:options customElement="tag-name" />inside the component instead.” 如果你正在从 Svelte 3 直接升级到最新大版本,可以直接按customElement的用法编写,无需经历tag的中间态。
SvelteComponentTyped 被弃用
由于 SvelteComponent 现在已具备全部的泛型类型能力,SvelteComponentTyped 被弃用,应把所有使用处替换为 SvelteComponent:
// 旧
// import { SvelteComponentTyped } from 'svelte';
// export class Foo extends SvelteComponentTyped<{ aProp: string }> {}
// 新
import { SvelteComponent } from 'svelte';
export class Foo extends SvelteComponent<{ aProp: string }> {}
还有一个容易踩的坑:如果你之前把 SvelteComponent 用作组件实例类型,升级后可能出现较难理解的类型错误,解决办法是写成 typeof SvelteComponent<any>:
<script>
import ComponentA from './ComponentA.svelte';
import ComponentB from './ComponentB.svelte';
import { SvelteComponent } from 'svelte';
// 旧写法 typeof SvelteComponent 会报错,需加上 <any>
let component: typeof SvelteComponent<any>;
function choseRandomly() {
component = Math.random() > 0.5 ? ComponentA : ComponentB;
}
</script>
<button on:click={choseRandomly}>random</button>
<svelte:element this={component} />
迁移脚本会同时自动处理以上两种替换。
过渡(Transitions)默认变为 local
Svelte 4 中过渡默认是 local 的,目的是避免页面跳转时的困惑。“local”的含义是:当元素处于一个嵌套控制流块(each/if/await/key)中,且被创建/销毁的不是它的直接父块而是更外层的块时,过渡不会播放。
例如,下面例子里 slide 的 intro 动画只在 success 从 false 变为 true 时播放;而 show 从 false 变为 true 时(外层块创建,内层元素随之出现)不会播放:
{#if show}
...
{#if success}
<p in:slide>Success</p>
{/if}
{/if}
如果希望过渡在任何外层控制流块创建/销毁时都播放,加上 |global 修饰符:
<p in:slide|global>Success</p>
迁移脚本会自动为原有过渡添加该修饰符。注意:如果你的业务依赖“父块切换时子元素也动效”的旧行为,升级后要检查 |global 是否已按预期补上。
默认插槽绑定不再暴露给具名插槽(反之亦然)
Svelte 4 收紧了插槽绑定(slot bindings)的作用域:let: 绑定只在其所属的插槽内可用:
<script>
import Nested from './Nested.svelte';
</script>
<Nested let:count>
<p>count in default slot — is available: {count}</p>
<p slot="bar">count in bar slot — is not available: {count}</p>
</Nested>
这样做的动机是让插槽绑定行为更一致——此前当默认插槽来自列表渲染而具名插槽不是时,行为是未定义的。
预处理器的执行顺序与命名要求
预处理器(preprocessors)的应用顺序发生了变化:现在预处理器按数组顺序执行,而在同一个预处理器内部,处理顺序固定为 markup → script → style。文档示例中两个预处理器的日志输出差异清晰地展示了两者的区别:
import { preprocess } from 'svelte/compiler';
const { code } = await preprocess(
source,
[
{
markup: () => console.log('markup-1'),
script: () => console.log('script-1'),
style: () => console.log('style-1')
},
{
markup: () => console.log('markup-2'),
script: () => console.log('script-2'),
style: () => console.log('style-2')
}
],
{ filename: 'App.svelte' }
);
// Svelte 3 的日志(按阶段分组):
// markup-1, markup-2, script-1, script-2, style-1, style-2
// Svelte 4 的日志(按预处理器分组,组内 markup → script → style):
// markup-1, script-1, style-1, markup-2, script-2, style-2
一个典型受影响场景是 MDsveX:它必须在任何 script/style 预处理器之前执行(因为它会把 Markdown 转换成含 <script> 的 Svelte 代码)。
preprocess: [
// Svelte 3 常见写法(升级后顺序错误)
// vitePreprocess(),
// mdsvex(mdsvexConfig)
// 正确:mdsvex 在前
mdsvex(mdsvexConfig),
vitePreprocess()
]
另外,每个预处理器现在都必须有一个 name。
补充(源码佐证):当前仓库的 preprocess 实现 印证了这一执行模型——
preprocess函数以for (const preprocessor of preprocessors)外层遍历数组,内层依次判断并调用preprocessor.markup、preprocessor.script、preprocessor.style,即“组间按顺序、组内 markup→script→style”。每个 preprocessor 处理完通过result.update_source(...)立即把结果写回,保证后一个 preprocessor 看到的是前一个的完整输出。
新的 ESLint 包
eslint-plugin-svelte3 被弃用(在 Svelte 4 下可能仍可用,但不作保证),官方推荐使用新包 eslint-plugin-svelte。迁移路径有两种:
- 按官方迁移说明操作(原文档引用了相关 GitHub 讨论帖,此处不附外部链接,可在 sveltejs 组织仓库中检索);
- 用
npm create svelte@latest创建新项目,选择 ESLint(以及可选的 TypeScript)选项,然后把相关配置文件复制回现有项目。
其他破坏性变更
以下变更单项影响较小,但都需要在升级清单中逐项核对:
inert属性用于 outro 中的元素:正在退场(outroing)的元素现在会被加上inert属性,使其对辅助技术不可见并阻止交互。如果你依赖用户在退场动画期间仍可操作元素,需要评估影响。- 运行时改用
classList.toggle(name, boolean):在非常老的浏览器上可能不工作,需要支持这些浏览器时请考虑 polyfill。 - 运行时改用
CustomEvent构造函数:同样可能在很老的浏览器上不可用,必要时引入 polyfill。 StartStopNotifier接口变化:如果你从零实现 store,使用了svelte/store的StartStopNotifier(传入writable等创建函数的接口),现在除了 set 函数还必须传入 update 函数。使用 store 或基于现有 Svelte stores 创建 store 的用户不受影响。derived对非 store 值抛错:derived现在会对传入的 falsy 值(即非 store 值)抛出错误,帮助尽早发现传错参数。svelte/internal类型定义被移除:为了进一步 discourage 使用这些非公开 API 的内部方法(其中大部分在后续大版本中还会变化)。- DOM 节点移除被批量化:移除操作的顺序略有变化,如果你在相关元素上使用
MutationObserver,事件触发顺序可能受影响。 svelte.JSX命名空间迁移:此前通过扩展svelte.JSX命名空间增强全局类型定义的用户,需要迁移到svelteHTML命名空间;此前从svelte.JSX引入类型定义的用户,需要改为使用svelte/elements中的类型。
升级操作清单
综合以上内容,一次完整的 Svelte 3 → 4 升级可以按以下顺序执行:
- 提升 Node、SvelteKit / vite-plugin-svelte / svelte-loader / rollup-plugin-svelte / TypeScript 至文档要求的最低版本;
- 运行
npx svelte-migrate@latest svelte-4,自动处理Action泛型、customElement、SvelteComponentTyped、过渡|global等变更; - 检查打包器(非 SvelteKit/Vite 场景)的
browser条件解析配置,验证onMount等生命周期正常触发; - 移除
svelte/register依赖,如必须保留 CJS 产物则增加 ESM→CJS 的构建后转换步骤; - 审查事件、action、
onMount的类型使用,按新契约修正 payload 与清理函数; - 核对预处理器数组顺序(MDsveX 在前)并为每个预处理器补充 name;
- 将 ESLint 插件从
eslint-plugin-svelte3切换到eslint-plugin-svelte; - 逐条核对“其他破坏性变更”一节(
inert、classList.toggle、CustomEvent、StartStopNotifier、derived报错、svelte.JSX→svelteHTML/svelte/elements)。
阅读提示:如果你实际的目标是升级到当前仓库对应的大版本(Svelte 5,package.json 版本 5.57.0),建议同时参考仓库中的 Svelte 5 migration guide。Svelte 4 的这些变更多数仍是 5 的前置基础(例如
customElement选项在 5 中已成为唯一写法,tag选项已被 validate-options.js 标记为 removed)。
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 StartedRust0622
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