迁移到 SvelteKit v2
从 SvelteKit 版本 1 升级到版本 2 应该非常顺利。需要注意一些重大更改,这些更改列在下面。您可以使用 `npx sv migrate sveltekit-2` 自动迁移其中一些更改。
我们强烈建议在升级到 2.0 之前升级到最新的 1.x 版本,以便您可以利用有针对性的弃用警告。我们还建议您首先升级到 Svelte 4:SvelteKit 1.x 的更高版本支持它,而 SvelteKit 2.0 则需要它。
redirect 和 error 不再由您抛出
以前,您必须自己抛出
从error(...)
和redirect(...)
返回的值。在 SvelteKit 2 中,情况不再如此——调用这些函数就足够了。
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit'
// ...
throw error(500, 'something went wrong');
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(500, 'something went wrong');
svelte-migrate
将自动为您执行这些更改。
如果错误或重定向是在try {...}
块内抛出的(提示:不要这样做!),您可以使用从@sveltejs/kit
导入的isHttpError
和isRedirect
将它们与意外错误区分开来。
设置 Cookie 时需要 path
在收到不指定path
的Set-Cookie
标头时,浏览器将将 Cookie 路径设置为相关资源的父级。这种行为并非特别有用或直观,并且经常会导致错误,因为开发人员期望 Cookie 应用于整个域。
从 SvelteKit 2.0 开始,您需要在调用cookies.set(...)
、cookies.delete(...)
或cookies.serialize(...)
时设置path
,以便没有歧义。大多数情况下,您可能希望使用path: '/'
,但您可以将其设置为任何您喜欢的值,包括相对路径——''
表示“当前路径”,'.'
表示“当前目录”。
/** @type {import('./$types').PageServerLoad} */
export function function load({ cookies }: {
cookies: any;
}): {
response: any;
}
load({ cookies: any
cookies }) {
cookies: any
cookies.set(const name: void
name, value, { path: string
path: '/' });
return { response: any
response }
}
svelte-migrate
将添加注释,突出显示需要调整的位置。
不再等待顶级 Promise
在 SvelteKit 版本 1 中,如果从load
函数返回的对象的顶级属性是 Promise,则会自动等待它们。随着流式传输的引入,这种行为变得有点尴尬,因为它迫使您将流式传输的数据嵌套一层。
从版本 2 开始,SvelteKit 不再区分顶级和非顶级 Promise。要恢复阻塞行为,请使用await
(在适当的情况下使用Promise.all
防止瀑布)
// If you have a single promise
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ fetch: {
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>;
}
fetch
is equivalent to the native fetch
web API, with a few additional features:
- It can be used to make credentialed requests on the server, as it inherits the
cookie
and authorization
headers for the page request.
- It can make relative requests on the server (ordinarily,
fetch
requires a URL with an origin when used in a server context).
- Internal requests (e.g. for
+server.js
routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the
text
and json
methods of the Response
object. Note that headers will not be serialized, unless explicitly included via filterSerializedResponseHeaders
- During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.
You can learn more about making credentialed requests with cookies here
fetch }) {
const const response: any
response = await fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)
fetch(const url: string
url).Promise<Response>.then<any, never>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<any>
Attaches callbacks for the resolution and/or rejection of the Promise.
then(r: Response
r => r: Response
r.Body.json(): Promise<any>
json());
return { response: any
response }
}
// If you have multiple promises
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ fetch: {
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>;
}
fetch
is equivalent to the native fetch
web API, with a few additional features:
- It can be used to make credentialed requests on the server, as it inherits the
cookie
and authorization
headers for the page request.
- It can make relative requests on the server (ordinarily,
fetch
requires a URL with an origin when used in a server context).
- Internal requests (e.g. for
+server.js
routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the
text
and json
methods of the Response
object. Note that headers will not be serialized, unless explicitly included via filterSerializedResponseHeaders
- During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.
You can learn more about making credentialed requests with cookies here
fetch }) {
const a = fetch(url1).then(r => r.json());
const b = fetch(url2).then(r => r.json());
const [const a: any
a, const b: any
b] = await var Promise: PromiseConstructor
Represents the completion of an asynchronous operation
Promise.PromiseConstructor.all<[Promise<any>, Promise<any>]>(values: [Promise<any>, Promise<any>]): Promise<[any, any]> (+1 overload)
Creates a Promise that is resolved with an array of results when all of the provided Promises
resolve, or rejected when any Promise is rejected.
all([
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)
fetch(const url1: string
url1).Promise<Response>.then<any, never>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<any>
Attaches callbacks for the resolution and/or rejection of the Promise.
then(r: Response
r => r: Response
r.Body.json(): Promise<any>
json()),
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)
fetch(const url2: string
url2).Promise<Response>.then<any, never>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<any>
Attaches callbacks for the resolution and/or rejection of the Promise.
then(r: Response
r => r: Response
r.Body.json(): Promise<any>
json()),
]);
return { a: any
a, b: any
b };
}
goto(...) 的更改
goto(...)
不再接受外部 URL。要导航到外部 URL,请使用window.location.href = url
。state
对象现在确定$page.state
,并且必须遵守App.PageState
接口(如果已声明)。有关更多详细信息,请参阅浅路由。
路径现在默认为相对路径
在 SvelteKit 1 中,%sveltekit.assets%
在您的app.html
中默认情况下会被替换为相对路径(即.
或..
或../..
等,具体取决于正在渲染的路径),除非paths.relative
配置选项明确设置为false
。对于从$app/paths
导入的base
和assets
也是如此,但前提是paths.relative
选项明确设置为true
。
此不一致性在版本 2 中已修复。路径始终为相对路径或绝对路径,具体取决于paths.relative
的值。它默认为true
,因为这会导致更便携的应用:如果base
与应用预期不符(例如,在互联网档案上查看时)或在构建时未知(例如,部署到IPFS等),则不太可能出现问题。
服务器获取不再可跟踪
以前,可以跟踪服务器上fetch
的 URL 以重新运行加载函数。这存在潜在的安全风险(私有 URL 泄露),因此它位于dangerZone.trackServerFetches
设置后面,该设置现已移除。
preloadCode 参数必须以 base 为前缀
SvelteKit 公开了两个函数,preloadCode
和preloadData
,用于以编程方式加载与特定路径关联的代码和数据。在版本 1 中,存在细微的不一致性——传递给preloadCode
的路径不需要以base
路径为前缀(如果已设置),而传递给preloadData
的路径则需要。
这在 SvelteKit 2 中已修复——在这两种情况下,如果设置了base
,则路径应以base
为前缀。
此外,preloadCode
现在接受单个参数,而不是n个参数。
已移除 resolvePath
SvelteKit 1 包含一个名为resolvePath
的函数,它允许您解析路由 ID(如/blog/[slug]
)和一组参数(如{ slug: 'hello' }
)以生成路径名。不幸的是,返回值不包含base
路径,这限制了它在设置base
时的实用性。
因此,SvelteKit 2 使用名为resolveRoute
(名称略好一些)的函数替换resolvePath
,该函数从$app/paths
导入,并考虑了base
。
import { resolvePath } from '@sveltejs/kit';
import { base } from '$app/paths';
import { function resolveRoute(id: string, params: Record<string, string | undefined>): string
Populate a route ID with params to resolve a pathname.
resolveRoute } from '$app/paths';
const path = base + resolvePath('/blog/[slug]', { slug });
const const path: string
path = function resolveRoute(id: string, params: Record<string, string | undefined>): string
Populate a route ID with params to resolve a pathname.
resolveRoute('/blog/[slug]', { slug: any
slug });
svelte-migrate
将为您执行方法替换,但是如果您稍后在结果前面加上base
,则需要自己将其删除。
改进的错误处理
在 SvelteKit 1 中,错误处理不一致。某些错误会触发handleError
钩子,但没有好的方法来识别其状态(例如,唯一区分 404 和 500 的方法是查看event.route.id
是否为null
),而其他错误(例如,对于没有操作的页面的POST
请求的 405 错误)根本不会触发handleError
,但应该触发。在后一种情况下,生成的$page.error
将偏离App.Error
类型(如果已指定)。
SvelteKit 2 通过使用两个新属性调用handleError
钩子来解决此问题:status
和message
。对于从您的代码(或您的代码调用的库代码)抛出的错误,状态将为500
,消息将为Internal Error
。虽然error.message
可能包含不应公开给用户的敏感信息,但message
是安全的。
在预渲染期间无法使用动态环境变量
$env/dynamic/public
和$env/dynamic/private
模块提供对运行时环境变量的访问,而不是$env/static/public
和$env/static/private
公开的构建时环境变量。
在 SvelteKit 1 中的预渲染期间,它们是一样的。因此,使用“动态”环境变量的预渲染页面实际上是在“烘焙”构建时值,这是不正确的。更糟糕的是,如果用户碰巧在导航到动态渲染的页面之前访问预渲染页面,则浏览器中的$env/dynamic/public
将填充这些过时值。
因此,在 SvelteKit 2 中,在预渲染期间不再能读取动态环境变量——您应该改用static
模块。如果用户访问预渲染页面,SvelteKit 将从服务器请求$env/dynamic/public
的最新值(默认情况下,从名为_env.js
的模块请求——这可以通过config.kit.env.publicModule
进行配置),而不是从服务器渲染的 HTML 中读取。
已从 use:enhance 回调中移除 form 和 data
如果您为use:enhance
提供回调,则将使用包含各种有用属性的对象调用它。
在 SvelteKit 1 中,这些属性包括form
和data
。这些属性在一段时间前已被弃用,取而代之的是formElement
和formData
,并且在 SvelteKit 2 中已被完全移除。
包含文件输入的表单必须使用 multipart/form-data
如果表单包含<input type="file">
但没有enctype="multipart/form-data"
属性,则非 JS 提交将省略文件。如果 SvelteKit 2 在use:enhance
提交期间遇到这样的表单,它将抛出错误,以确保您的表单在没有 JavaScript 的情况下也能正常工作。
生成的 tsconfig.json 更加严格
之前,生成的 tsconfig.json
会尽力在你的 tsconfig.json
包含 paths
或 baseUrl
时仍然生成一个有效的配置。在 SvelteKit 2 中,验证更加严格,并在你在 tsconfig.json
中使用 paths
或 baseUrl
时发出警告。这些设置用于生成路径别名,你应该使用 svelte.config.js
中的 alias
配置选项,以便也为打包器创建相应的别名。
getRequest 不再抛出错误
@sveltejs/kit/node
模块导出了一些辅助函数,用于 Node 环境,包括 getRequest
,它将 Node ClientRequest
转换为标准的 Request
对象。
在 SvelteKit 1 中,如果 Content-Length
头超过指定的大小限制,getRequest
可能会抛出错误。在 SvelteKit 2 中,错误不会立即抛出,而是在稍后读取请求体(如果有)时抛出。这使得诊断更加完善,代码也更加简单。
vitePreprocess 不再从 @sveltejs/kit/vite 导出
由于 @sveltejs/vite-plugin-svelte
现在是 peer 依赖,因此 SvelteKit 2 不再重新导出 vitePreprocess
。你应该直接从 @sveltejs/vite-plugin-svelte
中导入它。
更新的依赖项要求
SvelteKit 2 需要 Node 18.13
或更高版本,以及以下最低依赖项版本
svelte@4
vite@5
typescript@5
@sveltejs/vite-plugin-svelte@3
(现在需要作为 SvelteKit 的peerDependency
— 之前是直接依赖的)@sveltejs/adapter-cloudflare@3
(如果你正在使用这些适配器)@sveltejs/adapter-cloudflare-workers@2
@sveltejs/adapter-netlify@3
@sveltejs/adapter-node@2
@sveltejs/adapter-static@3
@sveltejs/adapter-vercel@4
svelte-migrate
将为你更新 package.json
。
作为 TypeScript 升级的一部分,生成的 tsconfig.json
(你的 tsconfig.json
继承自它)现在使用 "moduleResolution": "bundler"
(这是 TypeScript 团队推荐的,因为它可以正确地解析来自具有 exports
映射的包中的类型)和 verbatimModuleSyntax
(它替换了现有的 importsNotUsedAsValues
和 preserveValueImports
标志 — 如果你在你的 tsconfig.json
中有这些标志,请删除它们。svelte-migrate
会为你完成此操作)。