@finesoft/front 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -193,21 +193,18 @@ The adapter serves these static files directly. No controller runs at request ti
|
|
|
193
193
|
|
|
194
194
|
### Incremental Static Regeneration (ISR)
|
|
195
195
|
|
|
196
|
-
The bundled server (`createServer`) and the preview server (`vp preview`)
|
|
196
|
+
The bundled server (`createServer`) and the preview server (`vp preview`) also cache `prerender` routes at runtime: a route is rendered on its **first** request and the HTML kept in an in-memory LRU (`ISR_CACHE_MAX = 1000` entries, evicted least-recently-used). Subsequent requests serve the cached HTML without re-running the controller.
|
|
197
|
+
|
|
198
|
+
Mark routes `prerender` per route (`renderMode: "prerender"`) or per glob via the Vite plugin (config-level wins over route-level):
|
|
197
199
|
|
|
198
200
|
```ts
|
|
199
201
|
finesoftFrontViteConfig({
|
|
200
202
|
ssr: { entry: "src/ssr.ts" },
|
|
201
|
-
|
|
202
|
-
// routes that should regenerate on demand
|
|
203
|
-
routes: ["/blog/*"],
|
|
204
|
-
// cache TTL in seconds
|
|
205
|
-
ttl: 300,
|
|
206
|
-
},
|
|
203
|
+
renderModes: { "/blog/*": "prerender" },
|
|
207
204
|
});
|
|
208
205
|
```
|
|
209
206
|
|
|
210
|
-
The
|
|
207
|
+
The runtime cache has **no TTL and no background regeneration** — entries live until LRU-evicted or the process restarts. Time-based stale-while-revalidate is delegated to the CDN by the platform adapters (Netlify emits a real `stale-while-revalidate` header; Cloudflare a plain `max-age`; node/Vercel none). See [server & deployment](./09-server-and-deployment.md#isr-incremental-static-regeneration) for the full picture.
|
|
211
208
|
|
|
212
209
|
## `PrefetchedIntents` — the SSR → CSR bridge
|
|
213
210
|
|
|
@@ -22,7 +22,7 @@ export default defineConfig({
|
|
|
22
22
|
i18n: { messagesDir: "src/locales" },
|
|
23
23
|
proxies: [{ prefix: "/api", target: "https://upstream.example" }],
|
|
24
24
|
adapter: "auto",
|
|
25
|
-
|
|
25
|
+
renderModes: { "/blog/*": "prerender" },
|
|
26
26
|
}),
|
|
27
27
|
],
|
|
28
28
|
});
|
|
@@ -30,13 +30,13 @@ export default defineConfig({
|
|
|
30
30
|
|
|
31
31
|
### Options
|
|
32
32
|
|
|
33
|
-
| Option | Type
|
|
34
|
-
| ------------------ |
|
|
35
|
-
| `ssr.entry` | `string`
|
|
36
|
-
| `i18n.messagesDir` | `string`
|
|
37
|
-
| `proxies` | `ProxyRouteConfig[]`
|
|
38
|
-
| `adapter` | `"auto" \| "node" \| ...`
|
|
39
|
-
| `
|
|
33
|
+
| Option | Type | Notes |
|
|
34
|
+
| ------------------ | ---------------------------- | ------------------------------------------------------------------------------ |
|
|
35
|
+
| `ssr.entry` | `string` | Path to your SSR entry (default `src/ssr.ts`). |
|
|
36
|
+
| `i18n.messagesDir` | `string` | Folder with `{locale}.json` files (default off). |
|
|
37
|
+
| `proxies` | `ProxyRouteConfig[]` | Declarative API forwarding. See below. |
|
|
38
|
+
| `adapter` | `"auto" \| "node" \| ...` | Target platform. `"auto"` detects from env vars. |
|
|
39
|
+
| `renderModes` | `Record<string, RenderMode>` | Per-route render-mode override (glob keys); `"prerender"` enables ISR caching. |
|
|
40
40
|
|
|
41
41
|
### What it does
|
|
42
42
|
|
|
@@ -55,21 +55,20 @@ In build:
|
|
|
55
55
|
|
|
56
56
|
## `createServer` — the standalone Hono server
|
|
57
57
|
|
|
58
|
-
For Node deployments and tests, the framework exports
|
|
58
|
+
For Node deployments and tests, the framework exports an async factory. It loads `.env`, detects the runtime, builds the Hono app, registers proxies + your `setup` routes, mounts the SSR catch-all, and **starts listening** (port from config or `PORT`, default `3000`) — then returns `{ app, vite, runtime }`:
|
|
59
59
|
|
|
60
60
|
```ts
|
|
61
61
|
import { createServer } from "@finesoft/front";
|
|
62
62
|
|
|
63
|
-
const app = createServer({
|
|
64
|
-
|
|
63
|
+
const { app } = await createServer({
|
|
64
|
+
ssr: { ssrProductionModule: "./dist/server/ssr.js" }, // or ssrEntryPath in dev
|
|
65
65
|
proxies: [{ prefix: "/api", target: "https://upstream.example" }],
|
|
66
|
-
|
|
67
|
-
isr: { routes: ["/blog/*"], ttl: 300 },
|
|
66
|
+
port: 3000,
|
|
68
67
|
});
|
|
69
68
|
|
|
70
|
-
// app is
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
// `app` is the started Hono instance — export it for serverless runtimes whose
|
|
70
|
+
// adapter imports the fetch handler (Vercel / Cloudflare / Netlify).
|
|
71
|
+
export { app };
|
|
73
72
|
```
|
|
74
73
|
|
|
75
74
|
### What it includes
|
|
@@ -149,48 +148,58 @@ This works for most CI environments — Vercel / Cloudflare / Netlify all set th
|
|
|
149
148
|
|
|
150
149
|
## ISR (Incremental Static Regeneration)
|
|
151
150
|
|
|
151
|
+
Mark routes `prerender` — per route (`renderMode: "prerender"`) or per glob via the Vite plugin's `renderModes` (config-level wins over route-level):
|
|
152
|
+
|
|
152
153
|
```ts
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
154
|
+
finesoftFrontViteConfig({
|
|
155
|
+
ssr: { entry: "src/ssr.ts" },
|
|
156
|
+
renderModes: { "/blog/*": "prerender", "/products/*": "prerender" },
|
|
157
|
+
});
|
|
157
158
|
```
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
A `prerender` route is served two ways:
|
|
160
161
|
|
|
161
|
-
1.
|
|
162
|
-
2. Subsequent requests
|
|
163
|
-
3. After expiry: next request triggers re-render; concurrent requests get stale HTML until re-render finishes
|
|
162
|
+
1. **Build-time static** — the static adapter renders each prerender route at build and writes `dist/<route>.html` (one per locale when i18n is on). Served as plain static files; no controller runs at request time.
|
|
163
|
+
2. **Runtime cache** — the bundled server (`createServer`) and `vp preview` render a prerender route on its **first** request and store the HTML in an in-memory LRU (`ISR_CACHE_MAX = 1000` entries, evicted least-recently-used). Subsequent requests serve the cached HTML without re-running the controller.
|
|
164
164
|
|
|
165
|
-
The cache is
|
|
165
|
+
> **No TTL, no background regeneration.** The runtime cache has no time-based expiry and no stale-while-revalidate — an entry lives until it is LRU-evicted or the process restarts. The "regenerate after N seconds" semantics live at the **CDN**, not in the framework (below). There is no `isr` config option and no programmatic invalidation API.
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
### Stale-while-revalidate is delegated to the CDN
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
Platform adapters set cache headers on prerender responses so the edge does the real ISR:
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
| Adapter | Header on prerender responses |
|
|
172
|
+
| ------------------------- | ------------------------------------------------------------------------------------------ |
|
|
173
|
+
| Netlify | `Netlify-CDN-Cache-Control: max-age=3600, stale-while-revalidate=3600, durable` (true SWR) |
|
|
174
|
+
| Cloudflare | `Cache-Control: public, max-age=3600` |
|
|
175
|
+
| Node (self-host) / Vercel | none — relies on the in-memory LRU |
|
|
176
|
+
|
|
177
|
+
The `3600`s window is a hard-coded per-adapter constant, not user-configurable. For multi-instance / multi-region deployments the CDN headers are what give you consistent caching; the in-memory LRU is per-instance single-server serving.
|
|
178
|
+
|
|
179
|
+
### Cache invalidation
|
|
172
180
|
|
|
173
|
-
|
|
174
|
-
- Wait for TTL
|
|
175
|
-
- Add a cache-busting query param the controller can ignore but that bypasses the cache key
|
|
181
|
+
There is no programmatic invalidation API. To force a refresh:
|
|
176
182
|
|
|
177
|
-
|
|
183
|
+
- Restart the server (clears the entire in-memory LRU)
|
|
184
|
+
- Redeploy (rebuilds build-time static and resets caches)
|
|
185
|
+
- On Netlify / Cloudflare, purge the CDN cache for the path
|
|
178
186
|
|
|
179
187
|
## Custom Hono middleware
|
|
180
188
|
|
|
181
|
-
If you need server logic outside the proxy and SSR (e.g., a webhook endpoint, a health check),
|
|
189
|
+
If you need server logic outside the proxy and SSR (e.g., a webhook endpoint, a health check), register it via the `setup` hook — it runs after proxies but **before** the SSR catch-all, so your routes win:
|
|
182
190
|
|
|
183
191
|
```ts
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
app.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
await createServer({
|
|
193
|
+
ssr: { ssrProductionModule: "./dist/server/ssr.js" },
|
|
194
|
+
setup: (app) => {
|
|
195
|
+
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
196
|
+
app.post("/webhook", async (c) => {
|
|
197
|
+
const body = await c.req.json();
|
|
198
|
+
await handleWebhook(body);
|
|
199
|
+
return c.json({ ok: true });
|
|
200
|
+
});
|
|
201
|
+
},
|
|
191
202
|
});
|
|
192
|
-
|
|
193
|
-
// SSR catch-all is registered last by createServer — your routes win.
|
|
194
203
|
```
|
|
195
204
|
|
|
196
205
|
## Environment variables
|
|
@@ -215,25 +224,16 @@ framework.container.register("config", () => ({
|
|
|
215
224
|
For Node deployments behind a load balancer:
|
|
216
225
|
|
|
217
226
|
```ts
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
/* ... */
|
|
227
|
+
await createServer({
|
|
228
|
+
ssr: { ssrProductionModule: "./dist/server/ssr.js" },
|
|
229
|
+
setup: (app) => app.get("/health", (c) => c.json({ ok: true })),
|
|
222
230
|
});
|
|
223
|
-
app.get("/health", (c) => c.json({ ok: true }));
|
|
224
|
-
|
|
225
|
-
const server = serve({ fetch: app.fetch, port: 3000 });
|
|
226
231
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// dispose Framework if you held a reference
|
|
230
|
-
framework.dispose();
|
|
231
|
-
process.exit(0);
|
|
232
|
-
});
|
|
233
|
-
});
|
|
232
|
+
// createServer starts the listener itself — no manual serve() needed.
|
|
233
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
234
234
|
```
|
|
235
235
|
|
|
236
|
-
`framework.dispose()` recursively disposes the container, calls `destroy()` on
|
|
236
|
+
`createServer` does not return the underlying `http.Server`, so there's no built-in `server.close()` connection-drain. If you need graceful draining — or a handle to call `framework.dispose()` (recursively disposes the container, calls `destroy()` on recorders/loggers, unregisters routes) on shutdown — compose the lower level instead: build the Hono app and own framework yourself and `serve()` it so you keep both handles.
|
|
237
237
|
|
|
238
238
|
## Next
|
|
239
239
|
|
|
@@ -193,21 +193,18 @@ adapter 直接服务这些静态文件。请求时不跑 Controller。
|
|
|
193
193
|
|
|
194
194
|
### 增量静态再生成(ISR)
|
|
195
195
|
|
|
196
|
-
打包的服务器(`createServer`)和预览服务器(`vp preview
|
|
196
|
+
打包的服务器(`createServer`)和预览服务器(`vp preview`)会在运行时缓存 `prerender` 路由:路由在**首次**请求时渲染,HTML 存入内存 LRU(`ISR_CACHE_MAX = 1000` 条,按最近最少使用驱逐)。后续请求直接吐缓存、不再跑 controller。
|
|
197
|
+
|
|
198
|
+
把路由标 `prerender`:路由级(`renderMode: "prerender"`)或经 Vite 插件按 glob 配置(配置级优先于路由级):
|
|
197
199
|
|
|
198
200
|
```ts
|
|
199
201
|
finesoftFrontViteConfig({
|
|
200
202
|
ssr: { entry: "src/ssr.ts" },
|
|
201
|
-
|
|
202
|
-
// 哪些路由按需再生成
|
|
203
|
-
routes: ["/blog/*"],
|
|
204
|
-
// 缓存 TTL(秒)
|
|
205
|
-
ttl: 300,
|
|
206
|
-
},
|
|
203
|
+
renderModes: { "/blog/*": "prerender" },
|
|
207
204
|
});
|
|
208
205
|
```
|
|
209
206
|
|
|
210
|
-
|
|
207
|
+
运行时缓存**无 TTL、无后台再生成** —— 条目存活到被 LRU 驱逐或进程重启为止。基于时间的 stale-while-revalidate 由平台 adapter 委托给 CDN(Netlify 发真正的 `stale-while-revalidate` 头;Cloudflare 发普通 `max-age`;node/Vercel 不发)。完整说明见 [服务器与部署](./09-server-and-deployment.md#isr增量静态再生成)。
|
|
211
208
|
|
|
212
209
|
## `PrefetchedIntents` —— SSR → CSR 的桥梁
|
|
213
210
|
|
|
@@ -22,7 +22,7 @@ export default defineConfig({
|
|
|
22
22
|
i18n: { messagesDir: "src/locales" },
|
|
23
23
|
proxies: [{ prefix: "/api", target: "https://upstream.example" }],
|
|
24
24
|
adapter: "auto",
|
|
25
|
-
|
|
25
|
+
renderModes: { "/blog/*": "prerender" },
|
|
26
26
|
}),
|
|
27
27
|
],
|
|
28
28
|
});
|
|
@@ -30,13 +30,13 @@ export default defineConfig({
|
|
|
30
30
|
|
|
31
31
|
### 选项
|
|
32
32
|
|
|
33
|
-
| 选项 | 类型
|
|
34
|
-
| ------------------ |
|
|
35
|
-
| `ssr.entry` | `string`
|
|
36
|
-
| `i18n.messagesDir` | `string`
|
|
37
|
-
| `proxies` | `ProxyRouteConfig[]`
|
|
38
|
-
| `adapter` | `"auto" \| "node" \| ...`
|
|
39
|
-
| `
|
|
33
|
+
| 选项 | 类型 | 说明 |
|
|
34
|
+
| ------------------ | ---------------------------- | ------------------------------------------------------------ |
|
|
35
|
+
| `ssr.entry` | `string` | SSR 入口路径(默认 `src/ssr.ts`)。 |
|
|
36
|
+
| `i18n.messagesDir` | `string` | 含 `{locale}.json` 文件的目录(默认关闭)。 |
|
|
37
|
+
| `proxies` | `ProxyRouteConfig[]` | 声明式 API 转发。详见下文。 |
|
|
38
|
+
| `adapter` | `"auto" \| "node" \| ...` | 目标平台。`"auto"` 从环境变量检测。 |
|
|
39
|
+
| `renderModes` | `Record<string, RenderMode>` | 按路由覆盖渲染模式(glob 键);`"prerender"` 启用 ISR 缓存。 |
|
|
40
40
|
|
|
41
41
|
### 它做什么
|
|
42
42
|
|
|
@@ -55,21 +55,20 @@ Build:
|
|
|
55
55
|
|
|
56
56
|
## `createServer` —— 独立 Hono 服务器
|
|
57
57
|
|
|
58
|
-
Node
|
|
58
|
+
Node 部署和测试时,框架导出一个 async 工厂。它加载 `.env`、检测运行时、构建 Hono app、注册 proxies + 你的 `setup` 路由、挂 SSR catch-all,并**启动监听**(端口取自配置或 `PORT`,默认 `3000`)—— 然后返回 `{ app, vite, runtime }`:
|
|
59
59
|
|
|
60
60
|
```ts
|
|
61
61
|
import { createServer } from "@finesoft/front";
|
|
62
62
|
|
|
63
|
-
const app = createServer({
|
|
64
|
-
|
|
63
|
+
const { app } = await createServer({
|
|
64
|
+
ssr: { ssrProductionModule: "./dist/server/ssr.js" }, // dev 用 ssrEntryPath
|
|
65
65
|
proxies: [{ prefix: "/api", target: "https://upstream.example" }],
|
|
66
|
-
|
|
67
|
-
isr: { routes: ["/blog/*"], ttl: 300 },
|
|
66
|
+
port: 3000,
|
|
68
67
|
});
|
|
69
68
|
|
|
70
|
-
// app
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
// `app` 是已启动的 Hono 实例 —— 导出给 adapter 导入 fetch handler 的 serverless 运行时
|
|
70
|
+
// (Vercel / Cloudflare / Netlify)。
|
|
71
|
+
export { app };
|
|
73
72
|
```
|
|
74
73
|
|
|
75
74
|
### 包含什么
|
|
@@ -149,48 +148,58 @@ serverless 函数下,proxy 逻辑可以内联到部署的函数 bundle 而不
|
|
|
149
148
|
|
|
150
149
|
## ISR(增量静态再生成)
|
|
151
150
|
|
|
151
|
+
把路由标 `prerender`:路由级(`renderMode: "prerender"`)或经 Vite 插件按 glob 配置(配置级优先于路由级):
|
|
152
|
+
|
|
152
153
|
```ts
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
154
|
+
finesoftFrontViteConfig({
|
|
155
|
+
ssr: { entry: "src/ssr.ts" },
|
|
156
|
+
renderModes: { "/blog/*": "prerender", "/products/*": "prerender" },
|
|
157
|
+
});
|
|
157
158
|
```
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
`prerender` 路由有两种服务方式:
|
|
160
161
|
|
|
161
|
-
1.
|
|
162
|
-
2.
|
|
163
|
-
3. 过期后:下个请求触发重渲染;并发请求拿到陈旧 HTML 直到重渲染完成
|
|
162
|
+
1. **构建期静态化** —— static adapter 在 build 时渲染每个 prerender 路由、写 `dist/<route>.html`(开 i18n 时按 locale 各一份)。当作纯静态文件服务,请求时不跑 controller。
|
|
163
|
+
2. **运行时缓存** —— 打包服务器(`createServer`)和 `vp preview` 在路由**首次**请求时渲染、把 HTML 存入内存 LRU(`ISR_CACHE_MAX = 1000` 条,按最近最少使用驱逐)。后续请求直接吐缓存、不再跑 controller。
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
> **无 TTL、无后台再生成。** 运行时缓存没有基于时间的过期、也没有 stale-while-revalidate —— 条目存活到被 LRU 驱逐或进程重启。「N 秒后再生成」的语义在 **CDN**、不在框架(见下)。没有 `isr` 配置项,也没有程序化失效 API。
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
### stale-while-revalidate 委托给 CDN
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
平台 adapter 在 prerender 响应上设缓存头,让边缘做真正的 ISR:
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
| Adapter | prerender 响应的缓存头 |
|
|
172
|
+
| ---------------------- | ----------------------------------------------------------------------------------------- |
|
|
173
|
+
| Netlify | `Netlify-CDN-Cache-Control: max-age=3600, stale-while-revalidate=3600, durable`(真 SWR) |
|
|
174
|
+
| Cloudflare | `Cache-Control: public, max-age=3600` |
|
|
175
|
+
| Node(自托管)/ Vercel | 无 —— 依赖内存 LRU |
|
|
176
|
+
|
|
177
|
+
`3600` 秒窗口是各 adapter 里硬编码的常量,不可配。多实例 / 多区域部署靠 CDN 头保证一致缓存;内存 LRU 是单实例单服务器的服务。
|
|
178
|
+
|
|
179
|
+
### 缓存失效
|
|
172
180
|
|
|
173
|
-
|
|
174
|
-
- 等 TTL
|
|
175
|
-
- 加 Controller 能忽略但能绕过缓存 key 的 cache-bust query 参数
|
|
181
|
+
没有程序化失效 API。要强制刷新:
|
|
176
182
|
|
|
177
|
-
|
|
183
|
+
- 重启服务器(清空整个内存 LRU)
|
|
184
|
+
- 重新部署(重建构建期静态 + 重置缓存)
|
|
185
|
+
- 在 Netlify / Cloudflare 上 purge 该路径的 CDN 缓存
|
|
178
186
|
|
|
179
187
|
## 自定义 Hono 中间件
|
|
180
188
|
|
|
181
|
-
如果你需要 proxy 和 SSR 之外的服务端逻辑(如 webhook
|
|
189
|
+
如果你需要 proxy 和 SSR 之外的服务端逻辑(如 webhook、健康检查),通过 `setup` 钩子注册 —— 它在 proxies 之后、SSR catch-all **之前**跑,所以你的路由优先:
|
|
182
190
|
|
|
183
191
|
```ts
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
app.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
await createServer({
|
|
193
|
+
ssr: { ssrProductionModule: "./dist/server/ssr.js" },
|
|
194
|
+
setup: (app) => {
|
|
195
|
+
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
196
|
+
app.post("/webhook", async (c) => {
|
|
197
|
+
const body = await c.req.json();
|
|
198
|
+
await handleWebhook(body);
|
|
199
|
+
return c.json({ ok: true });
|
|
200
|
+
});
|
|
201
|
+
},
|
|
191
202
|
});
|
|
192
|
-
|
|
193
|
-
// SSR catch-all 由 createServer 最后注册 —— 你的路由优先。
|
|
194
203
|
```
|
|
195
204
|
|
|
196
205
|
## 环境变量
|
|
@@ -215,25 +224,16 @@ framework.container.register("config", () => ({
|
|
|
215
224
|
负载均衡器后的 Node 部署:
|
|
216
225
|
|
|
217
226
|
```ts
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
/* ... */
|
|
227
|
+
await createServer({
|
|
228
|
+
ssr: { ssrProductionModule: "./dist/server/ssr.js" },
|
|
229
|
+
setup: (app) => app.get("/health", (c) => c.json({ ok: true })),
|
|
222
230
|
});
|
|
223
|
-
app.get("/health", (c) => c.json({ ok: true }));
|
|
224
|
-
|
|
225
|
-
const server = serve({ fetch: app.fetch, port: 3000 });
|
|
226
231
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// 如果你拿着 Framework 引用,dispose 它
|
|
230
|
-
framework.dispose();
|
|
231
|
-
process.exit(0);
|
|
232
|
-
});
|
|
233
|
-
});
|
|
232
|
+
// createServer 自身启动监听 —— 不需要再手动 serve()。
|
|
233
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
234
234
|
```
|
|
235
235
|
|
|
236
|
-
`
|
|
236
|
+
`createServer` 不返回底层 `http.Server`,因此没有内建的 `server.close()` 连接 drain。若你需要优雅 drain —— 或需要句柄在关闭时调 `framework.dispose()`(递归 dispose 容器、对 recorder/logger 调 `destroy()`、注销路由)—— 改用更底层的搭建:自己建 Hono app 和 framework 并 `serve()`,从而同时握住两个句柄。
|
|
237
237
|
|
|
238
238
|
## 下一步
|
|
239
239
|
|