SvelteKit 3 是 10 月 1 日正式发布的,HN 上那篇发布帖现在 411 分、179 条评论。我在 HN Daily 里刷到它的时候本来只想看个更新说明,结果越看越觉得这次的大版本变动不小:配置文件搬家、$lib 别名退役、$app/stores 直接砍掉。这种级别的变更,光读 release notes 是记不住的,不如亲手升一遍。
于是我在沙盒里搭了一个"最有代表性"的 SvelteKit 2 项目。说代表性,是因为我特意把它写成了 2024 年教程里最常见的样子:TypeScript、svelte.config.js、$app/stores 里的 $page、不带扩展名的 $lib 导入。然后把它扔给官方迁移命令,看它到底能替我干多少活。
实验体:一个故意很"老"的项目
项目很小,两页面的博客:首页列表 + /blog/[slug] 详情页,但每一处都踩在 3.0 的变更点上。svelte.config.js 里配着 adapter-auto 和 preprocess,这是 3.0 里被移除的文件;+layout.svelte 从 $app/stores 导入 page,模板里写 $page.url.pathname;路由文件里全是 import { formatDate } from '$lib/utils' 这种不带扩展名的导入。
package.json 锁在 @sveltejs/kit ^2.70.3------2.x 线的最后一个版本,配 Vite 7 和 TypeScript 5.9。装完依赖先跑一次 build 确认基线干净:4.5 秒出头,一次过。然后 git commit,这是后面看 diff 的基准。
顺带说一句,官方迁移指南的第一条建议就是"先升到 2.x 的最新版本再迁移",因为 2.x 末期版本会对所有弃用 API 打警告。我搭的项目已经是 2.70.3,所以这步等于白捡。
跑迁移:12 个任务,一条命令
css
npx sv migrate sveltekit-3 --tasks all --confirm
CLI 的输出直接列出它会做的事,一共 12 项:package-json、tsconfig、svelte-config、environment、paths、external-redirects、shallow-routing、params、imports、lib-alias、app-state,最后一个 collect-migration-instructions。我盯着它跑完,全程没有任何交互提问,末尾一句 "All tasks applied successfully!",然后自动执行了 npm install。
之后 git diff 的结果比我想象的干净。排除 node_modules,它动了 6 个源文件、删掉了 svelte.config.js、改了 package.json / tsconfig.json / vite.config.ts,另外生成了一个 MIGRATION_TASKS.md。
最有信息量的是下面这些改动。 先看 package.json 里冒出来的 imports 字段,这是 3.0 新引入的 #lib 别名的声明:
json
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
}
注意它指向的是 .js 文件,而我的源码全是 .ts。这不是迁移工具的 bug,是 TypeScript 的标准玩法------moduleResolution 按 bundler 模式解析时,.js 后缀会映射到同名 .ts 文件。官方文档里专门提醒过 "$lib 换成 #lib 后必须带上模块扩展名",我本来以为这是升级里最容易漏的手工活,结果工具把我的 $lib/utils 全部改写成了 #lib/utils.js,扩展名都替我补上了。
第二处是配置的搬家。 svelte.config.js 被整个删掉,原来的 preprocess 和 adapter 变成了 vite.config.ts 里 sveltekit() 的插件参数:
css
export default defineConfig({
plugins: [
sveltekit({ preprocess: vitePreprocess(), adapter: adapter() })
]
});
tsconfig 也跟着瘦身,extends 从 ./.svelte-kit/tsconfig.json 改成 $app/tsconfig,原来那一串 allowJs、moduleResolution 之类的重复项被删掉,只剩我真正自定义的 sourceMap 和 strict。include 则被显式写成了 ["src", "vite.config.ts"]。
第三处是 store 的替换。 $app/stores 在 3.0 里彻底移除,工具把我的导入改成了 $app/state,模板里的 $page.url.pathname 同步改成 page.url.pathname------少了个 $,语义也从 store 变成了响应式对象。另外有个小改动容易忽略:HandleServerError 这个类型的导入路径从 @sveltejs/kit 挪到了 @sveltejs/kit/hooks,工具同样顺手改掉了。
依赖版本也是它一手包办的:kit ^3.0.0、Vite ^8.0.12、TypeScript ^6.0.0、svelte ^5.57.1,svelte-check 从 4.3 跳到 4.7.5。装下来没有任何 peer 冲突报错。
它没帮我做的部分,才是重点
diff 里那张 MIGRATION_TASKS.md,官方的说法是"不常见、依赖上下文、或者自动化做不安全的迁移"才进这张清单。我的项目只命中了三条,先说扎手的:handleError 的行为变了。2.x 里 handleError 只接收未预期的错误,我 error(404, 'Post not found') 抛出去的东西不会进这个钩子;3.x 把预期错误、校验错误、渲染错误统统喂给它。区别在哪?在我 hooks.server.ts 里那行 console.error('unhandled error on', ...)------3.x 之下,每次有人访问一个不存在的博客文章,这行日志都会打着 "unhandled error" 的名义刷出来。代码一行不用改,但语义已经完全不对了,这种事自动化工具确实不敢替你判断,只能列在清单里让我自己过。
page.url 在 3.0 里变成 readonly,如果要改动它得先 new URL(page.url.href) 拷贝一份。清单把我的 +layout.svelte 列进去了------但我只是在读 pathname,属于"宁可误报不可漏报"的宽匹配,确认无影响后跳过即可。剩下那条静态资源 CORS 在 dev 下交给 Vite 处理,我的项目没这个需求,忽略。
也就是说:12 个自动化任务加一张 3 条的人工清单,我的项目升级完事了。整个过程中我没写过一行迁移代码,最接近"动手"的动作是读完 MIGRATION_TASKS.md 后在脑子里给三条任务各打了一个勾。
升级完的验证
svelte-check 跑下来 0 errors 0 warnings。error(404, 'Post not found') 这种 3.0 里收紧过的 API(第二参数现在必须是 string)我的写法本来就合规,不用动。
构建速度是这次升级最直观的收益。Vite 8 内置的 rolldown 已经是 1.0 正式版,替代了 rollup。为了对比公平,我把 2.x 和 3.x 的代码分别 archive 成两个干净目录、重新 install、清掉缓存后各跑一次冷构建,同一个 1 核 2G 的沙盒:2.x(Vite 7 + Rollup)client 1.20 秒、server 4.44 秒;3.x(Vite 8 + rolldown)两段分别是 190ms 和 291ms。两页面的玩具项目从约 5.6 秒降到 0.5 秒,项目的绝对规模小,但方向和量级摆在那,生产项目只会受益更大。
最后起 preview 服务验了运行时:首页 SSR 输出正常,page.url.pathname 的判断生效,访问 /blog/nope 如实返回 404。除了一条------那行 404 的 "unhandled error" 日志,正是前面说的 handleError 语义变化,我在清单里确认过、还没改的那处。
要不要现在升
我的判断分两头。新项目没有理由再用 2.x,sv create 默认生成的就是 3.0 形态。存量项目的话,迁移成本比我预想的低得多------前提是你的项目和我一样用着"正统"的 2.x 写法,越偏离官方模板的项目,那张 MIGRATION_TASKS.md 就会越长,自动化能覆盖的比例就越低。
两个观望理由也说一下:remote functions 虽然是这次主推的方向,但 3.0 里仍然是实验特性,要开 experimental.remoteFunctions 标志才能用;adapter 生态刚发大版本(adapter-auto 已到 8.x),自定义 adapter 的项目升级前最好先确认对应版本。另外官方在发布文末预告了 11 月 19-20 日在卢布尔雅那的 Svelte Summit,到时候应该有 remote functions 的后续消息,不急的项目可以等那个节点再动手。
我的建议是:找个有空的时间,git commit 之后直接跑 npx sv migrate sveltekit-3,看看你的 MIGRATION_TASKS.md 有几行。清单要是很长,多半说明你的项目早就没按官方姿势写。