Utilitaire SSG
L’utilitaire SSG génère un site statique à partir de votre application Hono. Il récupère le contenu des routes enregistrées et l’enregistre sous forme de fichiers statiques.
Utilisation
Méthode manuelle
Si vous disposez d’une application Hono simple comme celle-ci :
// index.tsx
const app = new Hono()
app.get('/', (c) => c.html('Hello, World!'))
app.use('/about', async (c, next) => {
c.setRenderer((content) => {
return c.html(
<html>
<head />
<body>
<p>{content}</p>
</body>
</html>
)
})
await next()
})
app.get('/about', (c) => {
return c.render(
<>
<title>Hono SSG Page</title>Hello!
</>
)
})
export default appPour Node.js, créez un script de compilation comme suit :
// build.ts
import app from './index'
import { toSSG } from 'hono/ssg'
import fs from 'fs/promises'
toSSG(app, fs)L’exécution du script produit les fichiers suivants :
ls ./static
about.html index.htmlPlugin Vite
Le plugin Vite @hono/vite-ssg permet de gérer facilement ce processus.
Pour plus de détails, consultez :
https://github.com/honojs/vite-plugins/tree/main/packages/ssg
toSSG
toSSG est la fonction principale de génération des sites statiques. Elle prend une application et un module de système de fichiers comme arguments. Elle repose sur les éléments suivants :
Entrée
Les arguments de toSSG sont spécifiés dans ToSSGInterface.
export interface ToSSGInterface {
(
app: Hono,
fsModule: FileSystemModule,
options?: ToSSGOptions
): Promise<ToSSGResult>
}appdésignenew Hono()avec les routes enregistrées.fsdésigne l’objet suivant, en supposant l’utilisation denode:fs/promise.
export interface FileSystemModule {
writeFile(path: string, data: string | Uint8Array): Promise<void>
mkdir(
path: string,
options: { recursive: boolean }
): Promise<void | string>
}Utiliser les adaptateurs pour Deno et Bun
Pour utiliser SSG sur Deno ou Bun, les paquets @hono/deno et @hono/bun fournissent une fonction toSSG.
Pour Deno :
import { toSSG } from '@hono/deno'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.Pour Bun :
import { toSSG } from '@hono/bun'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.Options
Les options sont spécifiées dans l’interface ToSSGOptions.
export interface ToSSGOptions {
dir?: string
concurrency?: number
extensionMap?: Record<string, string>
plugins?: SSGPlugin[]
}direst le répertoire de destination des fichiers statiques. Sa valeur par défaut est./static.concurrencyest le nombre de fichiers à générer simultanément. Sa valeur par défaut est2.extensionMapest une table dont les clés sont des valeurs deContent-Typeet les valeurs des chaînes d’extension. Elle sert à déterminer l’extension du fichier généré.pluginsest un tableau de plugins SSG qui étendent les fonctionnalités du processus de génération du site statique.
Sortie
toSSG renvoie le résultat selon le type Result suivant.
export interface ToSSGResult {
success: boolean
files: string[]
error?: Error
}Génération de fichiers
Route et nom de fichier
Les règles suivantes s’appliquent aux informations des routes enregistrées et aux noms des fichiers générés. Avec la valeur par défaut ./static, le comportement est le suivant :
/->./static/index.html/path->./static/path.html/path/->./static/path/index.html
Extension de fichier
L’extension du fichier dépend du Content-Type renvoyé par chaque route. Par exemple, les réponses de c.html sont enregistrées en .html.
Pour personnaliser les extensions des fichiers, définissez l’option extensionMap.
import { toSSG, defaultExtensionMap } from 'hono/ssg'
// Save `application/x-html` content with `.html`
toSSG(app, fs, {
extensionMap: {
'application/x-html': 'html',
...defaultExtensionMap,
},
})Notez que les chemins se terminant par une barre oblique sont enregistrés sous le nom index.ext, quelle que soit l’extension.
// 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'))Middleware
Présentation des middlewares intégrés qui prennent en charge SSG.
ssgParams
Vous pouvez utiliser une API similaire à generateStaticParams de Next.js.
Exemple :
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>
)
}
)isSSGContext
isSSGContext est une fonction utilitaire qui renvoie true si l’application actuelle s’exécute dans le contexte SSG déclenché par toSSG.
app.get('/page', (c) => {
if (isSSGContext(c)) {
return c.text('This is generated by SSG')
}
return c.text('This is served dynamically')
})disableSSG
Les routes sur lesquelles le middleware disableSSG est défini sont exclues de la génération de fichiers statiques par toSSG.
app.get('/api', disableSSG(), (c) => c.text('an-api'))onlySSG
Les routes sur lesquelles le middleware onlySSG est défini sont remplacées par c.notFound() après l’exécution de toSSG.
app.get('/static-page', onlySSG(), (c) => c.html(<h1>Welcome to my site</h1>))Plugins
Les plugins permettent d’étendre les fonctionnalités du processus de génération du site statique. Ils utilisent des hooks pour personnaliser ce processus à différentes étapes.
Plugin par défaut
Par défaut, toSSG utilise defaultPlugin, qui ignore les réponses dont le statut n’est pas 200, comme les redirections, les erreurs ou les réponses 404. Cela évite de générer des fichiers pour les réponses qui n’indiquent pas une réussite.
import { toSSG, defaultPlugin } from 'hono/ssg'
// defaultPlugin is automatically applied when no plugins specified
toSSG(app, fs)
// Equivalent to:
toSSG(app, fs, { plugins: [defaultPlugin] })Si vous spécifiez des plugins personnalisés, defaultPlugin n’est pas inclus automatiquement. Pour conserver le comportement par défaut tout en ajoutant des plugins, incluez-le explicitement :
toSSG(app, fs, {
plugins: [defaultPlugin, myCustomPlugin],
})Plugin de redirection
redirectPlugin génère des pages HTML de redirection pour les routes qui renvoient des réponses de redirection HTTP (301, 302, 303, 307, 308). Le HTML généré inclut une balise <meta http-equiv="refresh"> et un lien canonique.
import { toSSG, redirectPlugin, defaultPlugin } from 'hono/ssg'
toSSG(app, fs, {
plugins: [redirectPlugin(), defaultPlugin()],
})Par exemple, si votre application contient :
app.get('/old', (c) => c.redirect('/new'))redirectPlugin génère un fichier HTML à l’emplacement /old.html, avec une redirection par meta refresh vers /new.
NOTE
Lorsque vous l’utilisez avec defaultPlugin, placez redirectPlugin avant defaultPlugin. Puisque defaultPlugin ignore les réponses dont le statut n’est pas 200, le placer en premier empêcherait redirectPlugin de traiter les réponses de redirection.
Types de hooks
Les plugins peuvent utiliser les hooks suivants pour personnaliser le processus toSSG :
export type BeforeRequestHook = (req: Request) => Request | false
export type AfterResponseHook = (res: Response) => Response | false
export type AfterGenerateHook = (
result: ToSSGResult
) => void | Promise<void>- BeforeRequestHook : Appelé avant le traitement de chaque requête. Renvoyez
falsepour ignorer la route. - AfterResponseHook : Appelé après la réception de chaque réponse. Renvoyez
falsepour ignorer la génération du fichier. - AfterGenerateHook : Appelé une fois l’ensemble du processus de génération terminé.
Interface des plugins
export interface SSGPlugin {
beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
afterResponseHook?: AfterResponseHook | AfterResponseHook[]
afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]
}Exemples de plugins simples
Filtrer uniquement les requêtes GET :
const getOnlyPlugin: SSGPlugin = {
beforeRequestHook: (req) => {
if (req.method === 'GET') {
return req
}
return false
},
}Filtrer par code de statut :
const statusFilterPlugin: SSGPlugin = {
afterResponseHook: (res) => {
if (res.status === 200 || res.status === 500) {
return res
}
return false
},
}Journaliser les fichiers générés :
const logFilesPlugin: SSGPlugin = {
afterGenerateHook: (result) => {
if (result.files) {
result.files.forEach((file) => console.log(file))
}
},
}Exemple de plugin avancé
Voici un exemple de plugin de sitemap qui génère un fichier sitemap.xml :
// plugins.ts
import fs from 'node:fs/promises'
import path from 'node:path'
import type { SSGPlugin } from 'hono/ssg'
import { DEFAULT_OUTPUT_DIR } from 'hono/ssg'
export const sitemapPlugin = (baseURL: string): SSGPlugin => {
return {
afterGenerateHook: (result, fsModule, options) => {
const outputDir = options?.dir ?? DEFAULT_OUTPUT_DIR
const filePath = path.join(outputDir, 'sitemap.xml')
const urls = result.files.map((file) =>
new URL(file, baseURL).toString()
)
const siteMapText = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map((url) => `<url><loc>${url}</loc></url>`).join('\n')}
</urlset>`
fsModule.writeFile(filePath, siteMapText)
},
}
}Application des plugins :
import app from './index'
import { toSSG } from 'hono/ssg'
import { sitemapPlugin } from './plugins'
toSSG(app, fs, {
plugins: [
getOnlyPlugin,
statusFilterPlugin,
logFilesPlugin,
sitemapPlugin('https://example.com'),
],
})