Migrating to 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.
سوفت-مهاجرت将为您自动执行这些更改。
如果错误或重定向在try {...}块内部抛出(提示:不要这样做!),您可以使用从@sveltejs/kit导入的isHttpError和isRedirect来区分它们与意外错误。
路径设置 cookie 时是必需的
当接收到未指定 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: anycookies }) {
cookies: anycookies.set(const name: voidname, value, { path: stringpath: '/' });
return { response: anyresponse }
}سلتو-مigrate 将添加注释以突出需要调整的位置。
顶级承诺不再等待
在 SvelteKit 版本 1 中,如果从load函数返回的对象的顶级属性是 promises,它们会自动等待。随着流式传输的引入,这种行为变得有些尴尬,因为它迫使你将流式数据嵌套在一层之内。
截至版本 2,SvelteKit 不再区分顶层和非顶层承诺。要恢复阻塞行为,请使用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: anyresponse = await fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)fetch(const url: stringurl).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: Responser => r: Responser.Body.json(): Promise<any>json());
return { response: anyresponse }
}// 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: anya, const b: anyb] = await var Promise: PromiseConstructorRepresents 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: stringurl1).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: Responser => r: Responser.Body.json(): Promise<any>json()),
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)fetch(const url2: stringurl2).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: Responser => r: Responser.Body.json(): Promise<any>json()),
]);
return { a: anya, b: anyb };
}goto(...) 变更
goto(...) 不再接受外部 URL。要导航到外部 URL,请使用 window.location.href = url。现在 state 对象决定 $page.state,并且如果已声明,必须遵循 App.PageState 接口。有关更多详细信息,请参阅 浅路由。
路径现在默认为相对路径
在 SvelteKit 1 中,您的app.html中的%sveltekit.assets%在服务器端渲染时默认被替换为相对路径(即.或..或../..等,具体取决于渲染的路径),除非显式地将paths.relative配置选项设置为false。同样,从$app/paths导入的base和assets也是如此,但仅当显式将paths.relative选项设置为true时。
此不一致性已在版本 2 中修复。路径要么始终是相对的,要么始终是绝对的,具体取决于paths.relative的值。默认为true,因为这会产生更便携的应用程序:如果base不是应用程序期望的(例如在互联网档案馆查看时)或构建时未知(例如部署到IPFS等),则出现问题的可能性更小。
服务器抓取不再可追踪
之前可以从服务器上的fetches 跟踪 URL 以重新运行加载函数。这可能导致潜在的安全风险(私人 URL 泄露),因此它位于dangerZone.trackServerFetches设置之后,该设置现已移除。
preloadCode 参数必须以 base 前缀
SvelteKit 公开了两个函数,preloadCode 和 preloadData,用于以编程方式加载与特定路径关联的代码和数据。在版本 1 中,存在一个微妙的不一致——传递给preloadCode的路径不需要(如果已设置)以base路径作为前缀,而传递给preloadData的路径则需要。
这是在 SvelteKit 2 中修复的——在两种情况下,如果已设置,路径应该以base为前缀。
此外,preloadCode 现在接受单个参数,而不是 n 个参数。
resolvePath 已被移除
SvelteKit 1 包含了一个名为 resolvePath 的函数,该函数允许您将路由 ID(如 /blog/[slug])和一组参数(如 { slug: 'hello' })解析为路径名。不幸的是,返回值没有包括 base 路径,限制了其在 base 已设置的情况下的实用性。
因此,SvelteKit 2 将 resolvePath 替换为(名称稍好一些的)函数 resolveRoute,该函数从 $app/paths 导入,并考虑了 base。
import { resolvePath } from '@sveltejs/kit';
import { base } from '$app/paths';
import { function resolveRoute(id: string, params: Record<string, string | undefined>): stringPopulate a route ID with params to resolve a pathname.
resolveRoute } from '$app/paths';
const path = base + resolvePath('/blog/[slug]', { slug });
const const path: stringpath = function resolveRoute(id: string, params: Record<string, string | undefined>): stringPopulate a route ID with params to resolve a pathname.
resolveRoute('/blog/[slug]', { slug: anyslug });سلتو-مigrate 将为您执行方法替换,尽管如果您稍后用 base 预先添加结果,您需要自己将其删除。
改进的错误处理
错误在 SvelteKit 1 中处理不一致。一些错误会触发handleError钩子,但没有好的方法来辨别它们的状态(例如,区分 404 和 500 的唯一方法是通过查看event.route.id是否为null),而其他错误(如对没有操作的页面进行POST请求的 405 错误)根本不会触发handleError,但应该会。在后一种情况下,结果$page.error将与指定的App.Error(如果指定的话)类型不符。
SvelteKit 2 通过调用 handleError 钩子并使用两个新属性:status 和 message 来清理。对于从您的代码(或由您的代码调用的库代码)抛出的错误,状态将是 500,消息将是 内部错误。而 error.message 可能包含不应向用户暴露的敏感信息,message 则是安全的。
动态环境变量在预渲染期间无法使用
The $env/dynamic/public 和 $env/dynamic/private 模块提供对 运行时 环境变量的访问,与 $env/static/public 和 $env/static/private 提供的 构建时 环境变量相反。
在 SvelteKit 1 的预渲染期间,它们是相同的。因此,使用 ‘动态’ 环境变量的预渲染页面实际上是在“烘焙”构建时间值,这是不正确的。更糟糕的是,如果用户在导航到动态渲染的页面之前恰好访问了预渲染的页面,$env/dynamic/public 将在浏览器中使用这些过时的值进行填充。
由于这个原因,在 SvelteKit 2 的预渲染过程中无法读取动态环境变量 — 您应该使用 静态 模块。如果用户访问预渲染的页面,SvelteKit 将从服务器(默认情况下从名为/_app/env.js的模块)请求最新的$env/dynamic/public值,而不是从服务器渲染的 HTML 中读取它们。
表单 和 数据 已从 使用:增强 回调中移除
如果您提供了一个回调到使用:增强,它将用一个包含各种有用属性的对象被调用。
在 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不再抛出错误
The @sveltejs/kit/node 模块导出用于 Node 环境的辅助函数,包括 getRequest,它将 Node ClientRequest 转换为标准的 Request 对象。
在 SvelteKit 1 中,getRequest 如果 Content-Length 头部超出指定大小限制时可能会抛出异常。在 SvelteKit 2 中,错误将不会立即抛出,而是在读取请求体(如果有)时才会抛出。这有助于更好地进行诊断和编写更简单的代码。
vitепPгeprеss不再从@sveltejs/kit/vite导出
由于 @sveltejs/vite-plugin-svelte 现在是一个 peer dependency,SvelteKit 2 不再导出 vitePreprocess。您应直接从 @sveltejs/vite-plugin-svelte 导入它。
更新依赖项要求
SvelteKit 2 需要 Node 18.13 或更高版本,以及以下最低依赖版本:
斯威夫特@4vit@5typescript@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
سلتو-مigrate 将为您更新 package.json。
作为 TypeScript 升级的一部分,生成的tsconfig.json(你的tsconfig.json从中扩展而来)现在使用"moduleResolution": "bundler"(TypeScript 团队推荐,因为它可以正确解析具有 package.json 中exports映射的包中的类型)和verbatimModuleSyntax(它替换了现有的importsNotUsedAsValues和preserveValueImports标志——如果你在tsconfig.json中有这些,请删除它们。svelte-migrate会为你完成这项工作)。
SvelteKit 2.12: $app/stores 已弃用
SvelteKit 2.12 引入了基于 $app/state 的 Svelte 5 runes API。$app/state 提供了与 $app/stores 相同的一切,但在使用方式和位置上提供了更大的灵活性。最重要的是,page 对象现在更加精细,例如,对 page.state 的更新不会使 page.data 无效,反之亦然。
因此,$app/stores 已弃用,并将在 SvelteKit 3 中被移除。如果您尚未升级,我们建议升级到 Svelte 5,然后迁移离开 $app/stores。大多数替代方案应该相当简单:将 $app/stores 的导入替换为 $app/state,并从使用位置删除 $ 前缀。
<script>
import { page } from '$app/stores';
import { page } from '$app/state';
</script>
{$page.data}
{page.data}使用npx sv migrate app-state自动迁移大部分在$app/stores中使用的.svelte组件。
Edit this page on GitHub llms.txt