feat(docs): add static generation and search indexes

This commit is contained in:
Maofeng
2026-09-20 16:23:32 +08:00
parent a57fdbe7c6
commit 9635de56e8
19 changed files with 529 additions and 169 deletions
+32 -24
View File
@@ -1,3 +1,5 @@
import { lazy } from "react"
import {
defaultDocLocale,
docLocales,
@@ -12,23 +14,17 @@ import {
} from "./types"
import { trans, type Translation } from "../lib/locale"
type DocModule = {
type DocContentModule = {
default: React.ComponentType<{ components?: MdxComponents }>
frontmatter: Record<string, unknown>
}
type LocalizedText = Translation<string>
type PackageDefinition = {
key: string
label: string
description: LocalizedText
description: Translation<string>
}
type ParsedDocPage = DocPage & {
packageKey: string
searchText: string
}
type ParsedDocPage = DocPage & { packageKey: string }
const packageDefinitions: readonly PackageDefinition[] = [
{
@@ -81,18 +77,23 @@ const packageDefinitions: readonly PackageDefinition[] = [
},
]
const docModules = import.meta.glob<DocModule>(
const docModuleLoaders = import.meta.glob<DocContentModule>(
"../../../packages/*/docs/*/**/*.mdx",
{ eager: true }
{ eager: false }
)
const docSources = import.meta.glob<string>(
const docFrontmatter = import.meta.glob<Record<string, unknown>>(
"../../../packages/*/docs/*/**/*.mdx",
{ eager: true, import: "default", query: "?docs-search-raw" }
{ eager: true, import: "frontmatter" }
)
const parsedPages = Object.entries(docModules).map(([path, module]) =>
parseDocPage(path, module, docSources[path] ?? "")
const parsedPages = Object.entries(docFrontmatter).map(
([path, frontmatter]) => {
const load = docModuleLoaders[path]
if (!load) throw new Error(`Missing documentation module for "${path}".`)
return parseDocPage(path, frontmatter, lazy(load))
}
)
const packagesByLocale: Record<DocLocale, readonly DocPackage[]> = {
@@ -147,7 +148,7 @@ export function getLocalizedHref(pathname: string, locale: DocLocale) {
}
export function resolveDocLocale(pathname: string): DocLocale {
const firstSegment = pathname.split("/").filter(Boolean)[0]
const firstSegment = pathname.split("/").find(Boolean)
return isDocLocale(firstSegment) ? firstSegment : defaultDocLocale
}
@@ -193,8 +194,17 @@ export function getDocSectionLabel(section: DocSection, locale: DocLocale) {
return trans(locale, labels[section])
}
export function getDocSearchText(page: DocPage) {
return (page as ParsedDocPage).searchText
export function getStaticDocRoutes() {
return docLocales.flatMap((locale) => {
const home = getDocsHomeHref(locale)
return [
home,
`${home === "/" ? "" : home}/settings`,
...getDocPackages(locale).flatMap((docPackage) =>
docPackage.pages.map((page) => getDocHref(docPackage, page))
),
]
})
}
function createDocPackage(
@@ -205,7 +215,7 @@ function createDocPackage(
.filter(
(page) => page.packageKey === definition.key && page.locale === locale
)
.sort(
.toSorted(
(left, right) =>
docSections.indexOf(left.section) -
docSections.indexOf(right.section) ||
@@ -241,8 +251,8 @@ function createDocPackage(
function parseDocPage(
path: string,
module: DocModule,
source: string
frontmatter: Record<string, unknown>,
Content: DocPage["Content"]
): ParsedDocPage {
const match = path.match(
/\/packages\/([^/]+)\/docs\/([^/]+)\/(overview|guide|advanced|examples)(?:\/([^/]+))?\.mdx$/
@@ -269,14 +279,12 @@ function parseDocPage(
throw new Error(`Documentation file is in the wrong directory: "${path}".`)
}
const frontmatter = module.frontmatter
return {
Content: module.default,
Content,
description: readString(frontmatter, "description"),
locale: localeValue,
order: readNumber(frontmatter, "order"),
packageKey,
searchText: source,
section: sectionValue,
slug: nestedSlug ?? "overview",
title: readString(frontmatter, "title"),
+31
View File
@@ -0,0 +1,31 @@
import type { DocLocale, DocSearchEntry } from "./types"
const indexes = new Map<DocLocale, Promise<readonly DocSearchEntry[]>>()
export function loadDocSearchIndex(locale: DocLocale) {
const existing = indexes.get(locale)
if (existing) return existing
const index = (
import.meta.env.DEV
? import("./search-index.source").then((module) =>
module.getDocSearchIndex(locale)
)
: fetch(
`${import.meta.env.BASE_URL}search/${encodeURIComponent(locale)}.json`
).then(async (response) => {
if (!response.ok) {
throw new Error(
`Unable to load the ${locale} documentation search index.`
)
}
return (await response.json()) as readonly DocSearchEntry[]
})
).catch((error: unknown) => {
indexes.delete(locale)
throw error
})
indexes.set(locale, index)
return index
}
+11
View File
@@ -0,0 +1,11 @@
import { createDocSearchIndex } from "./search-index"
import type { DocLocale } from "./types"
const docSources = import.meta.glob<string>(
"../../../packages/*/docs/*/**/*.mdx",
{ eager: true, import: "default", query: "?docs-search-raw" }
)
export function getDocSearchIndex(locale: DocLocale) {
return createDocSearchIndex(locale, docSources)
}
+49
View File
@@ -0,0 +1,49 @@
import { getDocHref, getDocPackages, getDocSectionLabel } from "./registry"
import type { DocLocale, DocSearchEntry, DocTocItem } from "./types"
export function createDocSearchIndex(
locale: DocLocale,
sources: Readonly<Record<string, string>>
): readonly DocSearchEntry[] {
const sourcesByPage = new Map(
Object.entries(sources).map(([path, source]) => [
getSourcePageKey(path),
stripFrontmatter(source),
])
)
return getDocPackages(locale).flatMap((docPackage) =>
docPackage.pages.map((page) => ({
body:
sourcesByPage.get(
`${docPackage.key}/${locale}/${page.section}/${page.slug}`
) ?? "",
description: page.description,
href: getDocHref(docPackage, page),
id: `${page.section}/${page.slug}`,
packageKey: docPackage.key,
packageLabel: docPackage.label,
sectionLabel: getDocSectionLabel(page.section, locale),
title: page.title,
toc: page.toc.flatMap(flattenTocTitles),
}))
)
}
function getSourcePageKey(path: string) {
const match = path.match(
/\/packages\/([^/]+)\/docs\/([^/]+)\/(overview|guide|advanced|examples)(?:\/([^/]+))?\.mdx$/
)
if (!match) throw new Error(`Invalid documentation search source: "${path}".`)
const [, packageKey, locale, section, nestedSlug] = match
return `${packageKey}/${locale}/${section}/${nestedSlug ?? "overview"}`
}
function flattenTocTitles(item: DocTocItem): readonly string[] {
return [item.title, ...(item.items?.flatMap(flattenTocTitles) ?? [])]
}
function stripFrontmatter(source: string) {
return source.replace(/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/, "")
}
+12
View File
@@ -30,6 +30,18 @@ export type DocPage = {
toc: readonly DocTocItem[]
}
export type DocSearchEntry = {
body: string
description: string
href: string
id: string
packageKey: string
packageLabel: string
sectionLabel: string
title: string
toc: readonly string[]
}
export type DocPackage = {
key: string
label: string