SvelteKit 最新中文文档教程(13)—— Hooks

冴羽
• 阅读 52

前言

Svelte,一个语法简洁、入门容易,面向未来的前端框架。

从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1

SvelteKit 最新中文文档教程(13)—— Hooks

Svelte 以其独特的编译时优化机制著称,具有轻量级高性能易上手等特性,非常适合构建轻量级 Web 项目

为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。

如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

Hooks

“Hooks” 是您声明的应用程序范围的函数,SvelteKit 会在响应特定事件时调用它们,让您能够对框架的行为进行更为精细的控制。

有三个 hook 文件,都是可选的:

  • src/hooks.server.js — 您的应用程序的服务端 hook
  • src/hooks.client.js — 您的应用程序的客户端 hook
  • src/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,因为它依赖于构建时的分析。预加载可以通过更早下载资源来提高性能,但如果不必要地下载太多内容也会适得其反。默认情况下,会预加载 jscss 文件。目前不会预加载 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

这个函数允许您修改(或替换)在服务端上运行的 loadaction 函数中发生的 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 实现会转发 cookieauthorization 头部。

对于跨源请求,如果请求 URL 属于应用程序的子域,则会包含 cookie — 例如,如果您的应用程序在 my-domain.com 上,而您的 API 在 api.my-domain.com 上,cookie 将包含在请求中。

如果您的应用程序和 API 在兄弟子域上 — 例如 www.my-domain.comapi.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.jssrc/hooks.client.js 中:

handleError

如果在加载或渲染期间抛出意外错误,此函数将被调用,并传入 erroreventstatus 代码和 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

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

点赞
收藏
评论区
推荐文章
冴羽 冴羽
1个月前
Svelte 最新中文文档教程(21)—— 自定义元素
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽 冴羽
1星期前
Svelte 最新中文文档教程(22)—— Svelte 5 迁移指南
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽 冴羽
1星期前
SvelteKit 最新中文文档教程(1)—— 入门指南
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽 冴羽
1星期前
SvelteKit 最新中文文档教程(2)—— 路由
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽 冴羽
1星期前
SvelteKit 最新中文文档教程(3)—— 数据加载
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽 冴羽
1星期前
SvelteKit 最新中文文档教程(4)—— 表单 actions
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽 冴羽
1星期前
SvelteKit 最新中文文档教程(5)—— 页面选项
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽 冴羽
1星期前
SvelteKit 最新中文文档教程(6)—— 状态管理
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽 冴羽
1星期前
SvelteKit 最新中文文档教程(7)—— 构建和部署
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽 冴羽
1星期前
SvelteKit 最新中文文档教程(8)—— 部署 Node 服务端
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽 冴羽
1星期前
SvelteKit 最新中文文档教程(9)—— 部署静态站点与单页应用
前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非
冴羽
冴羽
Lv1
男 · 淘宝 · 前端工程师
分享技术,也分享人生。GitHub 中国区 Top 30,掘金签约作者,掘金前端领域关注数 Top 1。至今写过 14 个系列、几百篇技术文章,全网千万阅读。
文章
66
粉丝
16
获赞
68