用 docusaurus 搭建文档网页

之前有个想法,把自己日常收集和整理的文档整合起来,包括前端知识、环境相关(mac/服务器)、手写代码等。之前这些文档都存在 github 上,比较散乱。如果有个文档网页承接这些内容,不仅利于整理,有助于我平时阅览,也可以让更多人看到我的内容

为什么选 docusaurus?其实 gatsby 也可以做,但是 docusaurus 主要是做内容网页这一块,有很多开箱即用的功能,包括文档目录、自动生成导航栏、暗黑模式、docsearch、seo 支持等,gatsby 还需要额外引入一些插件来完成这些事,会比较麻烦点。其他方面其实都差不多,比如基于 react,支持 typescript、mdx 等等

这篇文章也主要总结下我在搭建过程遇到的一些经验,当然,目前只是个基础版本,欢迎 comment ~ Lucas’s docs

新建项目

注意:Node.js >= 16.14(node -v)。可以用 fnm 或 nvm 快捷切换 Node.js 版本

npx create-docusaurus@latest [项目名称] classic --typescript

基本项目结构如下:

├── blog # 内置博客存放文档的位置,可以直接在这里存个人的博客文档。用不上博客的话直接删除就行了
├── docs # 内置文档存放的位置
├── src
│   ├── css
│   └── pages # 页面,此目录中的任何扩展名为 JSX/TSX/MDX 文件都将被转换为网站的独立页面
├── static # 存放静态文件
├── docusaurus.config.js # 网页配置文件
├── sidebars.js # 侧边栏,可以在这里指定侧边栏中的文档顺序或生成规则
├── package.json
├── README.md
└── yarn.lock

本地调试和打包

npm install
# 本地调试
npm start
# 生成静态文件
npm run build

配置

项目根目录会有这个文件 docusaurus.config.js,用于配置一些内置功能。可以参考我的这份配置:

const config = {
  // 网站标题
  title: "Lucas's Docs",
  tagline: "Dinosaurs are cool",
  // 网站图标
  favicon: "img/favicon.ico",
  // 网站网址
  url: "https://docs.zhouweibin.top",
  baseUrl: "/",
  organizationName: "facebook", // Usually your GitHub org/user name.
  projectName: "docusaurus", // Usually your repo name.
  onBrokenLinks: "throw",
  onBrokenMarkdownLinks: "warn",
  i18n: {
    defaultLocale: "en",
    locales: ["en"],
  },
  presets: [
    [
      "classic",
      /** @type {import('@docusaurus/preset-classic').Options} */
      ({
        docs: {
          sidebarPath: require.resolve("./sidebars.js"),
          // 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/",
        },
        // 不需要博客功能可以注释掉
        // blog: {
        //   showReadingTime: 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/",
        // },
        // 自定义主题和样式
        theme: {
          customCss: require.resolve("./src/css/custom.css"),
        },
        // google搜索需要
        gtag: {
          trackingID: "G-G4NRSZR6K3",
          anonymizeIP: true,
        },
      }),
    ],
  ],
  themeConfig:
    /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
    ({
      // 最左侧图标
      image: "img/docusaurus-social-card.jpg",
      navbar: {
        // hideOnScroll: true, 滚动时是否隐藏顶部栏
        title: "Lucas's docs",
        logo: {
          alt: "Lucas's Docs Logo",
          src: "img/logo.svg",
        },
        items: [
          // 顶部栏
          {
            type: "docSidebar",
            sidebarId: "awesome-dev",
            position: "left",
            label: "awesome-FED",
          },
          // 顶部栏图标配置,后面会讲到怎么设置图标
          {
            href: "https://github.com/GitHubJackson/my-docs",
            position: "right",
            className: "header-github-link",
            "aria-label": "GitHub",
          },
          {
            href: "https://mail.google.com/?view=cm&fs=1&tf=1&to=jacksonzhou52017@gmail.com",
            position: "right",
            className: "header-email-link",
            "aria-label": "Email",
          },
        ],
      },
      // 底部栏
      footer: {
        style: "dark",
        links: [
          {
            title: "Link",
            items: [
              {
                label: "Lucas's Blog",
                to: "https://blog.zhouweibin.top",
              },
            ],
          },
          {
            title: "Community",
          },
        ],
        copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
      },
      // 主题配置
      prism: {
        theme: lightCodeTheme,
        darkTheme: darkCodeTheme,
      },
      // docsearch 后面会讲到,需要替换成自己的
      algolia: {
        appId: "xxx", // Application ID
        apiKey: "xxx", //  Search-Only API Key
        indexName: "lucas'docs",
      },
    }),
};

module.exports = config;

自定义样式和主题

src/css/custom.css 这里定义样式和主题色,可以点击 这里 快速配置和阅览效果

编写文档

直接在 docs 目录下新增 markdown 文件,默认会自动显示到侧边栏。文档顶部配置参考如下:

---
sidebar_position: 1    # 设置文档顺序,值越小越排前
title: Github Actions  # 文档标题。不设置的话默认以文件名或用#指定标题
description: desc      # 文档描述
tags:                  # 文档相关标签,会显示在文档末尾
  - tag1
  - tag2
---

正文...

更详细的参数参考 https://www.docusaurus.cn/docs/create-doc

侧边栏设置

侧边栏

可以调整 sidebars.js,更改侧边栏的选项和顺序

const sidebars = {
  // key 对应顶部栏,顶部栏会在下面讲到
  "awesome-dev": [
    {
      type: "autogenerated", // 根据文件夹内容自动生成,推荐这个配置
      dirName: "awesome-dev", // 该目录相对于docs目录,也就是在docs下新增一个文件夹
    },
  ],
};
module.exports = sidebars;

更多参考 https://www.docusaurus.cn/docs/sidebar

增加顶部栏

顶部菜单

  1. docusaurus.config.js > themeConfig 增加选项,以 new-tab 为例:
// ...
themeConfig: {
  navbar: {
    title: "Lucas's docs",
    logo: {
      alt: "Lucas's Docs Logo",
      src: "img/logo.svg",
    },
    items: [
      {
        type: "docSidebar",
        sidebarId: "awesome-dev",
        position: "left",
        label: "awesome-FED",
      },
      {
        type: "docSidebar",
        label: "新栏目",
        sidebarId: "new-tab",
        position: "left",
      },
    ],
  },
}
// ...
  1. 记得别忘了在 sidebars.js 配置对应的侧边栏
const sidebars = {
  // key 对应顶部栏,顶部栏会在下面讲到
  "awesome-dev": [
    {
      type: "autogenerated", // 根据文件夹内容自动生成,推荐这个配置
      dirName: "awesome-dev",
    },
  ],
  "new-tab": [
    {
      type: "autogenerated",
      dirName: "new-tab",
    },
  ],
};

之后页面就会新增一个 new-tab 的顶部栏。那有时候只想加个图标,比如夜间模式切换图标和 github 图标,需要怎么实现呢?也不难,参考以下步骤:

  1. docusaurus.config.js > themeConfig 增加选项,以 email 为例:
// ...
themeConfig: {
  navbar: {
    title: "Lucas's docs",
    logo: {
      alt: "Lucas's Docs Logo",
      src: "img/logo.svg",
    },
    items: [
      {
        type: "docSidebar",
        sidebarId: "awesome-dev",
        position: "left",
        label: "awesome-FED",
      },
      {
        // email 自行搜索各邮件的接口格式,这里是 gmail,点击后会重定向到发邮件地址,收件人会自动填入我指定的地址
        href: "https://mail.google.com/?view=cm&fs=1&tf=1&to=jacksonzhou52017@gmail.com",
        position: "right",
        className: "header-email-link",
        "aria-label": "Email",
      },
    ],
  },
}
  1. 定义图标,这里通过 css 加载图标
// src/css/custom.css
// ...
/* header-email-link */
.header-email-link:hover {
  opacity: 0.6;
}

.header-email-link::before {
  content: "";
  width: 24px;
  height: 24px;
  display: flex;
  background: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M854.016 342.016V256L512 470.016 169.984 256v86.016L512 554.026zm0-172.032q34.005 0 59.008 25.984t25.003 59.99v512q0 34.005-25.003 59.989t-59.008 25.984h-683.99q-34.005 0-59.007-25.984t-25.003-59.99v-512q0-34.005 25.003-59.989t59.008-25.984h683.989z' fill='%23444'/%3E%3C/svg%3E")
    no-repeat center;
  background-size: 100% 100%;
}

[data-theme="dark"] .header-email-link::before {
  background: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M854.016 342.016V256L512 470.016 169.984 256v86.016L512 554.026zm0-172.032q34.005 0 59.008 25.984t25.003 59.99v512q0 34.005-25.003 59.989t-59.008 25.984h-683.99q-34.005 0-59.007-25.984t-25.003-59.99v-512q0-34.005 25.003-59.989t59.008-25.984h683.989z' fill='%23cdcdcd'/%3E%3C/svg%3E")
    no-repeat center;
  background-size: 100% 100%;
}

图标是 svg 格式,但是需要做下转义,推荐个工具 https://www.zhangxinxu.com/sp/svgo/

文章多级分类

通过多层文件夹可以实现文章多级分类

多层分类.png

比如这里的 explorer 对应网页顶部的一个菜单,图片这个文件夹对应左侧的一级分类,下面的内容属于二级分类,参考下图。_category_.json 可以实现一个下图所示的父级分类卡片概览:

分类概览.png

_category_.json 内容参考:

{
  "label": "Getting Started",
  "position": 1,
  "link": {
    "type": "generated-index",
    "description": "该分类的描述信息,在卡片概览上方显示"
  }
}

评论功能

评论区.png

这个可以借助 giscus,需要先安装 giscus app

  1. 新增一个空的仓库用于承接评论信息(公开、启用 Discussions)
  2. https://giscus.app/zh-CN ,按照官网的步骤先做好配置,记住最后的代码片段(script 标签),后面会用到
  3. swizzling 对应的组件,默认文档组件是隐藏的,需要一次性导出,才能增加评论组件
yarn run swizzle @docusaurus/theme-classic DocItem/Layout -- --eject --typescript

这条命令会生成文件 src/theme/DocItem/Layout/index.tsx,我们需要对其进行修改,改动内容参考以下代码:

// ...
  // 可以指定某些文档不允许评论,文章开头增加 hide_comment 配置即可
  const { hide_comment: hideComment } = frontMatter;
  const commentElement = useRef(null);
  useEffect(() => {
    // Update the document title using the browser API
    let s = document.createElement("script");
    s.src = "https://giscus.app/client.js";
    s.setAttribute("data-repo", "xxx");
    s.setAttribute("data-repo-id", "xxx");
    s.setAttribute("data-category", "Announcements");
    s.setAttribute("data-category-id", "xxx");
    s.setAttribute("data-mapping", "pathname");
    s.setAttribute("data-reactions-enabled", "1");
    s.setAttribute("data-emit-metadata", "0");
    s.setAttribute("data-input-position", "bottom");
    s.setAttribute("data-theme", "light");
    s.setAttribute("data-lang", "zh-CN");
    s.setAttribute("crossorigin", "anonymous");
    s.async = true;
    commentElement.current.appendChild(s);
  }, []);
  // ...
  return (
    // ...
    <DocItemPaginator />
    {!hideComment && <div ref={commentElement}></div>}
    // ...
  )

部署项目

简单点,其实就是在服务器划分一个文件夹存储网页的静态资源,用 nginx 配置端口和网页资源的映射关系,然后本地 build 生成静态资源后,通过 scp 把静态资源上传到服务器指定的一个目录下(或者用 ftp 可视化工具上传也行)。如果要搞提交代码后自动部署网页,内容可能稍微有点多,需要的话可以参考我之前写的一些文章:

  1. 购置服务器,配置 nginx,参考 文章
  2. 配置前端环境(Node.js>=16.14),参考 文章
  3. 自动化部署。借助 github actions 完成代码提交后自动部署,搭建指南参考 Github Actions

添加文档搜索

docsearch

既然是内容网页,高效的内容检索就很必要了。docsearch 这个功能放到这里说,是因为跟 github actions 有关

  1. 先到 官网 注册,大概两天后会收到邮件通知注册成功,再接着往下操作
  2. 登录
  3. 创建一个新的 Application,选择左边的免费版本进行下一步
  4. 定义 index_name,选择响应快的服务进行创建。到这一步就创建成功了
  5. 打开 API keys,获取三个需要的字段 Application IDSearch-Only API keyAdmin API key
  6. 修改 docusaurus.config.js
// ...
themeConfig: {
  prism: {
    theme: lightCodeTheme,
    darkTheme: darkCodeTheme,
  },
  // 新增
  algolia: {
    appId: "xxx", // Application ID
    apiKey: "xxx", // Search-Only API Key
    indexName: "xxx", // index_name
  },
}
// ...

之后本地调试应该能看到 docsearch 的组件了

  1. 本地新增 docsearch.json 配置文件,注意几个字段要替换成自己的
{
  // 需要替换
  "index_name": "xxx",
  // 需要替换。网站网址
  "start_urls": ["xxx"],
  // 需要替换。sitemap的网址,docusaurus 默认在根目录下生成 sitemap.xml
  "sitemap_urls": ["xxx"],
  "selectors": {
    "lvl0": {
      "selector": "(//ul[contains(@class,'menu__list')]//a[contains(@class, 'menu__link menu__link--sublist menu__link--active')]/text() | //nav[contains(@class, 'navbar')]//a[contains(@class, 'navbar__link--active')]/text())[last()]",
      "type": "xpath",
      "global": true,
      "default_value": "Documentation"
    },
    "lvl1": "header h1, article h1",
    "lvl2": "article h2",
    "lvl3": "article h3",
    "lvl4": "article h4",
    "lvl5": "article h5, article td:first-child",
    "lvl6": "article h6",
    "text": "article p, article li, article td:last-child"
  },
  "custom_settings": {
    "attributesForFaceting": [
      "type",
      "lang",
      "language",
      "version",
      "docusaurus_tag"
    ],
    "attributesToRetrieve": [
      "hierarchy",
      "content",
      "anchor",
      "url",
      "url_without_anchor",
      "type"
    ],
    "attributesToHighlight": ["hierarchy", "content"],
    "attributesToSnippet": ["content:10"],
    "camelCaseAttributes": ["hierarchy", "content"],
    "searchableAttributes": [
      "unordered(hierarchy.lvl0)",
      "unordered(hierarchy.lvl1)",
      "unordered(hierarchy.lvl2)",
      "unordered(hierarchy.lvl3)",
      "unordered(hierarchy.lvl4)",
      "unordered(hierarchy.lvl5)",
      "unordered(hierarchy.lvl6)",
      "content"
    ],
    "distinct": true,
    "attributeForDistinct": "url",
    "customRanking": [
      "desc(weight.pageRank)",
      "desc(weight.level)",
      "asc(weight.position)"
    ],
    "ranking": [
      "words",
      "filters",
      "typo",
      "attribute",
      "proximity",
      "exact",
      "custom"
    ],
    "highlightPreTag": "<span class='algolia-docsearch-suggestion--highlight'>",
    "highlightPostTag": "</span>",
    "minWordSizefor1Typo": 3,
    "minWordSizefor2Typos": 7,
    "allowTyposOnNumericTokens": false,
    "minProximity": 1,
    "ignorePlurals": true,
    "advancedSyntax": true,
    "attributeCriteriaComputedByMinProximity": true,
    "removeWordsIfNoResults": "allOptional",
    "separatorsToIndex": "_",
    "synonyms": [
      ["js", "javascript"],
      ["ts", "typescript"]
    ]
  }
}
  1. 增加 .github/workflows/docseach.yml,在提交代码后提醒 algolia 服务更新本站资源。注意:需要先新增两个 key:ALGOLIA_APP_IDALGOLIA_API_KEY,分别对应 Application IDAdmin API key
name: docsearch

on:
  push:
    branches:
      - main

jobs:
  algolia:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Get the content of docsearch.json as config
        id: algolia_config
        run: echo "::set-output name=config::$(cat docsearch.json | jq -r tostring)"

      - name: Run algolia/docsearch-scraper image
        env:
          ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
          ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
          CONFIG: ${{ steps.algolia_config.outputs.config }}
        run: |
          docker run \
            --env APPLICATION_ID=${ALGOLIA_APP_ID} \
            --env API_KEY=${ALGOLIA_API_KEY} \
            --env "CONFIG=${CONFIG}" \
            algolia/docsearch-scraper

SEO 相关

生成站点地图。docusaurus 在 build 时默认会创建 sitemap.xml,存放在打包文件夹根目录下。之后可以上传到 谷歌/bing/百度的站长管理平台,只需上传一次,后面他们都会定时去更新(不过最近几个月百度都没法上传 sitemap 了,一整个无语住了,有了解的同学可以 comment 下..)

在每篇文章开头补充详细的 keywords 和 description,也有利于搜索引擎检索

google analytics

可以分析你网站的运行情况,比如访问量、用户属性等,然后可以检查自己的网页有没有被检索,没有的话可以主动请求编入索引(当然,需要你的文章内容过关,并且适合用于移动网页)

添加教程参考 添加帐号。在 docusaurus 中使用 google analytics 需要用到插件 plugin-google-gtag

npm install --save @docusaurus/plugin-google-gtag

docusaurus.config.js 追加 gtag 配置如下:

// ...
presets: [
  [
    "classic",
    ({
      // ...
      gtag: {
        // 从 google analytics 账号中获取
        trackingID: "xxx",
        anonymizeIP: true,
      },
    }),
  ],
],

robots.txt

放在 static 目录下即可,build 后会在网页根目录下。如果没有限制搜索引擎访问某些网页的需求,那直接用下面的内容就行了## 文章多级分类

通过多层文件夹可以实现文章多级分类

多层分类.png

比如这里的 explorer 对应网页顶部的一个菜单,图片这个文件夹对应左侧的一级分类,下面的内容属于二级分类,参考下图。_category_.json 可以实现一个下图所示的父级分类卡片概览:

分类概览.png

_category_.json 内容参考:

{
  "label": "Getting Started",
  "position": 1,
  "link": {
    "type": "generated-index",
    "description": "该分类的描述信息,在卡片概览上方显示"
  }
}
User-agent: *
Disallow:

自定义组件

待补充…

更多插件

待补充…

写在最后

有疑问或建议欢迎 comment.