前言
Svelte,一個語法簡潔、入門容易,面向未來的前端框架。
從 Svelte 誕生之初,就備受開發者的喜愛,根據統計,從 2019 年到 2024 年,連續 6 年一直是開發者最感興趣的前端框架 No.1:
Svelte 以其獨特的編譯時優化機制著稱,具有輕量級、高性能、易上手等特性,非常適合構建輕量級 Web 項目。
為了幫助大家學習 Svelte,我同時搭建了 Svelte 最新的中文文檔站點。
如果需要進階學習,也可以入手我的小冊《Svelte 開發指南》,語法篇、實戰篇、原理篇三大篇章帶你係統掌握 Svelte!
歡迎圍觀我的“網頁版朋友圈”、加入“冴羽·成長陪伴社羣”,踏上“前端大佬成長之路”。
Hooks
“Hooks” 是您聲明的應用程序範圍的函數,SvelteKit 會在響應特定事件時調用它們,讓您能夠對框架的行為進行更為精細的控制。
有三個 hook 文件,都是可選的:
src/hooks.server.js— 您的應用程序的服務端 hooksrc/hooks.client.js— 您的應用程序的客户端 hooksrc/hooks.js— 您的應用程序的在客户端和服務端都運行的 hook
這些模塊中的代碼會在應用程序啓動時運行,這使得它們對初始化數據庫客户端等操作很有用。
[!NOTE] 您可以通過 config.kit.files.hooks 配置這些文件的位置。
服務端 hook
以下 hook 可以添加到 src/hooks.server.js 中:
handle
這個函數在 SvelteKit 服務端每次接收到 request 時運行 — 無論是在應用程序運行時,還是在預渲染過程中 — 並決定response。
它接收一個表示請求的 event 對象和一個名為 resolve 的函數,該函數渲染路由並生成一個 Response。這允許您修改響應頭或響應體,或完全繞過 SvelteKit(例如,用於以編程方式實現路由)。
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
if (event.url.pathname.startsWith('/custom')) {
return new Response('custom response');
}
const response = await resolve(event);
return response;
}
[!NOTE] 對靜態資源的請求 — 包括已經預渲染的頁面 — 不會由 SvelteKit 處理。
如果未實現,默認為 ({ event, resolve }) => resolve(event)。
locals
要向請求中添加自定義數據(這些數據會傳遞給 +server.js 中的處理程序和服務端的 load 函數),可以填充 event.locals 對象,如下所示。
/// file: src/hooks.server.js
// @filename: ambient.d.ts
type User = {
name: string;
}
declare namespace App {
interface Locals {
user: User;
}
}
const getUserInformation: (cookie: string | void) => Promise<User>;
// @filename: index.js
// ---cut---
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
event.locals.user = await getUserInformation(event.cookies.get('sessionid'));
const response = await resolve(event);
response.headers.set('x-custom-header', 'potato');
return response;
}
您可以定義多個 handle 函數,並使用sequence 輔助函數執行它們。
resolve 還支持第二個可選參數,讓您能夠更好地控制響應的渲染方式。該參數是一個對象,可以包含以下字段:
transformPageChunk(opts: { html: string, done: boolean }): MaybePromise<string | undefined>— 對 HTML 應用自定義轉換。如果done為 true,則是最後一個塊。塊不保證是格式良好的 HTML(例如,它們可能包含一個元素的開始標籤但沒有結束標籤),但它們總是會在合理的邊界處分割,比如%sveltekit.head%或佈局/頁面組件。filterSerializedResponseHeaders(name: string, value: string): boolean— 確定當load函數使用fetch加載資源時,哪些頭部應該包含在序列化的響應中。默認情況下,不會包含任何頭部。preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean— 確定應該在<head>標籤中添加哪些文件以預加載。該方法在構建代碼塊時被調用,每個找到的文件都會被調用 — 例如,如果您在+page.svelte中有import './styles.css,在訪問該頁面時,preload將傳入該 CSS 文件的解析路徑進行調用。注意,在開發模式下不會調用preload,因為它依賴於構建時的分析。預加載可以通過更早下載資源來提高性能,但如果不必要地下載太多內容也會適得其反。默認情況下,會預加載js和css文件。目前不會預加載asset文件,但我們可能會在評估反饋後添加此功能。
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
transformPageChunk: ({ html }) => html.replace('old', 'new'),
filterSerializedResponseHeaders: (name) => name.startsWith('x-'),
preload: ({ type, path }) => type === 'js' || path.includes('/important/')
});
return response;
}
注意,resolve(...) 永遠不會拋出錯誤,它總是會返回一個帶有適當狀態碼的 Promise<Response>。如果在 handle 期間其他地方拋出錯誤,這將被視為致命錯誤,SvelteKit 將根據 Accept 頭部返回錯誤的 JSON 表示或回退錯誤頁面 — 後者可以通過 src/error.html 自定義。您可以在這裏閲讀更多關於錯誤處理的信息。
handleFetch
這個函數允許您修改(或替換)在服務端上運行的 load 或 action 函數中發生的 fetch 請求(或在預渲染期間)。
例如,當用户執行客户端導航到相應頁面時,您的 load 函數可能會向公共 URL(如 https://api.yourapp.com)發出請求,但在 SSR 期間,直接訪問 API 可能更有意義(繞過位於它和公共互聯網之間的代理和負載均衡器)。
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ request, fetch }) {
if (request.url.startsWith('https://api.yourapp.com/')) {
// 克隆原始請求,但改變 URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request
);
}
return fetch(request);
}
認證憑據
對於同源請求,除非 credentials 選項設置為 "omit",否則 SvelteKit 的 fetch 實現會轉發 cookie 和 authorization 頭部。
對於跨源請求,如果請求 URL 屬於應用程序的子域,則會包含 cookie — 例如,如果您的應用程序在 my-domain.com 上,而您的 API 在 api.my-domain.com 上,cookie 將包含在請求中。
如果您的應用程序和 API 在兄弟子域上 — 例如 www.my-domain.com 和 api.my-domain.com — 那麼屬於共同父域(如 my-domain.com)的 cookie 將不會被包含,因為 SvelteKit 無法知道 cookie 屬於哪個域。在這些情況下,您需要使用 handleFetch 手動包含 cookie:
/// file: src/hooks.server.js
// @errors: 2345
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ event, request, fetch }) {
if (request.url.startsWith('https://api.my-domain.com/')) {
request.headers.set('cookie', event.request.headers.get('cookie'));
}
return fetch(request);
}
共享 hook
以下 hook 可以同時添加到 src/hooks.server.js 和 src/hooks.client.js 中:
handleError
如果在加載或渲染期間拋出意外錯誤,此函數將被調用,並傳入 error、event、status 代碼和 message。這允許兩件事:
- 您可以記錄錯誤
- 您可以生成一個安全的、顯示給用户的自定義錯誤表示,省略敏感的詳細信息,如消息和堆棧跟蹤。返回的值(默認為
{ message })會成為$page.error的值。
對於從您的代碼(或您的代碼調用的庫代碼)拋出的錯誤,狀態將為 500,消息將為 "Internal Error"。雖然 error.message 可能包含不應暴露給用户的敏感信息,但 message 是安全的(儘管對普通用户來説沒有意義)。
要以類型安全的方式向 $page.error 對象添加更多信息,您可以通過聲明 App.Error 接口(必須包含 message: string,以保證合理的回退行為)來自定義預期的形狀。這允許您 — 例如 — 附加一個跟蹤 ID,供用户在與技術支持人員通信時引用:
/// file: src/app.d.ts
declare global {
namespace App {
interface Error {
message: string;
errorId: string;
}
}
}
export {};
/// file: src/hooks.server.js
// @errors: 2322 2353
// @filename: ambient.d.ts
declare module '@sentry/sveltekit' {
export const init: (opts: any) => void;
export const captureException: (error: any, opts: any) => void;
}
// @filename: index.js
// ---cut---
import * as Sentry from '@sentry/sveltekit';
Sentry.init({/*...*/})
/** @type {import('@sveltejs/kit').HandleServerError} */
export async function handleError({ error, event, status, message }) {
const errorId = crypto.randomUUID();
// 與 https://sentry.io/ 集成的示例
Sentry.captureException(error, {
extra: { event, errorId, status }
});
return {
message: '哎呀!',
errorId
};
}
/// file: src/hooks.client.js
// @errors: 2322 2353
// @filename: ambient.d.ts
declare module '@sentry/sveltekit' {
export const init: (opts: any) => void;
export const captureException: (error: any, opts: any) => void;
}
// @filename: index.js
// ---cut---
import * as Sentry from '@sentry/sveltekit';
Sentry.init({/*...*/})
/** @type {import('@sveltejs/kit').HandleClientError} */
export async function handleError({ error, event, status, message }) {
const errorId = crypto.randomUUID();
// 與 https://sentry.io/ 集成的示例
Sentry.captureException(error, {
extra: { event, errorId, status }
});
return {
message: '哎呀!',
errorId
};
}
[!NOTE] 在src/hooks.client.js中,handleError的類型是HandleClientError而不是HandleServerError,並且event是一個NavigationEvent而不是RequestEvent。
此函數不會因為預期的錯誤(那些使用從 @sveltejs/kit 導入的 error 函數拋出的錯誤)而被調用。
在開發過程中,如果由於 Svelte 代碼中的語法錯誤而發生錯誤,傳入的錯誤會附加一個 frame 屬性,突出顯示錯誤的位置。
[!NOTE] 確保 handleError 永遠不會拋出錯誤
init
這個函數在服務端創建或應用程序在瀏覽器中啓動時運行一次,是執行異步工作(如初始化數據庫連接)的有用位置。
[!NOTE] 如果您的環境支持頂級 await,init 函數實際上與在模塊頂層編寫初始化邏輯沒有什麼不同,但一些環境 — 尤其是 Safari — 不支持。
/// file: src/hooks.server.js
import * as db from '$lib/server/database';
/** @type {import('@sveltejs/kit').ServerInit} */
export async function init() {
await db.connect();
}
[!NOTE]
在瀏覽器中,init中的異步工作會延遲水合,所以要注意您在那裏放什麼。
通用 hook
以下 hook 可以添加到 src/hooks.js 中。通用 hook 在服務端和客户端都運行(不要與共享 hook 混淆,後者是特定環境的)。
reroute
這個函數在 handle 之前運行,允許您更改 URL 如何轉換為路由。返回的路徑名(默認為 url.pathname)用於選擇路由及其參數。
例如,您可能有一個 src/routes/[[lang]]/about/+page.svelte 頁面,它應該可以訪問為 /en/about 或 /de/ueber-uns 或 /fr/a-propos。您可以用 reroute 來實現:
/// file: src/hooks.js
// @errors: 2345
// @errors: 2304
/** @type {Record<string, string>} */
const translated = {
'/en/about': '/en/about',
'/de/ueber-uns': '/de/about',
'/fr/a-propos': '/fr/about'
};
/** @type {import('@sveltejs/kit').Reroute} */
export function reroute({ url }) {
if (url.pathname in translated) {
return translated[url.pathname];
}
}
lang 參數將從返回的路徑名正確派生。
使用 reroute 不會改變瀏覽器地址欄的內容,也不會改變 event.url 的值。
傳輸
這是一組 傳輸器,允許您跨服務端/客户端邊界傳遞自定義類型 - 從 load 和 form actions 返回的類型。每個傳輸器都包含一個 encode 函數,該函數對服務端上的值進行編碼(或對任何不是該類型的實例返回 false),以及一個相應的 decode 函數:
/// file: src/hooks.js
import { Vector } from '$lib/math';
/** @type {import('@sveltejs/kit').Transport} */
export const transport = {
Vector: {
encode: (value) => value instanceof Vector && [value.x, value.y],
decode: ([x, y]) => new Vector(x, y)
}
};
進一步閲讀
- 教程: Hooks
Svelte 中文文檔
點擊查看中文文檔:SvelteKit 高級路由
系統學習 Svelte,歡迎入手小冊《Svelte 開發指南》。語法篇、實戰篇、原理篇三大篇章帶你係統掌握 Svelte!
此外我還寫過 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答讀者問等 14 個系列文章, 全系列文章目錄:https://github.com/mqyqingfeng/Blog
歡迎圍觀我的“網頁版朋友圈”、加入“冴羽·成長陪伴社羣”,踏上“前端大佬成長之路”。