SSG 輔助工具
SSG 輔助工具會從您的 Hono 應用程式產生靜態網站。它會擷取已註冊路由的內容,並將其儲存為靜態檔案。
用法
手動
如果您有一個如下的簡單 Hono 應用程式
// index.tsx
const app = new Hono()
app.get('/', (c) => c.html('Hello, World!'))
app.use('/about', async (c, next) => {
c.setRenderer((content, head) => {
return c.html(
<html>
<head>
<title>{head.title ?? ''}</title>
</head>
<body>
<p>{content}</p>
</body>
</html>
)
})
await next()
})
app.get('/about', (c) => {
return c.render('Hello!', { title: 'Hono SSG Page' })
})
export default app
對於 Node.js,建立如下的建置腳本
// build.ts
import app from './index'
import { toSSG } from 'hono/ssg'
import fs from 'fs/promises'
toSSG(app, fs)
透過執行腳本,檔案將會輸出如下
ls ./static
about.html index.html
Vite 外掛程式
使用 @hono/vite-ssg
Vite 外掛程式,您可以輕鬆處理此過程。
如需更多詳細資訊,請參閱這裡
https://github.com/honojs/vite-plugins/tree/main/packages/ssg
toSSG
toSSG
是產生靜態網站的主要函式,它會接收應用程式和檔案系統模組作為參數。它基於以下內容:
輸入
toSSG 的參數在 ToSSGInterface 中指定。
export interface ToSSGInterface {
(
app: Hono,
fsModule: FileSystemModule,
options?: ToSSGOptions
): Promise<ToSSGResult>
}
app
指定具有已註冊路由的new Hono()
。fs
指定以下物件,假設為node:fs/promise
。
export interface FileSystemModule {
writeFile(path: string, data: string | Uint8Array): Promise<void>
mkdir(
path: string,
options: { recursive: boolean }
): Promise<void | string>
}
為 Deno 和 Bun 使用轉接器
如果您想在 Deno 或 Bun 上使用 SSG,則會為每個檔案系統提供一個 toSSG
函式。
對於 Deno
import { toSSG } from 'hono/deno'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.
對於 Bun
import { toSSG } from 'hono/bun'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.
選項
選項在 ToSSGOptions 介面中指定。
export interface ToSSGOptions {
dir?: string
concurrency?: number
beforeRequestHook?: BeforeRequestHook
afterResponseHook?: AfterResponseHook
afterGenerateHook?: AfterGenerateHook
extensionMap?: Record<string, string>
}
dir
是靜態檔案的輸出目的地。預設值為./static
。concurrency
是同時產生檔案的並行數量。預設值為2
。extensionMap
是包含Content-Type
作為鍵,以及副檔名字串作為值的對應。這用於判斷輸出檔案的副檔名。
每個 Hook 將在稍後描述。
輸出
toSSG
會在以下 Result 型別中傳回結果。
export interface ToSSGResult {
success: boolean
files: string[]
error?: Error
}
Hook
您可以透過在選項中指定以下自訂 Hook 來客製化 toSSG
的流程。
export type BeforeRequestHook = (req: Request) => Request | false
export type AfterResponseHook = (res: Response) => Response | false
export type AfterGenerateHook = (
result: ToSSGResult
) => void | Promise<void>
BeforeRequestHook/AfterResponseHook
toSSG
會針對應用程式中註冊的所有路由,但如果有些路由您想要排除,您可以透過指定 Hook 來篩選它們。
例如,如果您只想輸出 GET 請求,請在 beforeRequestHook
中篩選 req.method
。
toSSG(app, fs, {
beforeRequestHook: (req) => {
if (req.method === 'GET') {
return req
}
return false
},
})
例如,如果您只想在 StatusCode 為 200 或 500 時輸出,請在 afterResponseHook
中篩選 res.status
。
toSSG(app, fs, {
afterResponseHook: (res) => {
if (res.status === 200 || res.status === 500) {
return res
}
return false
},
})
AfterGenerateHook
如果您想掛鉤 toSSG
的結果,請使用 afterGenerateHook
。
toSSG(app, fs, {
afterGenerateHook: (result) => {
if (result.files) {
result.files.forEach((file) => console.log(file))
}
})
})
產生檔案
路由和檔案名稱
以下規則適用於已註冊的路由資訊和產生的檔案名稱。預設的 ./static
行為如下
/
->./static/index.html
/path
->./static/path.html
/path/
->./static/path/index.html
副檔名
副檔名取決於每個路由傳回的 Content-Type
。例如,來自 c.html
的回應會儲存為 .html
。
如果您想客製化副檔名,請設定 extensionMap
選項。
import { toSSG, defaultExtensionMap } from 'hono/ssg'
// Save `application/x-html` content with `.html`
toSSG(app, fs, {
extensionMap: {
'application/x-html': 'html',
...defaultExtensionMap,
},
})
請注意,以斜線結尾的路徑無論副檔名為何,都會儲存為 index.ext。
// save to ./static/html/index.html
app.get('/html/', (c) => c.html('html'))
// save to ./static/text/index.txt
app.get('/text/', (c) => c.text('text'))
中介層
介紹支援 SSG 的內建中介層。
ssgParams
您可以使用類似 Next.js 的 generateStaticParams
的 API。
範例
app.get(
'/shops/:id',
ssgParams(async () => {
const shops = await getShops()
return shops.map((shop) => ({ id: shop.id }))
}),
async (c) => {
const shop = await getShop(c.req.param('id'))
if (!shop) {
return c.notFound()
}
return c.render(
<div>
<h1>{shop.name}</h1>
</div>
)
}
)
disableSSG
設定了 disableSSG
中介層的路由會從 toSSG
的靜態檔案產生中排除。
app.get('/api', disableSSG(), (c) => c.text('an-api'))
onlySSG
設定了 onlySSG
中介層的路由,在 toSSG
執行後將會被 c.notFound()
覆寫。
app.get('/static-page', onlySSG(), (c) => c.html(<h1>Welcome to my site</h1>))