跳至主要內容

遷移至 SvelteKit v2

從 SvelteKit 版本 1 升級到版本 2 應該大多是無縫的。這裡列出了一些需要注意的重大變更。您可以使用 npx sv migrate sveltekit-2 自動遷移其中一些變更。

我們強烈建議您在升級到 2.0 之前,先升級到最新的 1.x 版本,以便您可以利用有針對性的棄用警告。我們也建議先更新到 Svelte 4:較新版本的 SvelteKit 1.x 支援它,而 SvelteKit 2.0 需要它。

redirecterror 不再由您拋出

先前,您必須自己 throwerror(...)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.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
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.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
error
(500, 'something went wrong');

svelte-migrate 將自動為您進行這些變更。

如果錯誤或重新導向是在 try {...} 區塊內拋出的(提示:不要這樣做!),您可以使用從 @sveltejs/kit 匯入的 isHttpErrorisRedirect 將它們與意外錯誤區分開來。

設定 Cookie 時必須指定 path

當接收到未指定 pathSet-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;
}
@type{import('./$types').PageServerLoad}
load
({ cookies: anycookies }) {
cookies: anycookies.set(const name: void
@deprecated
name
, value, { path: stringpath: '/' });
return { response: anyresponse } }

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>>
@type{import('./$types').PageServerLoad}
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.

@paramonfulfilled The callback to execute when the Promise is resolved.
@paramonrejected The callback to execute when the Promise is rejected.
@returnsA Promise for the completion of which ever callback is executed.
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>>
@type{import('./$types').PageServerLoad}
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: 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.

@paramvalues An array of Promises.
@returnsA new Promise.
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.

@paramonfulfilled The callback to execute when the Promise is resolved.
@paramonrejected The callback to execute when the Promise is rejected.
@returnsA Promise for the completion of which ever callback is executed.
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.

@paramonfulfilled The callback to execute when the Promise is resolved.
@paramonrejected The callback to execute when the Promise is rejected.
@returnsA Promise for the completion of which ever callback is executed.
then
(r: Responser => r: Responser.Body.json(): Promise<any>json()),
]); return { a: anya, b: anyb }; }

goto(...) 的變更

goto(...) 不再接受外部 URL。若要導覽至外部 URL,請使用 window.location.href = urlstate 物件現在決定 $page.state,並且如果已宣告,則必須遵守 App.PageState 介面。如需更多詳細資訊,請參閱淺層路由

路徑現在預設為相對路徑

在 SvelteKit 1 中,您的 app.html 中的 %sveltekit.assets% 在伺服器端渲染期間預設會被替換為相對路徑(即 ...../.. 等,取決於正在渲染的路徑),除非明確將 paths.relative 設定選項設定為 false。從 $app/paths 匯入的 baseassets 也是如此,但前提是明確將 paths.relative 選項設定為 true

此不一致之處已在版本 2 中修正。路徑永遠是相對的或永遠是絕對的,具體取決於 paths.relative 的值。它預設為 true,因為這會產生更具可移植性的應用程式:如果 base 與應用程式預期的不同(例如,在 Internet Archive 上檢視時),或在建置時未知(例如,部署到 IPFS 等時),則比較不容易發生錯誤。

伺服器擷取不再可追蹤

先前,可以追蹤伺服器上 fetch 的 URL,以便重新執行載入函式。這構成潛在的安全性風險(洩漏私有 URL),因此它位於 dangerZone.trackServerFetches 設定之後,現在已移除。

preloadCode 參數必須以 base 為前綴

SvelteKit 公開兩個函式,preloadCodepreloadData,用於以程式設計方式載入與特定路徑相關聯的程式碼和資料。在版本 1 中,有一個細微的不一致之處 — 傳遞給 preloadCode 的路徑不需要以 base 路徑為前綴(如果已設定),而傳遞給 preloadData 的路徑則需要。

這已在 SvelteKit 2 中修正 — 在這兩種情況下,如果設定了路徑,則路徑都應該以 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.

@examplejs import { resolveRoute } from '$app/paths'; resolveRoute( `/blog/[slug]/[...somethingElse]`, { slug: 'hello-world', somethingElse: 'something/else' } ); // `/blog/hello-world/something/else`
resolveRoute
} from '$app/paths';
const path = base + resolvePath('/blog/[slug]', { slug }); const const path: stringpath = function resolveRoute(id: string, params: Record<string, string | undefined>): string

Populate a route ID with params to resolve a pathname.

@examplejs import { resolveRoute } from '$app/paths'; resolveRoute( `/blog/[slug]/[...somethingElse]`, { slug: 'hello-world', somethingElse: 'something/else' } ); // `/blog/hello-world/something/else`
resolveRoute
('/blog/[slug]', { slug: anyslug });

svelte-migrate 將為您進行方法替換,但如果您稍後在結果前面加上 base,則需要自行移除。

改進的錯誤處理

SvelteKit 1 中的錯誤處理不一致。有些錯誤會觸發 handleError 鉤子,但沒有很好的方法來辨別其狀態(例如,判斷 404 錯誤與 500 錯誤的唯一方法是查看 event.route.id 是否為 null),而其他錯誤(例如針對沒有操作的頁面的 POST 請求的 405 錯誤)根本不會觸發 handleError,但應該觸發。在後一種情況下,產生的 $page.error 會偏離 App.Error 類型(如果已指定)。

SvelteKit 2 會透過使用兩個新屬性 statusmessage 呼叫 handleError 鉤子來清理此問題。對於從您的程式碼(或您的程式碼呼叫的程式庫程式碼)拋出的錯誤,狀態將為 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 中讀取它們。

formdata 已從 use:enhance 回調中移除

如果您為 use:enhance 提供回調,則會使用包含各種有用屬性的物件呼叫它。

在 SvelteKit 1 中,這些屬性包含 formdata。這些屬性在先前已棄用,轉而使用 formElementformData,並且已在 SvelteKit 2 中完全移除。

包含檔案輸入的表單必須使用 multipart/form-data

如果表單包含 <input type="file">,但沒有 enctype="multipart/form-data" 屬性,則非 JavaScript 提交將會省略該檔案。SvelteKit 2 在 use:enhance 提交期間如果遇到這種表單,將會拋出錯誤,以確保您的表單在 JavaScript 不存在時也能正確運作。

產生的 tsconfig.json 更加嚴格

先前,當您的 tsconfig.json 包含 pathsbaseUrl 時,產生的 tsconfig.json 會盡力產生一個大致有效的配置。在 SvelteKit 2 中,驗證更加嚴格,當您在 tsconfig.json 中使用 pathsbaseUrl 時會發出警告。這些設定是用來產生路徑別名,您應該改用 svelte.config.js 中的 alias 配置選項,以便同時為打包器建立對應的別名。

getRequest 不再拋出錯誤

@sveltejs/kit/node 模組匯出用於 Node 環境的輔助函式,包括將 Node ClientRequest 轉換為標準 Request 物件的 getRequest

在 SvelteKit 1 中,如果 Content-Length 標頭超過指定的大小限制,getRequest 可能會拋出錯誤。在 SvelteKit 2 中,錯誤將不會提前拋出,而是延遲到稍後讀取請求主體(如果有的話)時才拋出。這能提供更好的診斷並簡化程式碼。

vitePreprocess 不再從 @sveltejs/kit/vite 匯出

由於 @sveltejs/vite-plugin-svelte 現在是一個對等依賴,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 團隊推薦使用,因為它可以正確解析 package.json 中具有 exports 映射的套件的類型)和 verbatimModuleSyntax(它取代了現有的 importsNotUsedAsValuespreserveValueImports 標誌 – 如果您的 tsconfig.json 中有這些標誌,請移除它們。svelte-migrate 會為您執行此操作)。

在 GitHub 上編輯此頁面

上一頁 下一頁