首页
/ Svelte 4 迁移实战指南:从 Svelte 3 到 4 的全部破坏性变更与升级要点

Svelte 4 迁移实战指南:从 Svelte 3 到 4 的全部破坏性变更与升级要点

2026-09-04 16:42:37作者:邬祺芯Juliet

本文基于 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-loader 3.1.8 或更高。更早版本不再支持。
  • Rollup 用户:升级 rollup-plugin-svelte 到 7.1.5 或更高。
  • TypeScript 用户:升级到 TypeScript 5 或更高。更低版本可能仍然可用,但官方不作保证。

说明:以上“Node 16”是 Svelte 4 的历史要求。当前仓库的 package.jsonengines.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/legacysvelte/internal/client 等子路径同样遵循该条件划分,这解释了为什么该配置错误会表现为“组件能渲染但生命周期回调缺失”。

移除 CJS 相关输出

Svelte 4 不再支持 CommonJS(CJS)格式的编译产物,同时移除了 svelte/register 钩子和 CJS 运行时版本。如果你的项目必须停留在 CJS 输出格式,官方建议改用打包器在构建后(post-build)步骤把 Svelte 的 ESM 输出转成 CJS。

Svelte 函数的更严格类型

这一节是影响 TypeScript 用户最多的变更,涉及 createEventDispatcherActionActionReturnonMount

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 的泛型参数

ActionActionReturn 的参数默认类型现在是 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 动画只在 successfalse 变为 true 时播放;而 showfalse 变为 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.markuppreprocessor.scriptpreprocessor.style,即“组间按顺序、组内 markup→script→style”。每个 preprocessor 处理完通过 result.update_source(...) 立即把结果写回,保证后一个 preprocessor 看到的是前一个的完整输出。

新的 ESLint 包

eslint-plugin-svelte3 被弃用(在 Svelte 4 下可能仍可用,但不作保证),官方推荐使用新包 eslint-plugin-svelte。迁移路径有两种:

  1. 按官方迁移说明操作(原文档引用了相关 GitHub 讨论帖,此处不附外部链接,可在 sveltejs 组织仓库中检索);
  2. npm create svelte@latest 创建新项目,选择 ESLint(以及可选的 TypeScript)选项,然后把相关配置文件复制回现有项目。

其他破坏性变更

以下变更单项影响较小,但都需要在升级清单中逐项核对:

  • inert 属性用于 outro 中的元素:正在退场(outroing)的元素现在会被加上 inert 属性,使其对辅助技术不可见并阻止交互。如果你依赖用户在退场动画期间仍可操作元素,需要评估影响。
  • 运行时改用 classList.toggle(name, boolean):在非常老的浏览器上可能不工作,需要支持这些浏览器时请考虑 polyfill。
  • 运行时改用 CustomEvent 构造函数:同样可能在很老的浏览器上不可用,必要时引入 polyfill。
  • StartStopNotifier 接口变化:如果你从零实现 store,使用了 svelte/storeStartStopNotifier(传入 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 升级可以按以下顺序执行:

  1. 提升 Node、SvelteKit / vite-plugin-svelte / svelte-loader / rollup-plugin-svelte / TypeScript 至文档要求的最低版本;
  2. 运行 npx svelte-migrate@latest svelte-4,自动处理 Action 泛型、customElementSvelteComponentTyped、过渡 |global 等变更;
  3. 检查打包器(非 SvelteKit/Vite 场景)的 browser 条件解析配置,验证 onMount 等生命周期正常触发;
  4. 移除 svelte/register 依赖,如必须保留 CJS 产物则增加 ESM→CJS 的构建后转换步骤;
  5. 审查事件、action、onMount 的类型使用,按新契约修正 payload 与清理函数;
  6. 核对预处理器数组顺序(MDsveX 在前)并为每个预处理器补充 name;
  7. 将 ESLint 插件从 eslint-plugin-svelte3 切换到 eslint-plugin-svelte
  8. 逐条核对“其他破坏性变更”一节(inertclassList.toggleCustomEventStartStopNotifierderived 报错、svelte.JSXsvelteHTML / svelte/elements)。

阅读提示:如果你实际的目标是升级到当前仓库对应的大版本(Svelte 5,package.json 版本 5.57.0),建议同时参考仓库中的 Svelte 5 migration guide。Svelte 4 的这些变更多数仍是 5 的前置基础(例如 customElement 选项在 5 中已成为唯一写法,tag 选项已被 validate-options.js 标记为 removed)。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384