Docusaurus (Document(文件)+ saurus(恐龍)),是由 Facebook 推出的開源靜態網站生成工具,以 React 技術構建,提供快速建置以文檔內容為核心的網站。我在 Survey 有哪些框架可以快速的把一堆 OpenAPI 文檔,轉成靜態頁面並且搜尋頁面內容,Claude 推薦我使用 Docusaurus
Claude 推薦我使用 Docusaurus 的原因如下:
- 完整支援 Markdown
- 可以整合 OpenAPI/Swagger 透過插件 docusaurus-plugin-openapi-docs
- 內建全文搜尋功能,也可以整合 Algolia DocSearch
- React-based,客製化彈性高
- Meta (Facebook) 維護,社群活躍
- 部署簡單,可以直接部署到靜態網站服務
於是,我來了。
開發環境
- Windows 11 Home
- node.js
- docusaurus 3.6.3
確保已經安裝 nodejs
node -v
npm -v
若沒有安裝的話使用 scoop 安裝
scoop install nodejs
確保已經安裝 yarn
若沒有 yarn,可以用 scoop 安裝
scoop install yarn
安裝 docusaurus
npx create-docusaurus@3.6.3 my-website-1 classic --typescript --package-manager yarn
- my-website 是你的專案名稱,可以更改為你想要的名稱。
- classic 是模板類型,也可以使用 minimal 或其他模板。
- 支援 typescript、javascript
- 最新版為 docusaurus@latest,當前為 3.6.3
切到 my-website-1 資料夾
yarn start 或是 npm start
訪問 http://localhost:3001
安裝 Open API 套件
yarn add docusaurus-plugin-openapi-docs
yarn add docusaurus-theme-openapi-docs
把 openapi.yml 放到 openapi 資料夾,目錄結構如下
my-website-1/
├── openapi/
│ ├── product.yml
│ ├── member.yml
├── docusaurus.config.ts
docusaurus.config.ts
複製以下內容,我把關鍵的行號加了 //<--- 加這個
import {themes as prismThemes} from 'prism-react-renderer';
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs"; //<--- 加這個
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
const config: Config = {
title: 'My Site',
tagline: 'Dinosaurs are cool',
favicon: 'img/favicon.ico',
// Set the production url of your site here
url: 'https://your-docusaurus-site.example.com',
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'facebook', // Usually your GitHub org/user name.
projectName: 'docusaurus', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
{
docs: {
sidebarPath: './sidebars.ts',
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
docItemComponent: "@theme/ApiItem", //<--- 加這個
},
blog: {
showReadingTime: true,
feedOptions: {
type: ['rss', 'atom'],
xslt: true,
},
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
// Useful options to enforce blogging best practices
onInlineTags: 'warn',
onInlineAuthors: 'warn',
onUntruncatedBlogPosts: 'warn',
},
theme: {
customCss: './src/css/custom.css',
},
} satisfies Preset.Options,
],
],
themeConfig: {
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
navbar: {
title: 'My Site',
logo: {
alt: 'My Site Logo',
src: 'img/logo.svg',
},
items: [
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Tutorial',
},
{to: '/blog', label: 'Blog', position: 'left'},
{
href: 'https://github.com/facebook/docusaurus',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Docs',
items: [
{
label: 'Tutorial',
to: '/docs/intro',
},
],
},
{
title: 'Community',
items: [
{
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/docusaurus',
},
{
label: 'Discord',
href: 'https://discordapp.com/invite/docusaurus',
},
{
label: 'X',
href: 'https://x.com/docusaurus',
},
],
},
{
title: 'More',
items: [
{
label: 'Blog',
to: '/blog',
},
{
label: 'GitHub',
href: 'https://github.com/facebook/docusaurus',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
} satisfies Preset.ThemeConfig,
plugins: [ // <--- 加這個
[
'docusaurus-plugin-openapi-docs',
{
id: "member",
docsPluginId: "classic", // unique docsPluginId
config: {
member: {
specPath: "openapi/member.yml",
outputDir: "docs/api/member",
sidebarOptions: {
groupPathsBy: "tag",
},
} satisfies OpenApiPlugin.Options,
product: {
specPath: "openapi/product.yml",
outputDir: "docs/api/product",
sidebarOptions: {
groupPathsBy: "tag",
},
} satisfies OpenApiPlugin.Options
},
},
],
],
themes: ["docusaurus-theme-openapi-docs"], // <--- 加這個
};
export default config;
把 openapi 轉成 mdx
yarn docusaurus gen-api-docs all
重新啟動 npm,就可以看到我放進去的 openapi.yml 了
若要更新 openapi 得先清掉,再產生
yarn docusaurus clean-api-docs all
參考:https://github.com/PaloAltoNetworks/docusaurus-openapi-docs
假若每一次增加一個 openapi.yml 就要調整一次設定,不是很聰明,把它改成讀取 openapi 的資料夾
import path from "path";
import fs from "fs";
const generateOpenApiPlugins = () => {
const openapiDir = path.resolve(__dirname, 'openapi');
const openapiFiles = fs.readdirSync(openapiDir).filter(file => file.endsWith('.yml'));
const config = openapiFiles.reduce((data, file) => {
const id = path.basename(file, '.yml');
console.log(`id: ${id}, file: ${file}`);
data[id] = {
specPath: path.join(openapiDir, file),
outputDir: `docs/api/${id}`,
sidebarOptions: {
groupPathsBy: 'tag',
}
} satisfies OpenApiPlugin.Options
return data;
}, {});
const result = [
'docusaurus-plugin-openapi-docs',
{
id: 'openapi',
docsPluginId: 'classic',
config,
},
];
console.log("result: " + result);
return result;
};
plugins 調用 generateOpenApiPlugins
plugins: [
generateOpenApiPlugins(),
],
完整 docusaurus.config.ts 內容如下
import {themes as prismThemes} from 'prism-react-renderer';
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs"; // <--- 加這個
import path from "path"; // <--- 加這個
import fs from "fs"; // <--- 加這個
const generateOpenApiPlugins = () => { // <--- 加這個
const openapiDir = path.resolve(__dirname, 'openapi');
const openapiFiles = fs.readdirSync(openapiDir).filter(file => file.endsWith('.yml'));
const config = openapiFiles.reduce((data, file) => {
const id = path.basename(file, '.yml');
console.log(`id: ${id}, file: ${file}`);
data[id] = {
specPath: path.join(openapiDir, file),
outputDir: `docs/api/${id}`,
sidebarOptions: {
groupPathsBy: 'tag',
}
} satisfies OpenApiPlugin.Options
return data;
}, {});
const result = [
'docusaurus-plugin-openapi-docs',
{
id: 'openapi',
docsPluginId: 'classic',
config,
},
];
console.log("result: " + result);
return result;
};
const config: Config = {
title: 'My Site',
tagline: 'Dinosaurs are cool',
favicon: 'img/favicon.ico',
// Set the production url of your site here
url: 'https://your-docusaurus-site.example.com',
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'facebook', // Usually your GitHub org/user name.
projectName: 'docusaurus', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
{
docs: {
sidebarPath: './sidebars.ts',
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
docItemComponent: "@theme/ApiItem", //<--- 加這個
},
blog: {
showReadingTime: true,
feedOptions: {
type: ['rss', 'atom'],
xslt: true,
},
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
// Useful options to enforce blogging best practices
onInlineTags: 'warn',
onInlineAuthors: 'warn',
onUntruncatedBlogPosts: 'warn',
},
theme: {
customCss: './src/css/custom.css',
},
} satisfies Preset.Options,
],
],
themeConfig: {
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
navbar: {
title: 'My Site',
logo: {
alt: 'My Site Logo',
src: 'img/logo.svg',
},
items: [
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Tutorial',
},
{to: '/blog', label: 'Blog', position: 'left'},
{
href: 'https://github.com/facebook/docusaurus',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Docs',
items: [
{
label: 'Tutorial',
to: '/docs/intro',
},
],
},
{
title: 'Community',
items: [
{
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/docusaurus',
},
{
label: 'Discord',
href: 'https://discordapp.com/invite/docusaurus',
},
{
label: 'X',
href: 'https://x.com/docusaurus',
},
],
},
{
title: 'More',
items: [
{
label: 'Blog',
to: '/blog',
},
{
label: 'GitHub',
href: 'https://github.com/facebook/docusaurus',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
} satisfies Preset.ThemeConfig,
plugins: [ // <--- 加這個
generateOpenApiPlugins(),
],
// plugins: [ // <--- 加這個
// [
// 'docusaurus-plugin-openapi-docs',
// {
// id: "member",
// docsPluginId: "classic", // unique docsPluginId
// config: {
// member: {
// specPath: "openapi/member.yml",
// outputDir: "docs/api/member",
// sidebarOptions: {
// groupPathsBy: "tag",
// },
// } satisfies OpenApiPlugin.Options,
// product: {
// specPath: "openapi/product.yml",
// outputDir: "docs/api/product",
// sidebarOptions: {
// groupPathsBy: "tag",
// },
// } satisfies OpenApiPlugin.Options
// },
//
// },
// ],
// ],
themes: ["docusaurus-theme-openapi-docs"], // <--- 加這個
};
export default config;
Search local
當文件越來越多的時候,搜尋功能就很重要了,中國開發者開發的 @easyops-cn/docusaurus-search-local - npm,是 search local 目前最多人在使用的,
安裝
yarn add @easyops-cn/docusaurus-search-local
在 docusaurus.config.ts,加上參數
themes: ["docusaurus-theme-openapi-docs",
[
"@easyops-cn/docusaurus-search-local", // <--- 加這個
{
hashed: true,
// language: ["en", "zh"],
// highlightSearchTermsOnTargetPage: true,
// explicitSearchResultPath: true,
},
],
],
更多的設定請參考:easyops-cn/docusaurus-search-local: Offline/local search for Docusaurus v2/v3
建立索引檔
yarn run docusaurus build
可以看到多出了 build 資料夾
使用 serve 叫起服務
yarn run docusaurus serve
查詢效果如下
範例位置
sample.dotblog/Docusaurus/my-website-1 at master · yaochangyu/sample.dotblog
code 拉下來後,執行 yarn install 或 npm install
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET