مساعد SSG
ينشئ SSG Helper موقعًا ثابتًا من تطبيق Hono الخاص بك. سيسترجع محتويات المسارات المسجلة ويحفظها كملفات ثابتة.
الاستخدام
يدوي
إذا كان لديك تطبيق Hono بسيط كما يلي:
// 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(
<head />
<body>
<p>{content}</p>
</body>
)
})
await next()
})
app.get('/about', (c) => {
return c.render(
<>
<title>Hono SSG Page</title>Hello!
</>
)
})
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 Plugin، يمكنك التعامل مع العملية بسهولة.
لمزيد من التفاصيل، انظر هنا:
https://github.com/honojs/vite-plugins/tree/main/packages/ssg
toSSG
toSSG هي الدالة الرئيسية لتوليد المواقع الثابتة، حيث تأخذ تطبيقًا ووحدة نظام ملفات (filesystem) كمعاملات. وهي مبنية على ما يلي:
المدخلات
تُحدَّد وسائط (arguments) 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
إذا كنت تريد استخدام SSG على Deno أو Bun، فإن toSSG الدالة مُوفّرة من قِبل @hono/deno و @hono/bun الحزم.
لـ 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
extensionMap?: Record<string, string>
plugins?: SSGPlugin[]
}
dirهي وجهة الإخراج للملفات الثابتة (Static files). القيمة الافتراضية هي./static.concurrencyهو عدد الملفات المتزامنة التي سيتم توليدها في الوقت نفسه. القيمة الافتراضية هي2.extensionMapخريطة تحتوي علىContent-Typeكمفتاح وسلسلة الامتداد كقيمة. يُستخدم هذا لتحديد امتداد ملف الإخراج.pluginsهي مصفوفة من إضافات SSG التي توسع وظائف عملية توليد المواقع الثابتة.
المخرجات
toSSG يُعيد النتيجة في نوع Result التالي.
export interface ToSSGResult {
success: boolean
files: string[]
error?: Error
}
إنشاء ملف
المسار واسم الملف
تنطبق القواعد التالية على معلومات المسار المسجلة واسم الملف المُنشأ. الافتراضي ./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
يمكنك استخدام واجهة API مثل generateStaticParams الخاص بـ Next.js.
مثال:
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 دالة مساعدة تُرجع true إذا كان التطبيق الحالي يعمل ضمن سياق SSG المُفعَّل بواسطة toSSG.
app.get('/page', (c) => {
if (isSSGContext(c)) {
return c.text('This is generated by SSG')
}
return c.text('This is served dynamically')
})
disableSSG
المسارات ذات disableSSG مجموعة الوسيطات (middleware) مُستثناة من توليد الملفات الثابتة بواسطة toSSG.
app.get('/api', disableSSG(), (c) => c.text('an-api'))
onlySSG
المسارات ذات onlySSG سيُتجاوَز طقم البرمجيات الوسيطة المضبوط بواسطة c.notFound() بعد toSSG التنفيذ.
app.get('/static-page', onlySSG(), (c) => c.html(<h1>Welcome to my site</h1>))
الإضافات
تسمح لك الإضافات (Plugins) بتوسيع وظائف عملية توليد المواقع الثابتة. تستخدم خطافات (hooks) لتخصيص عملية التوليد في مراحل مختلفة.
الإضافة الافتراضية
بشكل افتراضي، toSSG يستخدم defaultPlugin الذي يتخطى استجابات الحالة غير 200 (مثل عمليات إعادة التوجيه أو الأخطاء أو 404). يمنع هذا إنشاء ملفات للاستجابات غير الناجحة.
import { toSSG, defaultPlugin } from 'hono/ssg'
// defaultPlugin is automatically applied when no plugins specified
toSSG(app, fs)
// Equivalent to:
toSSG(app, fs, { plugins: [defaultPlugin] })
إذا حدّدت إضافات (plugins) مخصصة، defaultPlugin لا يتم تضمينه تلقائيًا. للاحتفاظ بالسلوك الافتراضي مع إضافة إضافات (plugins) مخصصة، قم بتضمينه صراحةً:
toSSG(app, fs, {
plugins: [defaultPlugin, myCustomPlugin],
})
إضافة إعادة التوجيه
الـ redirectPlugin ينشئ صفحات إعادة توجيه HTML للمسارات التي تُرجع استجابات إعادة توجيه HTTP (301، 302، 303، 307، 308). يتضمن HTML الناتج <meta http-equiv="refresh"> وسم ورابط canonical.
import { toSSG, redirectPlugin, defaultPlugin } from 'hono/ssg'
toSSG(app, fs, {
plugins: [redirectPlugin(), defaultPlugin()],
})
على سبيل المثال، إذا كان تطبيقك يمتلك:
app.get('/old', (c) => c.redirect('/new'))
الـ redirectPlugin سيُنشئ ملف HTML في /old.html مع إعادة توجيه meta refresh إلى /new.
[!NOTE] عند استخدامه مع
defaultPlugin، ضعredirectPluginbeforedefaultPlugin. منذdefaultPluginيتخطى الاستجابات غير 200، فإن وضعه أولاً سيمنعredirectPluginمن معالجة استجابات إعادة التوجيه.
أنواع الخطافات
يمكن للإضافات استخدام الخطّافات (hooks) التالية لتخصيص toSSG process:
export type BeforeRequestHook = (req: Request) => Request | false
export type AfterResponseHook = (res: Response) => Response | false
export type AfterGenerateHook = (
result: ToSSGResult
) => void | Promise<void>
- BeforeRequestHook: يُستدعى قبل معالجة كل طلب. أعد
falseلتخطّي المسار. - AfterResponseHook: يُستدعى بعد استلام كل استجابة. أعد
falseلتخطي إنشاء الملف. - AfterGenerateHook: يُستدعى بعد اكتمال عملية التوليد (generation) بأكملها.
واجهة الإضافة
export interface SSGPlugin {
beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
afterResponseHook?: AfterResponseHook | AfterResponseHook[]
afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]
}
أمثلة أساسية للإضافات
تصفية طلبات GET فقط:
const getOnlyPlugin: SSGPlugin = {
beforeRequestHook: (req) => {
if (req.method === 'GET') {
return req
}
return false
},
}
التصفية حسب رمز الحالة:
const statusFilterPlugin: SSGPlugin = {
afterResponseHook: (res) => {
if (res.status === 200 || res.status === 500) {
return res
}
return false
},
}
تسجيل الملفات المُنشأة:
const logFilesPlugin: SSGPlugin = {
afterGenerateHook: (result) => {
if (result.files) {
result.files.forEach((file) => console.log(file))
}
},
}
مثال على إضافة متقدمة
فيما يلي مثال على إنشاء إضافة (plugin) لخريطة الموقع (sitemap) تُنشئ ملف sitemap.xml file:
// 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)
},
}
}
تطبيق الإضافات:
import app from './index'
import { toSSG } from 'hono/ssg'
import { sitemapPlugin } from './plugins'
toSSG(app, fs, {
plugins: [
getOnlyPlugin,
statusFilterPlugin,
logFilesPlugin,
sitemapPlugin('https://example.com'),
],
})