diff --git a/packages/document/main-doc/docs/en/configure/app/usage.mdx b/packages/document/main-doc/docs/en/configure/app/usage.mdx index 219a1370ce05..77b9d3fcc331 100644 --- a/packages/document/main-doc/docs/en/configure/app/usage.mdx +++ b/packages/document/main-doc/docs/en/configure/app/usage.mdx @@ -13,7 +13,7 @@ Modern.js does not support configuring the same configuration item in both `pack **Runtime configuration** can be configured in the `src/modern.runtime.(ts|js|mjs)` file. -**Server Runtime configuration** can be configured in the `modern.server-runtime.config.(ts|js|mjs)` file in the root path. +**Server Runtime configuration** can be configured in the `server/modern.server.(ts|js|mjs)` file. ## Compile Configuration diff --git a/packages/document/main-doc/docs/en/guides/advanced-features/_meta.json b/packages/document/main-doc/docs/en/guides/advanced-features/_meta.json index 59325b2c4600..3ce0c342a833 100644 --- a/packages/document/main-doc/docs/en/guides/advanced-features/_meta.json +++ b/packages/document/main-doc/docs/en/guides/advanced-features/_meta.json @@ -23,5 +23,6 @@ "label": "server-monitor", "collapsed": true }, + "custom-server", "web-server" ] diff --git a/packages/document/main-doc/docs/en/guides/advanced-features/custom-server.mdx b/packages/document/main-doc/docs/en/guides/advanced-features/custom-server.mdx new file mode 100644 index 000000000000..3a2c9e1a1fb3 --- /dev/null +++ b/packages/document/main-doc/docs/en/guides/advanced-features/custom-server.mdx @@ -0,0 +1,218 @@ +--- +sidebar_position: 16 +--- + +# Custom Server + +Modern.js encapsulates most server-side capabilities required by projects, typically eliminating the need for server-side development. However, in certain scenarios such as user authentication, request preprocessing, or adding page skeletons, custom server-side logic may still be necessary. + +## Custom Server Capabilities + +Create the `server/modern.server.ts` file in the project directory, and you can add the following configurations to extend the Server: +- **Middleware** +- **Render Middleware** +- **Server Plugin** + +In the **Plugin**, you can define **Middleware** and **RenderMiddleware**. The middleware loading process is illustrated in the following diagram: + + + +### Basic Configuration + +```ts title="server/modern.server.ts" +import { defineServerConfig } from '@modern-js/server-runtime'; + +export default defineServerConfig({ + middlewares: [], + renderMiddlewares: [], + plugins: [], +}); +``` + + +### Type Definition + +`defineServerConfig` type definition is as follows: + +```ts +import type { MiddlewareHandler } from 'hono'; + +type MiddlewareOrder = 'pre' | 'post' | 'default'; +type MiddlewareObj = { + name: string; + path?: string; + method?: 'options' | 'get' | 'post' | 'put' | 'delete' | 'patch' | 'all'; + handler: MiddlewareHandler | MiddlewareHandler[]; + before?: Array; + order?: MiddlewareOrder; +}; +type ServerConfig = { + middlewares?: MiddlewareObj[]; + renderMiddlewares?: MiddlewareObj[]; + plugins?: (ServerPlugin | ServerPluginLegacy)[]; +} +``` + + +### Middleware + +Middleware supports executing custom logic before and after the **request handling** and **page routing** processes in Modern.js services. +That is, if custom logic needs to handle both API routes and affect page routes, then Middleware is the obvious choice. If you only need to handle BFF API routes, this can be achieved by configuring the `path` to the BFF's `prefix`. + +:::note +In the BFF scenario, BFF routing will only go through Middleware when the [runtime framework](/guides/advanced-features/bff/frameworks.html) is Hono. +::: + +#### Using Posture + +```ts title="server/modern.server.ts" +import { defineServerConfig, type MiddlewareHandler } from '@modern-js/server-runtime'; +import { getMonitors } from '@modern-js/runtime'; + +export const handler: MiddlewareHandler = async (c, next) => { + const monitors = getMonitors(); + const start = Date.now(); + + await next(); + + const end = Date.now(); + // Report Duration + monitors.timing('request_timing', end - start); +}; + +export default defineServerConfig({ + middlewares: [ + { + name: 'request-timing', + handler, + }, + ], +}); +``` + +:::warning +You must execute the `next` function to proceed with the subsequent Middleware. +::: + + +### RenderMiddleware + +If you only need to handle the logic before and after page rendering, modern.js also provides rendering middleware. + +#### Using Posture + +```ts title="server/modern.server.ts" +import { defineServerConfig, type MiddlewareHandler } from '@modern-js/server-runtime'; + +// Inject render performance metrics +const renderTiming: MiddlewareHandler = async (c, next) => { + const start = Date.now(); + + await next(); + + const end = Date.now(); + c.res.headers.set('server-timing', `render; dur=${end - start}`); +}; + +// Modify the Response Body +const modifyResBody: MiddlewareHandler = async (c, next) => { + await next(); + + const { res } = c; + const text = await res.text(); + const newText = text.replace('', '

bytedance

'); + + c.res = c.body(newText, { + status: res.status, + headers: res.headers, + }); +}; + +export default defineServerConfig({ + renderMiddlewares: [ + { + name: 'render-timing', + handler: renderTiming, + }, + { + name: 'modify-res-body', + handler: modifyResBody, + }, + ], +}); +``` + + +### Plugin + +Modern.js supports adding the aforementioned middleware and rendering middleware for the Server in custom plugins. + +#### Using Posture + + +```ts title="server/plugins/server.ts" +import type { ServerPluginLegacy } from '@modern-js/server-runtime'; + +export default (): ServerPluginLegacy => ({ + name: 'serverPlugin', + setup(api) { + return { + prepare(serverConfig) { + const { middlewares, renderMiddlewares } = api.useAppContext(); + + // Inject server-side data for page dataLoader consumption + middlewares?.push({ + name: 'server-plugin-middleware', + handler: async (c, next) => { + c.set('message', 'hi modern.js'); + await next(); + // ... + }, + }); + + // redirect + renderMiddlewares?.push({ + name: 'server-plugin-render-middleware', + handler: async (c, next) => { + const user = getUser(c.req); + if (!user) { + return c.redirect('/login'); + } + + await next(); + }, + }); + return serverConfig; + }, + }; + }, +}); +``` + + +```ts title="server/modern.server.ts" +import { defineServerConfig } from '@modern-js/server-runtime'; +import serverPlugin from './plugins/serverPlugin'; + +export default defineServerConfig({ + plugins: [serverPlugin()], +}); +``` + + +```ts title="src/routes/page.data.ts" +import { useHonoContext } from '@modern-js/server-runtime'; +import { defer } from '@modern-js/runtime/router'; + +export default () => { + const ctx = useHonoContext(); + // Consuming Data Injected by the Server-Side + const message = ctx.get('message'); + + // ... +}; + +``` diff --git a/packages/document/main-doc/docs/en/guides/advanced-features/web-server.mdx b/packages/document/main-doc/docs/en/guides/advanced-features/web-server.mdx index faf0e487c5de..cd6fb10b7b7c 100644 --- a/packages/document/main-doc/docs/en/guides/advanced-features/web-server.mdx +++ b/packages/document/main-doc/docs/en/guides/advanced-features/web-server.mdx @@ -2,7 +2,11 @@ sidebar_position: 16 --- -# Custom Web Server +# Custom Web Server (Not Recommended) + +:::warning +Custom Web Server is compatible but no longer recommended. For extending Server capabilities, please refer to [Custom Server](/guides/advanced-features/custom-server.html). For migration guide, see [Migrate to the New Version of Custom Server](/guides/advanced-features/web-server.html#migrate-to-the-new-version-of-custom-server). +::: Modern.js encapsulates most server-side capabilities required by projects, typically eliminating the need for server-side development. However, in certain scenarios such as user authentication, request preprocessing, or adding page skeletons, custom server-side logic may still be necessary. @@ -96,3 +100,172 @@ Best practices when using Hooks: :::info For detailed API and more usage, see [Hook](/apis/app/runtime/web-server/hook). ::: + + +## Migrate to the New Version of Custom Server + +### Migration Background + +Modern.js Server is continuously evolving to provide more powerful features. We have optimized the definition and usage of middleware and Server plugins. +While the old custom Web Server approach is still compatible, we strongly recommend migrating according to this guide to fully leverage the advantages of the new version. + +### Migration Steps + +1. Upgrade Modern.js version to x.67.5 or above. +2. Configure middleware or plugins in `server/modern.server.ts` according to the new definition method. +3. Migrate the custom logic in `server/index.ts` to middleware or plugins, and update your code with reference to the differences between `Context` and `Next`. + +### Context Differences + +In the new version, the middleware handler type is Hono's `MiddlewareHandler`, meaning the `Context` type is `Hono Context`. The differences from the old custom Web Server's `Context` are as follows: + + +#### UnstableMiddleware + + +```ts +type Body = ReadableStream | ArrayBuffer | string | null; + +type UnstableMiddlewareContext< + V extends Record = Record, +> = { + request: Request; + response: Response; + get: Get; + set: Set; + // Current Matched Routing Information + route: string; + header: (name: string, value: string, options?: { append?: boolean }) => void; + status: (code: number) => void; + redirect: (location: string, status?: number) => Response; + body: (data: Body, init?: ResponseInit) => Response; + html: ( + data: string | Promise, + init?: ResponseInit, + ) => Response | Promise; +}; +``` + +Differences between UnstableMiddleware Context and Hono Context: + +| UnstableMiddleware | Hono | Description | +| :----------------------- | :---------------------------- | :------------------------------------------------------------------------ | +| `c.request` | `c.req.raw` | Refer to [HonoRequest raw](https://hono.dev/docs/api/request#raw) documentation | +| `c.response` | `c.res` | Refer to [Hono Context res](https://hono.dev/docs/api/context#res) documentation | +| `c.route` | `c.get('route')` | Get application context information. | +| `loaderContext.get` | `honoContext.get` | After injecting data using `c.set`, consume in dataLoader: the old version uses `loaderContext.get`, refer to the new version in [Plugin](/guides/advanced-features/custom-server.html#using-posture-2) example | + + +#### Middleware + +```ts +type MiddlewareContext = { + response: { + set: (key: string, value: string) => void; + status: (code: number) => void; + getStatus: () => number; + cookies: { + set: (key: string, value: string, options?: any) => void; + clear: () => void; + }; + raw: ( + body: string, + { status, headers }: { status: number; headers: Record }, + ) => void; + locals: Record; + }; + request: { + url: string; + host: string; + pathname: string; + query: Record; + cookie: string; + cookies: { + get: (key: string) => string; + }; + headers: IncomingHttpHeaders; + }; + source: { + req: IncomingMessage; + res: ServerResponse; + }; +}; + +``` + +Differences between Middleware `Context` and Hono `Context`: +| UnstableMiddleware | Hono | Description | +| :----------------------- | :---------------------------- | :--------------------------------------------------------------------------- | +| `c.request.cookie` | `c.req.cookie()` | Refer to [Hono Cookie Helper](https://hono.dev/docs/helpers/cookie) documentation | +| `c.request.pathname` | `c.req.path` | Refer to [HonoRequest path](https://hono.dev/docs/api/request#path) documentation | +| `c.request.url` | - | Hono `c.req.url` provides the full request URL, calculate manually from URL | +| `c.request.host` | `c.req.header('Host')` | Obtain host through header | +| `c.request.query` | `c.req.query()` | Refer to [HonoRequest query](https://hono.dev/docs/api/request#query) documentation | +| `c.request.headers` | `c.req.header()` | Refer to [HonoRequest header](https://hono.dev/docs/api/request#header) documentation | +| `c.response.set` | `c.res.headers.set` | Example: `c.res.headers.set('custom-header', '1')` | +| `c.response.status` | `c.status` | Example: `c.status(201)` | +| `c.response.cookies` | `c.header` | Example: `c.header('Set-Cookie', 'user_id=123')` | +| `c.response.raw` | `c.res` | Refer to [Hono Context res](https://hono.dev/docs/api/context#res) documentation | + +#### Hook + +```ts +type HookContext = { + response: { + set: (key: string, value: string) => void; + status: (code: number) => void; + getStatus: () => number; + cookies: { + set: (key: string, value: string, options?: any) => void; + clear: () => void; + }; + raw: ( + body: string, + { status, headers }: { status: number; headers: Record }, + ) => void; + }; + request: { + url: string; + host: string; + pathname: string; + query: Record; + cookie: string; + cookies: { + get: (key: string) => string; + }; + headers: IncomingHttpHeaders; + }; +}; + +type AfterMatchContext = HookContext & { + router: { + redirect: (url: string, status: number) => void; + rewrite: (entry: string) => void; + }; +}; + +type AfterRenderContext = { + template: { + get: () => string; + set: (html: string) => void; + prependHead: (fragment: string) => void; + appendHead: (fragment: string) => void; + prependBody: (fragment: string) => void; + appendBody: (fragment: string) => void; + }; +}; +``` + +Hook Context is mostly consistent with Middleware Context, so we need to pay extra attention to the additional parts of different Hooks. + +| UnstableMiddleware | Hono | Description | +| :----------------------- | :---------------------------- | :------------------------------------ | +| `router.redirect` | `c.redirect` | Refer to [Hono Context redirect](https://hono.dev/docs/api/context#redirect) documentation | +| `router.rewrite` | - | No corresponding capability provided at the moment | +| template API | `c.res` | Refer to [Hono Context res](https://hono.dev/docs/api/context#res) documentation | + + +### Differences in Next API + +In Middleware and Hooks, the render function executes even without invoking `next`. +In the new design, subsequent Middleware will only execute if the `next` function is invoked. diff --git a/packages/document/main-doc/docs/zh/configure/app/usage.mdx b/packages/document/main-doc/docs/zh/configure/app/usage.mdx index 4173bd8f6bcf..312cb00fb615 100644 --- a/packages/document/main-doc/docs/zh/configure/app/usage.mdx +++ b/packages/document/main-doc/docs/zh/configure/app/usage.mdx @@ -13,7 +13,7 @@ Modern.js 不支持同时在 `package.json` 中和 `modern.config.ts` 中配置 **运行时配置**可以在 `src/modern.runtime.(ts|js|mjs)` 文件中配置。 -**服务端运行时配置**可以在根路径下的 `modern.server-runtime.config.(ts|js|mjs)` 中进行配置。 +**服务端运行时配置**可以在 `server/modern.server.(ts|js|mjs)` 中进行配置。 ## 编译时配置 diff --git a/packages/document/main-doc/docs/zh/guides/advanced-features/_meta.json b/packages/document/main-doc/docs/zh/guides/advanced-features/_meta.json index 59325b2c4600..3ce0c342a833 100644 --- a/packages/document/main-doc/docs/zh/guides/advanced-features/_meta.json +++ b/packages/document/main-doc/docs/zh/guides/advanced-features/_meta.json @@ -23,5 +23,6 @@ "label": "server-monitor", "collapsed": true }, + "custom-server", "web-server" ] diff --git a/packages/document/main-doc/docs/zh/guides/advanced-features/custom-server.mdx b/packages/document/main-doc/docs/zh/guides/advanced-features/custom-server.mdx new file mode 100644 index 000000000000..ff3ee498fbc3 --- /dev/null +++ b/packages/document/main-doc/docs/zh/guides/advanced-features/custom-server.mdx @@ -0,0 +1,216 @@ +--- +sidebar_position: 16 +--- + +# 自定义 Server + +Modern.js 将大部分项目需要的服务端能力都进行了封装,通常项目无需进行服务端开发。但在有些开发场景下,例如用户鉴权、请求预处理、添加页面渲染骨架等,项目仍需要对服务端进行定制。 + +## 自定义 Server 能力 + +项目目录下创建 `server/modern.server.ts` 文件,可以在这个文件中添加如下配置来扩展 Server: +- **中间件(Middleware)** +- **渲染中间件(RenderMiddleware)** +- **服务端插件(Plugin)** + + +其中 **Plugin** 中可以定义 **Middleware** 与 **RenderMiddleware**。 中间件加载流程如下图所示: + + + +### 基本配置 + +```ts title="server/modern.server.ts" +import { defineServerConfig } from '@modern-js/server-runtime'; + +export default defineServerConfig({ + middlewares: [], // 中间件 + renderMiddlewares: [], // 渲染中间件 + plugins: [], // 插件 +}); +``` + + +### 类型定义 + +`defineServerConfig` 类型定义如下: + +```ts +import type { MiddlewareHandler } from 'hono'; + +type MiddlewareObj = { + name: string; + path?: string; + method?: 'options' | 'get' | 'post' | 'put' | 'delete' | 'patch' | 'all'; + handler: MiddlewareHandler | MiddlewareHandler[]; +}; +type ServerConfig = { + middlewares?: MiddlewareObj[]; + renderMiddlewares?: MiddlewareObj[]; + plugins?: (ServerPlugin | ServerPluginLegacy)[]; +} +``` + + +### Middleware + +Middleware 支持在 Modern.js 服务的**请求处理**与**页面路由**的流程前后,执行自定义逻辑。 +即自定义逻辑既要处理接口路由,也要作用于页面路由,那么 Middleware 是不二选择。如果仅需要处理 BFF 接口路由,可以通过配置 `path` 为 BFF 的 `prefix` 来实现。 + +:::note +BFF 场景只有[运行时框架](/guides/advanced-features/bff/frameworks.html)为 Hono 时,BFF 路由才会经过 Middleware。 +::: + +#### 使用姿势 + +```ts title="server/modern.server.ts" +import { defineServerConfig, type MiddlewareHandler } from '@modern-js/server-runtime'; +import { getMonitors } from '@modern-js/runtime'; + +export const handler: MiddlewareHandler = async (c, next) => { + const monitors = getMonitors(); + const start = Date.now(); + + await next(); + + const end = Date.now(); + // 上报耗时 + monitors.timing('request_timing', end - start); +}; + +export default defineServerConfig({ + middlewares: [ + { + name: 'request-timing', + handler, + }, + ], +}); +``` + +:::warning +必须执行 `next` 函数才会执行后续的 Middleware。 +::: + + +### RenderMiddleware + +如果只需要处理页面渲染的前后执行逻辑,modern.js 也提供了渲染中间件。 + +#### 使用姿势 + +```ts title="server/modern.server.ts" +import { defineServerConfig, type MiddlewareHandler } from '@modern-js/server-runtime'; + +// 注入 render 性能指标 +const renderTiming: MiddlewareHandler = async (c, next) => { + const start = Date.now(); + + await next(); + + const end = Date.now(); + c.res.headers.set('server-timing', `render; dur=${end - start}`); +}; + +// 修改响应体 +const modifyResBody: MiddlewareHandler = async (c, next) => { + await next(); + + const { res } = c; + const text = await res.text(); + const newText = text.replace('', '

bytedance

'); + + c.res = c.body(newText, { + status: res.status, + headers: res.headers, + }); +}; + +export default defineServerConfig({ + renderMiddlewares: [ + { + name: 'render-timing', + handler: renderTiming, + }, + { + name: 'modify-res-body', + handler: modifyResBody, + }, + ], +}); +``` + + +### Plugin + +Modern.js 支持在自定义插件中为 Server 添加上述 Middleware 及 RenderMiddleware。 + +#### 使用姿势 + + +```ts title="server/plugins/server.ts" +import type { ServerPluginLegacy } from '@modern-js/server-runtime'; + +export default (): ServerPluginLegacy => ({ + name: 'serverPlugin', + setup(api) { + return { + prepare(serverConfig) { + const { middlewares, renderMiddlewares } = api.useAppContext(); + + // 注入服务端数据,供页面 dataLoader 消费 + middlewares?.push({ + name: 'server-plugin-middleware', + handler: async (c, next) => { + c.set('message', 'hi modern.js'); + await next(); + // ... + }, + }); + + // 重定向 + renderMiddlewares?.push({ + name: 'server-plugin-render-middleware', + handler: async (c, next) => { + const user = getUser(c.req); + if (!user) { + return c.redirect('/login'); + } + + await next(); + }, + }); + return serverConfig; + }, + }; + }, +}); +``` + + +```ts title="server/modern.server.ts" +import { defineServerConfig } from '@modern-js/server-runtime'; +import serverPlugin from './plugins/serverPlugin'; + +export default defineServerConfig({ + plugins: [serverPlugin()], +}); +``` + + +```ts title="src/routes/page.data.ts" +import { useHonoContext } from '@modern-js/server-runtime'; +import { defer } from '@modern-js/runtime/router'; + +export default () => { + const ctx = useHonoContext(); + // 消费服务端注入的数据 + const message = ctx.get('message'); + + // ... +}; + +``` diff --git a/packages/document/main-doc/docs/zh/guides/advanced-features/web-server.mdx b/packages/document/main-doc/docs/zh/guides/advanced-features/web-server.mdx index bedce303d66d..61fb14cada03 100644 --- a/packages/document/main-doc/docs/zh/guides/advanced-features/web-server.mdx +++ b/packages/document/main-doc/docs/zh/guides/advanced-features/web-server.mdx @@ -2,7 +2,11 @@ sidebar_position: 16 --- -# 自定义 Web Server +# 自定义 Web Server(不推荐) + +:::warning +自定义 Web Server 兼容但不再推荐使用,扩展 Server 能力请移步 [自定义 Server](/guides/advanced-features/custom-server.html),迁移指南参考 [迁移至新版自定义 Server](/guides/advanced-features/web-server.html#%E8%BF%81%E7%A7%BB%E8%87%B3%E6%96%B0%E7%89%88%E8%87%AA%E5%AE%9A%E4%B9%89-server)。 +::: Modern.js 将大部分项目需要的服务端能力都进行了封装,通常项目无需进行服务端开发。但在有些开发场景下,例如用户鉴权、请求预处理、添加页面渲染骨架等,项目仍需要对服务端进行定制。 @@ -96,3 +100,172 @@ export const afterRender: AfterRenderHook = (ctx, next) => { :::info 详细 API 和更多用法可以查看 [Hook](/apis/app/runtime/web-server/hook)。 ::: + + +## 迁移至新版自定义 Server + +### 迁移背景 + +Modern.js Server 在不断演进,为了提供更强大的功能,我们对中间件和 Server 插件的定义和使用方式进行了优化。 +虽然旧版自定义 Web Server 写法仍被兼容,但我们强烈建议您按照本指南进行迁移,以充分利用新版的优势。 + +### 迁移步骤 + +1. 升级 Modern.js 版本至 x.67.5 及以上。 +2. 按照新版定义方式,在 `server/modern.server.ts` 中配置中间件或插件。 +3. 将 `server/index.ts` 自定义逻辑迁移到中间件或插件中,并参考 `Context` 和 `Next` 差异,更新您的代码。 + +### Context 差异 + +新版中间件 handler 类型为 Hono 的 `MiddlewareHandler`,即 `Context` 类型为 `Hono Context`。对比旧版自定义 Web Server 中 `Context` 差异如下: + +#### UnstableMiddleware + + +```ts +type Body = ReadableStream | ArrayBuffer | string | null; + +type UnstableMiddlewareContext< + V extends Record = Record, +> = { + request: Request; + response: Response; + get: Get; + set: Set; + // 当前匹配到的路由信息 + route: string; + header: (name: string, value: string, options?: { append?: boolean }) => void; + status: (code: number) => void; + redirect: (location: string, status?: number) => Response; + body: (data: Body, init?: ResponseInit) => Response; + html: ( + data: string | Promise, + init?: ResponseInit, + ) => Response | Promise; +}; +``` + +UnstableMiddleware `Context` 和 Hono `Context` 的具体差异: + +| UnstableMiddleware | Hono | 说明 | +| :----------------------- | :---------------------------- | :------------------------------------------------------------------------ | +| `c.request` | `c.req.raw` | 参考 [HonoRequest raw](https://hono.dev/docs/api/request#raw) 文档 | +| `c.response` | `c.res` | 参考 [Hono Context res](https://hono.dev/docs/api/context#res) 文档 | +| `c.route` | `c.get('route')` | 获取应用上下文信息 | +| `loaderContext.get` | `honoContext.get` | 通过 `c.set` 注入数据后 dataLoader 中消费:旧版通过 `loaderContext.get` 获取,新版参考 [Plugin](/guides/advanced-features/custom-server.html#使用姿势-2) 示例 | + +#### Middleware + +```ts +type MiddlewareContext = { + response: { + set: (key: string, value: string) => void; + status: (code: number) => void; + getStatus: () => number; + cookies: { + set: (key: string, value: string, options?: any) => void; + clear: () => void; + }; + raw: ( + body: string, + { status, headers }: { status: number; headers: Record }, + ) => void; + locals: Record; + }; + request: { + url: string; + host: string; + pathname: string; + query: Record; + cookie: string; + cookies: { + get: (key: string) => string; + }; + headers: IncomingHttpHeaders; + }; + source: { + req: IncomingMessage; + res: ServerResponse; + }; +}; + +``` + +Middleware `Context` 和 Hono `Context` 的具体差异: + +| UnstableMiddleware | Hono | 说明 | +| :----------------------- | :---------------------------- | :--------------------------------------------------------------------------- | +| `c.request.cookie` | `c.req.cookie()` | 参考 [Hono Cookie Helper](https://hono.dev/docs/helpers/cookie) 文档 | +| `c.request.pathname` | `c.req.path` | 参考 [HonoRequest path](https://hono.dev/docs/api/request#path) 文档 | +| `c.request.url` | - | Hono `c.req.url` 为完整请求路径,自行通过 url 计算 | +| `c.request.host` | `c.req.header('Host')` | 通过 header 获取 host | +| `c.request.query` | `c.req.query()` | 参考 [HonoRequest query](https://hono.dev/docs/api/request#query) 文档 | +| `c.request.headers` | `c.req.header()` | 参考 [HonoRequest header](https://hono.dev/docs/api/request#header) 文档 | +| `c.response.set` | `c.res.headers.set` | 例:`c.res.headers.set('custom-header', '1')` | +| `c.response.status` | `c.status` | 例:`c.status(201)` | +| `c.response.cookies` | `c.header` | 例:`c.header('Set-Cookie', 'user_id=123')` | +| `c.response.raw` | `c.res` | 参考 [Hono Context res](https://hono.dev/docs/api/context#res) 文档 | + + +#### Hook + +```ts +type HookContext = { + response: { + set: (key: string, value: string) => void; + status: (code: number) => void; + getStatus: () => number; + cookies: { + set: (key: string, value: string, options?: any) => void; + clear: () => void; + }; + raw: ( + body: string, + { status, headers }: { status: number; headers: Record }, + ) => void; + }; + request: { + url: string; + host: string; + pathname: string; + query: Record; + cookie: string; + cookies: { + get: (key: string) => string; + }; + headers: IncomingHttpHeaders; + }; +}; + +type AfterMatchContext = HookContext & { + router: { + redirect: (url: string, status: number) => void; + rewrite: (entry: string) => void; + }; +}; + +type AfterRenderContext = { + template: { + get: () => string; + set: (html: string) => void; + prependHead: (fragment: string) => void; + appendHead: (fragment: string) => void; + prependBody: (fragment: string) => void; + appendBody: (fragment: string) => void; + }; +}; +``` + +Hook Context 大部分和 Middleware Context 一致,因此我们要额外关注不同 Hook 多余的部分。 + +| UnstableMiddleware | Hono | 说明 | +| :----------------------- | :---------------------------- | :----------------------------- | +| `router.redirect` | `c.redirect` | 参考 [Hono Context redirect](https://hono.dev/docs/api/context#redirect) 文档 | +| `router.rewrite` | - | 暂时没有提供对应的能力 | +| template API | `c.res` | 参考 [Hono Context res](https://hono.dev/docs/api/context#res) 文档 | + + +### Next API 差异 + +在 Middleware 和 Hook 中,即使不执行 `next`,渲染函数也会执行。 +在新的设计中,必须执行 `next` 函数才会执行后续的 Middleware。