feat(docs): add static generation and search indexes
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import { mkdir, readFile, rm, writeFile } from "node:fs/promises"
|
||||
import { dirname, join } from "node:path"
|
||||
import { pathToFileURL } from "node:url"
|
||||
|
||||
import { docLocales } from "../src/content/types.ts"
|
||||
|
||||
type StaticPage = {
|
||||
html: string
|
||||
locale: (typeof docLocales)[number]
|
||||
metadata: { description: string; title: string }
|
||||
}
|
||||
|
||||
type ServerEntry = {
|
||||
getStaticDocRoutes: () => readonly string[]
|
||||
getStaticSearchIndex: (locale: (typeof docLocales)[number]) => unknown
|
||||
renderStaticPage: (pathname: string) => Promise<StaticPage>
|
||||
}
|
||||
|
||||
const docsDirectory = dirname(dirname(import.meta.filename))
|
||||
const outputDirectory = join(docsDirectory, "dist")
|
||||
const serverDirectory = join(docsDirectory, ".ssr")
|
||||
const serverEntryUrl = pathToFileURL(
|
||||
join(serverDirectory, "entry-server.js")
|
||||
).href
|
||||
const server = (await import(serverEntryUrl)) as ServerEntry
|
||||
const template = await readFile(join(outputDirectory, "index.html"), "utf8")
|
||||
|
||||
await Promise.all(
|
||||
server.getStaticDocRoutes().map(async (route) => {
|
||||
const page = await server.renderStaticPage(route)
|
||||
const filename = getRouteFilename(route)
|
||||
await mkdir(dirname(filename), { recursive: true })
|
||||
await writeFile(filename, createHtml(template, page), "utf8")
|
||||
})
|
||||
)
|
||||
|
||||
const notFoundPage = await server.renderStaticPage("/404")
|
||||
await writeFile(
|
||||
join(outputDirectory, "404.html"),
|
||||
createHtml(template, notFoundPage),
|
||||
"utf8"
|
||||
)
|
||||
|
||||
const searchDirectory = join(outputDirectory, "search")
|
||||
await mkdir(searchDirectory, { recursive: true })
|
||||
await Promise.all(
|
||||
docLocales.map((locale) =>
|
||||
writeFile(
|
||||
join(searchDirectory, `${locale}.json`),
|
||||
JSON.stringify(server.getStaticSearchIndex(locale)),
|
||||
"utf8"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
await rm(serverDirectory, { recursive: true })
|
||||
|
||||
function getRouteFilename(route: string) {
|
||||
return route === "/"
|
||||
? join(outputDirectory, "index.html")
|
||||
: join(outputDirectory, route.slice(1), "index.html")
|
||||
}
|
||||
|
||||
function createHtml(
|
||||
source: string,
|
||||
page: Awaited<ReturnType<typeof server.renderStaticPage>>
|
||||
) {
|
||||
const description = `<meta name="description" content="${escapeAttribute(page.metadata.description)}" />`
|
||||
|
||||
return source
|
||||
.replace(/<html lang="[^"]*">/, `<html lang="${page.locale}">`)
|
||||
.replace(
|
||||
/<title>[^<]*<\/title>/,
|
||||
`<title>${escapeText(page.metadata.title)}</title>\n ${description}`
|
||||
)
|
||||
.replace('<div id="root"></div>', `<div id="root">${page.html}</div>`)
|
||||
}
|
||||
|
||||
function escapeText(value: string) {
|
||||
return value
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
}
|
||||
|
||||
function escapeAttribute(value: string) {
|
||||
return escapeText(value).replaceAll('"', """)
|
||||
}
|
||||
Reference in New Issue
Block a user