遷移至 SvelteKit v2
從 SvelteKit 版本 1 升級到版本 2 應該大多是無縫的。這裡列出了一些需要注意的重大變更。您可以使用 npx sv migrate sveltekit-2
自動遷移其中一些變更。
我們強烈建議您在升級到 2.0 之前,先升級到最新的 1.x 版本,以便您可以利用有針對性的棄用警告。我們也建議先更新到 Svelte 4:較新版本的 SvelteKit 1.x 支援它,而 SvelteKit 2.0 需要它。
redirect
和 error
不再由您拋出
先前,您必須自己 throw
從 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 中,您的 app.html
中的 %sveltekit.assets%
在伺服器端渲染期間預設會被替換為相對路徑(即 .
或 ..
或 ../..
等,取決於正在渲染的路徑),除非明確將 paths.relative
設定選項設定為 false
。從 $app/paths
匯入的 base
和 assets
也是如此,但前提是明確將 paths.relative
選項設定為 true
。
此不一致之處已在版本 2 中修正。路徑永遠是相對的或永遠是絕對的,具體取決於 paths.relative
的值。它預設為 true
,因為這會產生更具可移植性的應用程式:如果 base
與應用程式預期的不同(例如,在 Internet Archive 上檢視時),或在建置時未知(例如,部署到 IPFS 等時),則比較不容易發生錯誤。
伺服器擷取不再可追蹤
先前,可以追蹤伺服器上 fetch
的 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 以一個(名稱稍微好一點的)函式 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 會透過使用兩個新屬性 status
和 message
呼叫 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 中讀取它們。
form
和 data
已從 use:enhance
回調中移除
如果您為 use:enhance
提供回調,則會使用包含各種有用屬性的物件呼叫它。
在 SvelteKit 1 中,這些屬性包含 form
和 data
。這些屬性在先前已棄用,轉而使用 formElement
和 formData
,並且已在 SvelteKit 2 中完全移除。
包含檔案輸入的表單必須使用 multipart/form-data
如果表單包含 <input type="file">
,但沒有 enctype="multipart/form-data"
屬性,則非 JavaScript 提交將會省略該檔案。SvelteKit 2 在 use:enhance
提交期間如果遇到這種表單,將會拋出錯誤,以確保您的表單在 JavaScript 不存在時也能正確運作。
產生的 tsconfig.json 更加嚴格
先前,當您的 tsconfig.json
包含 paths
或 baseUrl
時,產生的 tsconfig.json
會盡力產生一個大致有效的配置。在 SvelteKit 2 中,驗證更加嚴格,當您在 tsconfig.json
中使用 paths
或 baseUrl
時會發出警告。這些設定是用來產生路徑別名,您應該改用 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
(它取代了現有的 importsNotUsedAsValues
和 preserveValueImports
標誌 – 如果您的 tsconfig.json
中有這些標誌,請移除它們。svelte-migrate
會為您執行此操作)。