前言
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)
}
};
进一步阅读
Svelte 中文文档
点击查看中文文档:SvelteKit 高级路由
系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!
此外我还写过 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog
欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。