跳至內容

SSG 輔助工具

SSG 輔助工具會從您的 Hono 應用程式產生靜態網站。它會擷取已註冊路由的內容,並將其儲存為靜態檔案。

用法

手動

如果您有一個如下的簡單 Hono 應用程式

tsx
// 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,建立如下的建置腳本

ts
// build.ts
import app from './index'
import { toSSG } from 'hono/ssg'
import fs from 'fs/promises'

toSSG(app, fs)

透過執行腳本,檔案將會輸出如下

bash
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 中指定。

ts
export interface ToSSGInterface {
  (
    app: Hono,
    fsModule: FileSystemModule,
    options?: ToSSGOptions
  ): Promise<ToSSGResult>
}
  • app 指定具有已註冊路由的 new Hono()
  • fs 指定以下物件,假設為 node:fs/promise
ts
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

ts
import { toSSG } from 'hono/deno'

toSSG(app) // The second argument is an option typed `ToSSGOptions`.

對於 Bun

ts
import { toSSG } from 'hono/bun'

toSSG(app) // The second argument is an option typed `ToSSGOptions`.

選項

選項在 ToSSGOptions 介面中指定。

ts
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 型別中傳回結果。

ts
export interface ToSSGResult {
  success: boolean
  files: string[]
  error?: Error
}

Hook

您可以透過在選項中指定以下自訂 Hook 來客製化 toSSG 的流程。

ts
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

ts
toSSG(app, fs, {
  beforeRequestHook: (req) => {
    if (req.method === 'GET') {
      return req
    }
    return false
  },
})

例如,如果您只想在 StatusCode 為 200 或 500 時輸出,請在 afterResponseHook 中篩選 res.status

ts
toSSG(app, fs, {
  afterResponseHook: (res) => {
    if (res.status === 200 || res.status === 500) {
      return res
    }
    return false
  },
})

AfterGenerateHook

如果您想掛鉤 toSSG 的結果,請使用 afterGenerateHook

ts
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 選項。

ts
import { toSSG, defaultExtensionMap } from 'hono/ssg'

// Save `application/x-html` content with `.html`
toSSG(app, fs, {
  extensionMap: {
    'application/x-html': 'html',
    ...defaultExtensionMap,
  },
})

請注意,以斜線結尾的路徑無論副檔名為何,都會儲存為 index.ext。

ts
// 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。

範例

ts
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 的靜態檔案產生中排除。

ts
app.get('/api', disableSSG(), (c) => c.text('an-api'))

onlySSG

設定了 onlySSG 中介層的路由,在 toSSG 執行後將會被 c.notFound() 覆寫。

ts
app.get('/static-page', onlySSG(), (c) => c.html(<h1>Welcome to my site</h1>))

以 MIT 授權發布。