这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

OINK

产品边界、架构与运作方式

Oink 是一款从 Docsy 演化而来的独立、本地优先 Hugo 文档主题。它保留 Docsy 成熟的内容模型,同时把一套实现确立为标准产品:文档外壳、仅依赖 Hugo 的消费端构建、本地浏览器运行时、多语言基础设施,以及可复用的内容组件。

公开 Hugo Module 是 github.com/pgsty/oink;文档与回归内容独立存放在 github.com/pgsty/oink.pgsty.com

产品契约

唯一标准主题

Oink 不是叠加在另一套 Docsy 安装之上的皮肤。项目不存在 oink.enabled 开关、不建立 params.oink.* 命名空间,也没有需要同步维护的第二套视觉实现。主题仓库根目录中的布局与资源就是产品本身。

语言、模块、菜单、输出与标记设置使用 Hugo 原生配置;语义仍然适用时沿用 Docsy 现有参数;只有当主题确实需要用户做出选择时,才增加职责单一的参数。

消费端仅依赖 Hugo 构建

站点导入模块后,生产构建命令只有:

hugo --gc --minify

消费站点无需安装 Node.js、npm、PostCSS、Autoprefixer 或浏览器端软件包。项目站点仓库中的维护工具不属于消费端构建契约。

默认本地优先

Bootstrap、Font Awesome、Web 字体、本地搜索、图表与 API 文档运行时,以及 Oink 内容组件都随主题提供。资源从生成后的站点提供;在可行的情况下,只有实际使用相应能力的页面才会加载它们。

作者仍可链接互联网、嵌入远程媒体、启用托管服务,或配置 PlantUML 与 Diagrams.net 端点。但这些网络边界必须显式声明;对于主题自带能力,Oink 不会暗中选择公共端点。

把多语言作为基础设施

语言行为完全根据 Hugo 已配置的语言和页面译文推导。Oink 会输出语言、书写方向、canonical、hreflang 和 Open Graph locale 元数据,并支持 .md.zh.md 这样的并置译文。

交付内容

  • 响应式文档与博客外壳、导航、搜索、打印输出、深色模式和移动端行为;
  • 本地 Mermaid、KaTeX、Markmap、Swagger UI、Redoc、Asciinema、ECharts 与 Infographic 运行时;
  • 折叠块、标签页、卡片、导航卡片、文档卡片和轮播;
  • 翻译资源,以及记录来源、许可证和校验值的版本化 VENDOR.json
  • Hugo 模块声明、Apache-2.0 许可证与必需归属信息。

不在交付范围内的内容

主题仓库不包含项目网站、生成的 public/ 输出、npm workspace、产品专用控件或部署配置。这些职责留在消费站点或独立的项目站点仓库中。

生产站点应固定发布标签或不可变 commit,而不是跟随 main

代码仓库

仓库 用途
pgsty/oink 发布主题与 Hugo Module
pgsty/oink.pgsty.com 文档、示例、测试与部署

本地开发主题时,把两个仓库克隆为同级目录,并用被忽略的 Go workspace 连接。

项目状态

当前验证基线为 Hugo Extended 0.164.0,主题声明的最低版本为 0.160.1。本地构建成功,本身并不能证明公开标签、托管站点或下游部署已经存在。

项目保留 Docsy 的 Apache-2.0 历史与归属信息。源码与离线发行包必须保留 LICENSENOTICE 以及适用的第三方声明。

后续步骤

  1. 安装 Hugo Module
  2. 阅读架构本地优先模型
  3. 查看内容组件配置
  4. 选择部署方案,并遵循发布检查表
  5. 从现有 Docsy 站点迁移时,请从迁移指南开始。

1 - 快速开始

以 Hugo Module 方式为站点添加 Oink

Oink 以 Hugo Module github.com/pgsty/oink 发布。消费站点只需 Hugo Extended 即可构建;Node.js、npm、PostCSS 和 CDN 托管的浏览器软件包不属于构建契约。

前置条件

安装 Git、Go 与 Hugo Extended 0.160.1 或更高版本。项目站点目前使用 0.164.0 验证:

git --version
go version
hugo version

Hugo 版本输出必须包含 extended

添加模块

在 Hugo 站点根目录中,如果站点还没有模块,先初始化模块,再固定一个 Oink 版本:

hugo mod init github.com/example/product-docs
hugo mod get github.com/pgsty/oink@THEME_REF

请把 THEME_REF 替换为 v0.16.0 之类的已发布标签,或不可变的 commit。然后在 hugo.yaml 中添加导入:

module:
  imports:
    - path: github.com/pgsty/oink

提交生成的 go.modgo.sum。生产构建不要跟随未固定版本的分支。

预览站点

启动编辑服务器:

hugo server --disableFastRender

生成生产构建产物:

hugo --gc --minify

Oink 已随主题提供 Bootstrap、Font Awesome、字体、搜索、图表、API 文档运行时和内容组件。消费站点不需要 node_modules 目录。

使用本地 checkout 开发

把主题与站点克隆为同级目录,再使用本地 Go workspace:

~/pgsty/
├── oink/
└── product-docs/
cd ~/pgsty/product-docs
go work init .
go work edit -replace=github.com/pgsty/oink=../oink
export HUGO_MODULE_WORKSPACE=go.work
hugo server

不要把 go.work 提交到版本库。已提交的 go.mod 仍固定公开模块;workspace 只在本机把它替换为同级 checkout。

添加双语内容

先创建英文页面:

content/docs/operations.md

然后在旁边添加译文:

content/docs/operations.zh.md

front matter 标识符、代码、命令、参数名与链接目标应保持语义一致;面向读者的正文则需要翻译。为了让不同语言下的深层链接保持稳定,请在中文标题中显式保留英文标题 ID:

## 故障恢复 {#failure-recovery}

配置最小站点

最基本的配置很精简:

title: Product Docs
baseURL: https://docs.example.com/
defaultContentLanguage: en

languages:
  en:
    label: English
    locale: en-US
    weight: 1
  zh:
    label: 简体中文
    locale: zh-CN
    weight: 2

params:
  logo: icons/logo.svg
  offlineSearch: true

module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: 0.160.1

站点扩展后,再逐步加入菜单、输出格式、Markdown 扩展、仓库链接和可选功能。支持的配置模型详见配置

发布前验证

至少完成以下检查:

  1. 使用已经提交模块文件的全新 checkout 构建;
  2. 使用固定版本的 Hugo Extended 运行 hugo --gc --minify
  3. 浏览具有代表性的英文与中文页面;
  4. 验证语言切换、搜索、移动导航、深色模式与打印输出;
  5. 如果站点承诺离线运行,检查浏览器网络请求。

这些检查只能证明构建产物成立。发布该产物并验证托管地址,是两个独立的部署步骤。

2 - 架构

Oink 如何把内容与本地资源构建成文档站点

Oink 是一款直接运行于 Hugo 的主题,而不是应用服务器,也不是套在 Docsy 外面的运行时包装层。Hugo 在构建阶段解析内容、配置、布局与资源,再生成可由普通文件托管服务发布的静态站点。

系统边界

flowchart LR
  C[站点内容] --> H[Hugo Extended]
  G[Hugo 配置] --> H
  T[Oink Hugo Module] --> H
  V[已提交的第三方资源] --> T
  H --> P[静态 public 目录]
  P --> B[浏览器]

消费端边界始于站点与已经解析的主题模块,止于 Hugo 生成的静态文件。在这条路径中,不需要 JavaScript 包管理器、CSS 后处理器可执行文件或远程资源下载。

交互功能仍会在浏览器中运行 JavaScript。“仅依赖 Hugo”描述的是构建依赖,并不意味着用户界面完全没有 JavaScript。

仓库边界

主题仓库

github.com/pgsty/oink 是公开 Hugo Module。仓库根目录包含标准布局、partial、短代码、SCSS、JavaScript、字体、图标、浏览器运行时、翻译资源、go.modhugo.yamlVENDOR.json 记录随附的第三方资源。

该仓库不包含项目网站或 npm workspace。README.mdLICENSENOTICEtheme.toml 与 vendor 清单等根元数据,是发布和标注主题来源所必需的内容。

项目站点仓库

github.com/pgsty/oink.pgsty.com 包含文档、双语示例、回归页面、站点专用布局与资源、基于 npm 的站点测试,以及部署配置。它在 hugo.yaml 中导入公开主题模块,并在 go.mod 中固定版本。

跨仓库本地开发时,被忽略的 go.work 会替换为同级主题 checkout;站点模块不会提交相对文件系统 replacement。

构建流水线

Hugo 会合并四类输入:

  1. 消费站点的页面 bundle 与 Markdown 内容;
  2. Hugo 原生配置和受支持的主题参数;
  3. 主题模板、翻译、SCSS 与 JavaScript;
  4. 已提交的 static 或 Hugo Asset 资源。

Hugo 使用内置流水线编译 SCSS、打包页面 JavaScript、压缩生产资源、为适用产物生成指纹,并按照配置的 baseURL 重写相对 URL。Oink 不调用 Hugo 的 postCSS pipe。

最终 public/ 目录包含 HTML、CSS、JavaScript、字体、搜索索引、feed、sitemap 与复制的静态文件;部署时不需要源码树。

页面外壳

标准页面外壳由小型 partial 组装:

  • 全局 navbar 与响应式次级导航;
  • 语言和颜色模式控件;
  • 可调整宽度、可折叠的文档侧栏;
  • 面包屑、目录、阅读元数据、反馈与仓库链接;
  • 公共页脚与打印布局。

Hugo 的正常模板查找机制仍可用于站点扩展。应覆盖最小范围的 partial,而不是复制 baseof.html 或整个外壳。

条件运行时加载

内容短代码会在 page store 中记录功能使用情况。资源 partial 检查这些标记,并且最多加入一次对应本地运行时:

flowchart TD
  S[短代码渲染] --> M[设置页面功能标记]
  M --> A[组装资源]
  A --> Q{是否使用功能?}
  Q -- 是 --> L[加入一次本地运行时]
  Q -- 否 --> O[省略运行时]

因此普通文章不会加载 ECharts、Asciinema 或 Infographic,同时功能页仍可包含多个组件实例。

多语言路由

Oink 把语言身份交给 Hugo 管理。选择器使用每页的 .Translations 与按权重排序的站点语言。缺少译文时回退到目标语言首页;同一组数据也用于 canonical 与 alternate 元数据。

安全边界

Oink 区分作者数据与作者提供的可执行代码:

  • 结构化 ECharts 选项按 JSON 或 YAML 解析并安全序列化;
  • ECharts 中的 JavaScript 默认拒绝,除非显式启用 unsafe 迁移开关;
  • 组件 ID 与配置由模板生成,不通过未转义 HTML 字符串拼装;
  • 托管搜索、分析、评论、远程媒体与服务端点始终由站点显式决定。

Goldmark 的 unsafe 设置允许受信任的项目作者使用行内 HTML;它不是针对不受信任输入的净化器。

上游维护

Oink 保留 Docsy 的源码历史与 Apache-2.0 义务。上游变更会被分类为适用、已被 Oink 有意差异取代,或无关。适用变更会移植到标准实现中,而不会重新制造上游与品牌两套运行模式。

扩展边界

一项实现如果广泛可复用、具有稳定内容 API,并能自行管理资源与无障碍行为,就应放入主题;如果它嵌入产品数据、价格、目录假设或一次性落地页结构,则应保留在站点中。

3 - 本地优先运行

在没有隐藏网络依赖的情况下构建并浏览 OINK 核心功能

OINK 的本地优先原则很简单:由主题提供的功能,不得暗中依赖公共 CDN、构建期下载或未经配置的公共服务。完整发行包能够在网络隔离环境中构建,其核心页面也能在该环境中浏览。

本地优先覆盖的范围

主题会从生成后的站点提供以下依赖:

能力 本地交付方式
页面外壳与响应式界面 Bootstrap 与 OINK CSS/JavaScript
图标与字体 Font Awesome、Open Sans、Chakra Petch、IBM Plex Mono
搜索 Lunr、CJK 子串回退与按语言生成的索引
图表与公式 Mermaid、KaTeX 与 Markmap
API 文档 Swagger UI 与 Redoc
富内容 Asciinema、ECharts、Infographic 与轮播运行时

这些资源提交在 assets/static/ 中。Hugo 会按照站点 baseURL 发布它们,部署在子路径时也不例外。

本地优先不覆盖的范围

OINK 无法让作者任意添加的内容自动离线。以下项目仍然是显式网络选择:

  • 外部链接、远程图片、视频、iframe 与 API 规范;
  • Algolia、Google CSE 等托管搜索;
  • 分析、评论、身份提供商与其他 SaaS 集成;
  • 作者主动选择远程渲染器时的 PlantUML 或 Diagrams.net。

使用这些功能的页面仍然可以是有效页面,但站点不应再宣称该页面能够完全离线使用。

依赖服务的图表

PlantUML 与 Diagrams.net 不同于纯浏览器库:其常规工作流依赖渲染或编辑服务。因此 OINK 不提供隐含的公共端点。

启用 PlantUML 却未设置 params.plantuml.svg_image_url,或启用 Diagrams.net 却未设置 params.drawio.drawio_server 时,构建会失败并给出可操作的错误信息。你可以配置受控的本地端点、发布预渲染图片,或者明确选择远程服务:

params:
  plantuml:
    enable: true
    svg: true
    svg_image_url: https://diagrams.internal.example/plantuml/svg/
  drawio:
    enable: true
    drawio_server: https://diagrams.internal.example/

为了继续渲染继承而来的图表示例,OINK 文档回归站显式配置了公共演示服务。这是样例站自己的选择,不是主题默认值,也不应复制到网络隔离站点。

设置:

params:
  offlineSearch: true

Hugo 会为每种语言生成搜索索引。浏览器对拉丁文字查询使用本地 Lunr 搜索,对 CJK 文本使用本地子串回退。查询内容不会离开站点。

为了提高搜索质量,请编写清晰的标题与摘要、设置正确的页面语言,并排除不应进入公开客户端索引的生成页面或敏感页面。本地索引可被每位访客下载,不能充当访问控制手段。

页面级资源加载

OINK 不会给每个页面都加载所有运行时。Mermaid、KaTeX、Markmap、Swagger UI、Redoc、Asciinema、ECharts、Infographic 与轮播,会根据页面功能标记按需选取。不使用某个组件的页面不会收到对应运行时。

同一页包含多个相同组件实例时,运行时仍只会加入一次。在 Hugo 流水线允许的情况下,生产资源会生成指纹,便于提供完整性元数据并使用长期缓存。

第三方来源追踪

VENDOR.json 是随附依赖的机器可读清单。每项依赖都会记录:

  • 名称与固定版本;
  • 原始来源;
  • 适用的许可证文件;
  • 已选取产物的路径与 SHA-256 值;
  • 维护者更新流程。

主题会在 vendor 资源旁保留相应许可证。更新运行时意味着同时更新产物、许可证与 NOTICE 材料、校验值和测试,使整个变更可以一次性审查。

获取离线归档

使用 Oink release 附带的版本化主题归档与 checksum。把两个文件传入隔离环境后运行:

shasum -a 256 -c oink-vX.Y.Z.tar.gz.sha256
tar -xzf oink-vX.Y.Z.tar.gz
mkdir -p product-docs/themes
mv oink-vX.Y.Z product-docs/themes/oink

让隔离站点使用解压后的传统主题:

theme: oink

归档必须包含 go.modhugo.yaml、布局、资源、静态文件、翻译、LICENSENOTICEVENDOR.json。在断网构建中使用前,应先检查归档内容。

验证隔离站点

有意义的网络隔离验收必须同时覆盖构建阶段与浏览器阶段:

  1. 从已经验证的主题归档和空 Hugo 缓存开始;
  2. 阻断出站 HTTP、HTTPS 与 Go Module 代理;
  3. 运行 Hugo 生产构建命令;
  4. 浏览生成结果中的英文与中文页面;
  5. 操作搜索、深色模式、图表、API 文档与内容组件;
  6. 检查全部 HTML 和 CSS 子资源 URL,确认没有意外远程来源。

项目站点回归套件会针对本地主题候选版本执行这些检查。一次成功只能证明被测提交与环境;每个候选版本以及每次随附依赖更新后都应重新验证。

内容安全策略

本地资源让严格的内容安全策略(CSP)更容易实现,但 OINK 不会为所有站点虚构一份万能策略。作者行内 HTML、ECharts unsafe 模式、分析服务、远程规范与自定义集成都可能改变所需指令。

请从能够支持已审查功能的最小策略开始。让 ECharts 保持结构化数据模式,避免任意行内脚本,只为站点主动启用的集成增加远程来源。

4 - 内容组件

OINK 新增的本地、可复用内容组件

OINK 把已经在多个 PGSTY 站点证明具有复用价值的内容组件纳入主题。每个组件都有稳定的作者接口、唯一实例 ID、本地资源和明确的安全边界。站点专用的数据控件仍然留在主题之外。

加载模型

交互式短代码会标记页面实际使用的功能。OINK 随后为每个所需样式或运行时只加入一次,即使页面中存在多个组件实例也不例外。普通页面不会下载从未使用的组件代码。

相对资源与链接参数会经过 Hugo URL 处理,因此部署到 baseURL 子路径时仍然正确。在适用情况下,组件标记还覆盖打印、深色模式、移动端、键盘操作与减少动态效果偏好。

Asciinema

使用 asciinema 播放保存在本地的 .cast 终端录像:

{{< asciinema
  file="oink/demo.cast"
  speed="1.5"
  markers="0:开始,1:完成"
>}}

file 是必填参数,也可以作为第一个位置参数传入。支持的选项包括 themefitwidthheightbothnone)、autoplaylooppreloadspeedstartAtpostercolsrowsidleTimeLimitpauseOnMarkers,以及逗号分隔的 markers

为了离线使用,请把 cast 文件保存在本地。只有作者显式提供远程 URL 时,组件才会访问远端。

ECharts

默认安全模式接受 JSON 或 YAML,并把解析后的值序列化到 application/json 元素中:

{{< echarts height="280px" >}}
xAxis: { type: category, data: [源码, 构建, 发布] }
yAxis: { type: value }
series: [{ type: bar, data: [1, 2, 3] }]
{{< /echarts >}}

height 默认为 400px,并且必须使用安全的 CSS 长度单位。theme 用来选择 ECharts 主题,full=true 则移除通常的正文宽度限制。

旧页面可能包含 JavaScript 围栏代码块与 $fn:name 引用。除非短代码设置 unsafe=true,或站点临时启用以下开关,否则 OINK 会拒绝这种可执行形式:

params:
  content:
    echarts_unsafe: true

该开关只能用于经过审查的迁移过程。新图表应始终采用结构化 JSON/YAML 模式。

Infographic

infographic 使用本地运行时渲染 AntV Infographic DSL:

{{< infographic >}}
infographic list-row-simple-horizontal-arrow
data
  items
    - label 源码
      desc Markdown 与配置
    - label 构建
      desc Hugo Extended
    - label 发布
      desc 静态文件
{{< /infographic >}}

height 接受 auto 或安全 CSS 长度;full=true 会移除通常的正文宽度限制。DSL 会作为数据序列化,而不是作为可执行脚本插入页面。

doc-cardnav-card 共用同一套卡片实现;doc-cardsnav-cards 可以创建一至四列的响应式卡片组。这些别名让现有站点内容继续使用语义最贴切的名称,同时避免复制标记与样式。

{{< nav-cards cols="3" >}}
  {{< nav-card
    title="架构"
    link="/zh/docs/oink/architecture/"
    icon="fa-solid fa-diagram-project"
    desc="了解构建与运行时边界。"
  >}}
  {{< nav-card
    title="部署"
    link="/zh/docs/oink/deployment/"
    badge="仅依赖 Hugo"
  >}}发布静态输出。{{< /nav-card >}}
{{< /nav-cards >}}

了解构建与运行时边界。

部署仅依赖 Hugo

卡片接受 titlelinkimagealticondescaccentbadge。卡片正文可以包含 Markdown 链接。desc 中的 {version} 之类 token,在站点参数存在同名值时会被替换。

把文档卡片放进 doc-carousel,即可生成无障碍横向轮播:

{{< doc-carousel label="OINK 工作流" >}}
  {{< doc-card title="编写" >}}创建成对内容。{{< /doc-card >}}
  {{< doc-card title="构建" >}}运行 Hugo Extended。{{< /doc-card >}}
  {{< doc-card title="验证" >}}检查静态站点。{{< /doc-card >}}
{{< /doc-carousel >}}

label 提供轮播的无障碍名称。方向键与可见的上一个/下一个按钮都能移动轨道;启用减少动态效果偏好时,不必要的动画会被禁用。

折叠块

details 输出原生 detailssummary 元素:

{{% details title="为什么只依赖 Hugo?" closed="false" %}}
已经提交的浏览器资源让消费端构建保持可复现。
{{% /details %}}
为什么只依赖 Hugo?
已经提交的浏览器资源让消费端构建保持可复现。

title 设置摘要。折叠块默认关闭;设置 closed=false 可让它初始展开。

标签页

OINK 沿用 Docsy 的 tabpanetab 创作模型,同时保留导入站点依赖的 selected=true 与空白处理行为:

{{< tabpane text=true >}}
  {{< tab header="本地" selected=true >}}
  使用完整本地主题构建。
  {{< /tab >}}
  {{< tab header="Cloudflare" >}}
  从源分支运行同一条 Hugo 命令。
  {{< /tab >}}
{{< /tabpane >}}
使用完整本地主题构建。
从源分支运行同一条 Hugo 命令。

Markdown 内容应设置 text=true;否则标签页会按代码进行语法高亮。标签页还支持按语言保存选择、禁用标签,以及右对齐条目。生成的标签与面板 ID 会形成正确的 ARIA 对应关系。

参数

param 输出页面参数;页面没有该参数时,会回退到同名站点参数:

当前版本:{{< param version >}}

当前版本:v0.16.0

指定参数不存在时,短代码会让构建失败。这是有意设计:缺少发布版本或仓库信息时,不应悄悄生成误导性文档。

现有富内容能力

OINK 也为继承而来的内容功能提供本地运行时:

  • mermaidmathmarkmap 围栏代码块;
  • swaggeruiredoc API 文档短代码;
  • Docsy blocks、alert、image、include、readfile、cards 等既有短代码。

完整创作参考详见短代码图表和公式

创作规则

  • 优先使用结构化数据,而不是可执行内容。
  • 为图片编写有意义的 alt 文本,并为轮播设置清晰的 label
  • 除非内容确实需要,否则不要启用自动播放。
  • 创建新包装组件时,要在同一页测试多个完全相同的实例。
  • 检查键盘导航、焦点可见性、深浅色主题、移动布局、打印输出与减少动态效果行为。
  • 把带有业务语义的数据组件留在消费站点。

5 - 配置

使用 Hugo 原生设置与职责明确的主题参数配置 OINK

OINK 遵循“原生优先”的配置模型。站点身份、语言、菜单、输出、taxonomy、标记与模块继续放在 Hugo 规定的位置;语义仍然适用的 Docsy 参数也保持原位。只有无法可靠推导的行为选择,OINK 才会增加职责明确的配置。

配置原则

  1. 优先使用 Hugo 配置,不创建主题专用的重复项。
  2. 优先使用成熟的 Docsy 参数,不另造 OINK 同义词。
  3. 品牌、内容、仓库与 UI 选项应放在各自语义位置。
  4. 内部 vendor 路径与模板组装方式不属于公开 API。
  5. 遇到非法值或缺少必需端点时,应尽早失败。

OINK 不提供 oink.enabled 开关,也不建立 params.oink.* 配置树。增加这些配置会制造第二套主题模式,让每项修复、测试和文档都产生歧义。

完整基线配置

以下示例把英文设为首要语言、简体中文设为第二语言:

title: Product Documentation
baseURL: https://docs.example.com/
defaultContentLanguage: en
enableRobotsTXT: true

languages:
  en:
    label: English
    locale: en-US
    weight: 1
    title: Product Documentation
    menus:
      main:
        - { name: Docs, pageRef: /docs, weight: 10 }
        - { name: Blog, pageRef: /blog, weight: 20 }
  zh:
    label: 简体中文
    locale: zh-CN
    weight: 2
    title: 产品文档
    menus:
      main:
        - { name: 文档, pageRef: /docs, weight: 10 }
        - { name: 博客, pageRef: /blog, weight: 20 }

outputs:
  home: [HTML]
  section: [HTML, RSS, print]

markup:
  goldmark:
    renderer:
      unsafe: true
    extensions:
      passthrough:
        enable: true
        delimiters:
          block: [['\[', '\]'], ['$$', '$$']]
          inline: [['\(', '\)']]
  highlight:
    noClasses: false

params:
  logo: icons/logo.svg
  offlineSearch: true
  offlineSearchIndex: summary
  offlineSearchMaxResults: 10
  github_repo: https://github.com/example/product-docs
  github_branch: main
  footer_icp: ''
  footer_icp_url: https://beian.miit.gov.cn/
  copyright:
    authors: Example Authors
    from_year: 2026
  ui:
    showLightDarkModeMenu: true
    quick_links: [docs, blog]
    sidebar_menu_foldable: true
    sidebar_item_overflow: wrap
    breadcrumb_disable: false

module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: 0.160.1

模块版本固定在站点的 go.mod 中。使用传统主题 checkout 时,可以把仓库放在 themes/oink/,并改用 theme: oink

语言

defaultContentLanguage 决定不带路径前缀的首要站点;语言 weight 控制显示顺序;label 是该语言的自称;locale 提供完整的 HTML 与 SEO locale。对于 RTL 语言,还应设置 languageDirection: rtl

文件命名

本站使用并置模型:

content/docs/guide.md
content/docs/guide.zh.md

基本名称相同的文件互为译文,其逻辑页面身份应保持一致。OINK 读取 Hugo 建立的翻译关系,不会根据任意 URL 模式猜测。

选择器状态

语言选择器不需要模式参数。只配置一种语言时隐藏;配置两种或更多语言时,点击语言图标会按 weight 顺序切换到下一种语言,悬停半秒或聚焦图标则打开完整菜单。

当前页面缺少目标译文时,会进入目标语言首页。不要为了让选择器停留在同一路径而生成貌似存在、实际失效的页面 URL。

品牌与代码仓库

请设置站点与各语言的 title 和描述。params.logo 可以指向 Hugo Asset,也可以指向 static/ 下的路径。favicon 与社交分享图应放在文档指定的资源位置。

仓库元数据用于生成“编辑此页”、问题反馈和最后修改记录链接:

params:
  github_repo: https://github.com/example/product-docs
  github_project_repo: https://github.com/example/product
  github_branch: main
  github_subdir: site

在支持的位置,github_project_repo 默认回退到 github_repogithub_subdir 是内容站在 monorepo 中的路径。github_branch 必须能够解析;用于展示的版本号不一定是 Git ref。

OINK 沿用 Docsy 菜单与 UI 参数,并增加职责明确的外壳控制项:

params:
  page_width: normal
  ui:
    quick_links: [docs, blog]
    sidebar_width_min: 220
    sidebar_width_max: 480
    sidebar_item_overflow: wrap
    sidebar_menu_compact: true
    sidebar_menu_foldable: true
    sidebar_root_enabled: true
    sidebar_root_menu: true
    sidebar_search_disable: false
    breadcrumb_disable: false
    showLightDarkModeMenu: true
    page_context_menu:
      enable: true
      links: []
    readingtime:
      enable: true

page_width 接受 normalwidefull,也可以在页面 front matter 中覆盖。侧栏最小与最大值以像素为单位,用来限制桌面端拖动调整的范围。sidebar_item_overflow: wrap 会让长标签换行;其他值保持紧凑的省略号行为。

quick_links 指定外壳中显示的顶层 page reference。请在各语言主菜单中定义相应的本地化名称。

页面上下文菜单在所有视口宽度下都把“复制 Markdown”“查看 Markdown”、编辑、反馈与打印入口放在页面标题旁。links 默认为空,因此站点未主动启用时,不会向外部 AI 服务发送页面信息。自定义链接可使用经过 URL 编码的 {url}{title}{markdown_url} 占位符:

params:
  ui:
    page_context_menu:
      enable: true
      links: []
      # - name: 询问外部助手
      #   icon: fa-solid fa-wand-magic-sparkles
      #   url: https://assistant.example/new?source={markdown_url}&title={title}

首页内容位于 data/home/<language>.yaml;缺少相应语言数据时回退到英文。可配置的顶层区块包括 herometricscapabilitiesprinciplesctafooter。每个区块都可以省略,因此无需复制布局也能得到更精简的首页。例如:

hero:
  eyebrow: 本地优先的产品文档
  title_lines:
    - words:
        - { mark: P, text: roduct, color: red }
        - { mark: D, text: ocs, color: blue }
  lead: 只用 Hugo 构建和交付的技术文档。
  actions:
    - { label: 阅读文档, url: docs/, icon: fa-solid fa-book, style: primary }

footer:
  brand:
    name: Product Docs
    tagline: 支持 **Markdown** 的简短介绍。
    slogan: 让答案离产品更近。
  columns:
    - title: 产品
      links:
        - { label: 概览, url: docs/ }

首页会在通用小页脚上方渲染品牌与导航组成的大页脚。小页脚左侧来自 params.copyright,中间使用可选的 params.footer_icpparams.footer_icp_url,右侧列出所有已配置语言。版权作者与大页脚品牌文字中的 Markdown 会渲染为真实链接与行内标记。

starter 默认使用本地搜索:

params:
  offlineSearch: true
  offlineSearchIndex: summary
  offlineSearchSummaryLength: 70
  offlineSearchMaxResults: 10

offlineSearchIndex 控制每种语言索引中可下载的文本范围,四档范围逐级累加:title 索引标题与分类元数据;heading 增加页面标题;summary 增加描述或摘要;content 再加入完整正文。content 是兼容旧行为的默认值,而多数文档站可从体积更小的 summary 开始。offlineSearchMaxResults 同时约束 Lunr 与 CJK 子串兜底结果数。

每种语言都会得到独立索引。通过 Docsy 既有配置仍可使用托管搜索,但启用它们会显式增加外部服务边界。除非已经决定界面应显示哪一种,否则不要同时配置多个相互竞争的搜索提供方。

内容运行时

纯浏览器运行时

Mermaid 与 KaTeX 会根据内容自动检测;Markmap 需要在站点级启用:

params:
  markmap:
    enable: true
  mermaid:
    theme: default

Swagger UI、Redoc、Asciinema、ECharts、Infographic 与轮播资源会在相应短代码出现时加载。它们的本地运行时路径属于内部实现,不应配置。

服务端点

PlantUML 与 Diagrams.net 需要显式端点:

params:
  plantuml:
    enable: true
    svg: true
    svg_image_url: https://diagrams.internal.example/plantuml/svg/
  drawio:
    enable: true
    drawio_server: https://diagrams.internal.example/

网络隔离站点应保持这些功能关闭,除非上述 URL 可以在隔离网络内部访问。

ECharts 迁移开关

结构化 ECharts 输入默认安全:

params:
  content:
    echarts_unsafe: false

只有迁移包含 JavaScript 且已经审查的旧页面时,才把它设为 true。更好的做法是在最小范围的短代码实例上设置 unsafe=true,随后重写图表并删除例外。

页面级覆盖

Hugo 的 .Param 查找机制允许在 front matter 中覆盖许多站点参数:

---
title: Wide reference
page_width: wide
hide_feedback: true
hide_readingtime: true
ui:
  no_left_sidebar: false
  scrollSpy:
    disable: false
---

只应为真实的内容差异使用覆盖,不要靠逐页设置重建另一套视觉系统。

避免虚假配置

不要暴露:

  • 在“Docsy”与“OINK”外壳之间切换的开关;
  • vendor JavaScript、CSS、字体或内部 partial 的路径;
  • 品牌命名空间下重复的语言或仓库值;
  • 只用于二选一复制实现的开关。

如果站点需要定制产品矩阵或门户,请把该组件留在站点,并使用范围明确的 hook 或短代码。清晰的本地业务功能,优于误导性的全局主题选项。

验证配置变更

修改配置后:

  1. 分别使用最低支持版本与当前验证版本的 Hugo Extended 构建;
  2. 测试每种已配置语言,以及至少一个缺少译文的页面;
  3. 如果同时支持根路径与子路径部署,验证两种 baseURL 输出;
  4. 检查本地搜索与可选运行时请求;
  5. 检查桌面端和移动端外壳、深浅色主题与打印输出。

真正可接受的配置必须能够正确构建并按预期运行,而不只是可以被 YAML 解析。

6 - 部署

一次构建 OINK,再发布其静态输出

OINK 部署包含两个独立阶段:Hugo 先生成完整的 public/ 目录,静态托管服务再发布该目录。请把构建验证与线上验证分开,避免把一次成功的本地命令误认为已经完成生产发布。

生产构建

在站点根目录使用固定版本的 Hugo Extended 运行:

hugo --gc --minify --cleanDestinationDir

--gc 会清理不再使用的缓存资源,--minify 生成生产资源,--cleanDestinationDir 删除上次构建残留的文件。如果 publishDir 不是站点专用输出目录,使用最后一个参数前必须先检查命令目标。

构建应顺利完成,并且不能用忽略告警的方式掩盖缺失内容、端点或资源。上传前请先在本地检查 public/

本地预览

编辑期间运行:

hugo server --disableFastRender

Hugo 开发服务器只能证明源码可以渲染;它不是生产托管服务,实时重载行为也不属于生成后的站点。每次发布前都应执行一次干净的生产构建。

静态托管

任何能够提供目录和文件的主机都可以发布 OINK:

  • 对象存储与 CDN;
  • GitHub Pages、GitLab Pages 或类似的 Git 驱动静态托管;
  • Netlify、Cloudflare Pages 或其他构建并发布的平台;
  • Nginx、Caddy、Apache 或内部文件服务器。

请把 baseURL 设置为生产环境 canonical URL。如果站点发布在 https://example.com/manual/ 之类的子路径下,应包含该路径并进行测试;OINK 的本地资源与组件 URL 设计为支持子路径部署。

Cloudflare Pages

让 Pages 直接连接源分支。OINK 不需要由 GitHub Actions 预先构建并推送孤立 Pages 分支。

当前 starter 使用以下设置:

设置
生产分支 main,或经过审查的源分支
根目录 独立站点目录
构建命令 hugo --gc --minify
构建输出目录 public
HUGO_VERSION 0.164.0
SKIP_DEPENDENCY_INSTALL 1

截至 2026-08-08,Cloudflare Pages v3 构建镜像文档中的默认 Hugo 版本为 0.147.7,低于 OINK 最低要求 0.160.1。应当在 Production 与 Preview 中都显式设置 HUGO_VERSION,不要依赖持续变化的平台默认值。 SKIP_DEPENDENCY_INSTALL=1 可阻止平台的通用依赖安装器增加站点并不需要的前端安装步骤。

预览环境如需使用 Pages 生成的地址作为构建 canonical URL,可以运行:

hugo --gc --minify --baseURL "$CF_PAGES_URL"

Cloudflare 官方文档把 public 列为 Hugo 标准输出目录,并说明了 HUGO_VERSION 覆盖与 CF_PAGES_URL base URL 用法。调整构建镜像或固定 Hugo 版本时,请重新核对平台文档。

参阅 Cloudflare Hugo 指南Cloudflare 构建镜像

网络隔离部署

在断网环境中,应同时传入站点源码与已经验证的主题归档,而不是依赖首次构建时下载 Hugo Module:

  1. 验证主题归档附带的 SHA-256 文件;
  2. 在环境中安装受支持的 Hugo Extended 二进制文件;
  3. 把主题解压到站点的 themes/oink/ 目录;
  4. 设置 theme: oink,并在站点中运行 hugo --gc --minify
  5. public/ 发布到内部静态服务器。

除非已经配置隔离网络内可达的端点,否则请保持 PlantUML 与 Diagrams.net 关闭。外部链接与嵌入内容仍由内容作者负责。

响应头与缓存

带指纹的 CSS 与 JavaScript 可以使用长期 immutable 缓存。HTML、搜索索引、Feed 与站点地图应使用较短缓存或重新验证,以便新部署及时生效。

项目站点提供了适用于部分托管平台的 static/_headers 示例。它只是起点,并非可移植标准。应根据站点实际使用的行内内容与集成审查安全响应头。

预览与生产 URL

canonical、hreflang、Open Graph、Feed 与绝对链接都依赖 baseURL。生产构建应使用生产 URL;如果链接验证或社交元数据需要准确,预览构建可以使用临时地址。

不要把针对预览地址生成的产物直接发布到生产环境;反过来,也不要因为预览中出现有意传入的预览域名就判定失败。

部署验收

每一层都要独立验证:

源码与配置

  • 预期提交与固定主题版本确实存在;
  • baseURL、语言、菜单、仓库元数据与可选端点正确;
  • 未发布草稿或秘密信息没有进入公开内容树。

构建产物

  • 使用固定版本的 Hugo Extended 完成干净生产构建;
  • 英文、中文、Feed、站点地图、搜索索引与 404.html 均存在;
  • 本地资源在根路径与配置的子路径下都能解析;
  • 产物包含所需许可证与归属说明。

托管站点

  • 生产 URL 返回新产物;
  • canonical 与备用语言 URL 使用生产域名;
  • 导航、搜索、语言切换、深色模式、打印和代表性组件在真实浏览器中工作;
  • 重定向、自定义响应头、缓存策略与 404 行为符合配置;
  • “支持网络隔离”的结论有浏览器网络审计作为依据。

绿色构建日志只完成产物阶段;托管检查全部通过后,部署才算完成。

回滚

保留上一份已知可用的静态产物或托管平台部署标识。新版本未通过线上验证时,应先恢复该产物,再诊断源码或平台行为。使用新的、未固定工具链重新构建旧提交,并不等同于恢复原产物。

7 - 迁移现有站点

用 OINK 替换复制的 Docsy 外壳,同时保留站点专用行为

OINK 旨在替换各站点复制的公共外壳、运行时与短代码,而无需批量重写普通正文。安全迁移应按依赖关系删除覆盖项,把产品专用行为留在站点,并在修改生产环境前先验证临时副本。

迁移原则

  • 固定目标实现,不要把生产站点迁移到未固定版本的分支。
  • 删除覆盖项之前先完成清点。
  • 删除公共主题副本,不删除站点业务逻辑。
  • 在 OINK API 兼容的地方,保持内容 URL、front matter 与短代码行为不变。
  • unsafe 或在线例外必须显式声明,并且只在过渡期使用。
  • 分别测试构建产物、浏览器行为与托管站点行为。

固定目标版本

请在 go.mod 中固定已发布标签,或使用完整的版本化归档。预发布评估期间,Hugo Module 站点可以使用被忽略的 Go workspace,在不修改已提交模块版本的情况下解析本地 checkout:

go work init .
go work edit -replace=github.com/pgsty/oink=/absolute/path/to/oink
export HUGO_MODULE_WORKSPACE=go.work
hugo --gc --minify

站点的 hugo.yaml 导入 github.com/pgsty/oink;workspace 只替换本地 checkout。

清点现有覆盖项

把每个站点级文件归入以下四类之一:

类别 处理方式
公共外壳的完全或近似副本 OINK 验证通过后删除
OINK 已提供的可复用组件 删除或机械重命名
范围明确的品牌或产品定制 保留,再缩小到最小 hook
业务专用数据或交互 留在站点

layouts/assets/static/、配置与构建工作流必须一起检查。复制的短代码通常还伴随一份 JavaScript bundle、样式表、vendor 文件和 CI 安装步骤。

迁移配置

搜索与品牌

启用主题提供的本地搜索,并让外壳使用站点自己的 Logo:

params:
  logo: img/product.svg
  offlineSearch: true

继续在原有语义位置使用 titlelanguages.*github_repogithub_project_repogithub_branchpage_widthui.*,不要把它们迁入 oink.* 命名空间。

ECharts 旧内容

旧 Pigsty 页面可能在 ECharts 块中包含 JavaScript。仅在经过审查的过渡期启用:

params:
  content:
    echarts_unsafe: true

新建与已经转换的图表都应使用 JSON 或 YAML。迁移完成后删除站点级开关;确实暂时无法转换时,也应把 unsafe=true 限制在具体短代码上。

字体

旧 Sass 开关 $td-enable-google-fonts: true 现在会选择 OINK 随附的本地 Open Sans,而不会请求 Google Fonts。$td-web-font-path 不再参与当前构建。需要其他字体的站点必须提供获准使用的本地资源及其许可证。

删除公共覆盖项

临时构建证明等价后,可以删除站点中的以下副本:

  • layouts/baseof.html 与公共 docs/blog baseof*.html
  • 公共 navbar、footer、sidebar、目录(TOC)、search、head CSS partial 及相应 hook;
  • 旧的公共品牌文档外壳 partial;
  • asciinemaechartsinfographicdoc-carouseldetailstab/tabpane、card 与 param 短代码副本;
  • 只服务于上述已删除实现的 JavaScript、Lunr 副本、轮播代码与 SCSS;
  • 不再被任何站点资源需要的消费端 PostCSS 与 Autoprefixer 步骤。

应按引用关系删除,而不是直接清空 layouts/。首页、下载页与门户仍可能调用本地 icon、search dialog、blog row 或 tag filter 等 partial。

保留站点专用行为

保留语义属于具体产品的内容与代码:

  • 产品矩阵与兼容性数据;
  • 价格、下载、门户、解决方案与目录页面;
  • 站点专用首页结构;
  • 自定义重定向、响应头、分析或身份集成;
  • 承载业务数据、而非通用呈现逻辑的内容组件。

对于 Pigsty 家族,pgverspgext_matrixpgext_os_matrixhome-docs 以及当前 metric 实现继续留在站点层。

参考站点矩阵

当前迁移计划采用以下边界:

站点 删除或迁移 保留
SILO 公共 docs/blog 外壳、核心短代码与重复运行时;设置 logo: img/silo.svg 首页、下载页与产品数据
PGSTY 公共外壳与核心短代码;设置 logo: img/logo/logo.svg 门户、解决方案与企业页面
SOW 公共 docs/blog 外壳、核心短代码与重复运行时;设置 logo: img/sow.svg 首页与仓库专用内容
Pigsty 公共外壳、核心短代码与重复运行时;设置 logo: icons/logo.svg,并在过渡期审查旧 ECharts 扩展矩阵、首页与价格页、目录样式

该矩阵是清点工作的起点,并不意味着可以删除所有名称相似的文件。必须在目标检出目录中解析实际模板引用。

演练工作流

请在消费站点的临时副本中演练迁移。应用本地 Oink workspace,每次删除一组计划覆盖项,阻断非预期网络与前端工具访问,再执行生产构建:

HUGO_MODULE_WORKSPACE=go.work hugo --gc --minify

演练不得修改源工作区。保留失败副本用于诊断,并记录准确的主题 commit、Hugo 版本、已删除文件与输出数量。

当前证据

最近一次记录的演练发生在 2026-08-08,使用 Hugo Extended 0.164.0

站点 演练结果 HTML 文件数
SILO 删除 20 个公共覆盖项;完整构建中英文内容,OINK 外壳、同源搜索与站点 Logo 生效 1,095
PGSTY 删除 20 个公共覆盖项;构建双语门户,并用临时 docs 页面验证外壳 16
SOW 删除 20 个公共覆盖项;完整构建中英文内容,OINK 外壳、同源搜索与站点 Logo 生效 128
Pigsty 删除 24 个公共覆盖项;保留三个业务矩阵短代码,并启用经过审查的旧 ECharts unsafe 模式 2,473

这些是临时副本的构建结果,不代表四个生产站点已经完成迁移或部署。

生产迁移流程

对每个站点依次执行:

  1. 创建专用迁移分支;
  2. 固定 OINK 候选版本并记录其源码提交;
  3. 每次只删除一组内聚的覆盖项;
  4. 执行干净的 Hugo-only 构建与针对性自动化测试;
  5. 比较具有代表性的首页、文档页、博客页、特殊页面与 404 页面;
  6. 检查移动导航、两种颜色模式、语言切换、搜索、打印与站点保留的业务组件;
  7. 部署预览,并验证真实 URL 与网络请求;
  8. 评审通过后才合并并部署,随后执行生产冒烟测试。

对于 OINK 有意改变外壳的部分,应记录合理差异,而不是强求像素级相同。

回滚

保留迁移前的主题 pin、站点提交与已知可用部署产物。回滚时三者应一致恢复。针对新主题只重新引入一部分随机复制布局,会形成比任一完整版本都更难诊断的混合状态。

8 - 发布流程

发布 Oink 主题并更新其独立项目站点

Oink 把实现、验证、公开发布与部署视为不同状态。一次绿色本地构建是有价值的证据,但它不是公开标签、可下载模块,也不是已经部署的文档更新。

发布状态

状态 必需证据
源码完成 范围、文档、变更日志、归属信息与评审全部完成
验证完成 主题模块与项目站点检查通过
公开发布 pgsty/oink 中存在不可变根标签,并可通过 Go 解析
文档完成 pgsty/oink.pgsty.com 固定并记录该标签
部署完成 托管文档与目标消费站点通过验证

应报告准确状态与证据;不要把本地构建称为发布。

版本管理

主题版本在 github.com/pgsty/oink 使用 vX.Y.Z 这样的根标签。主题现在就是仓库根模块,因此不再使用嵌套的 theme/vX.Y.Z 标签。

项目站点的 version 参数标识发布的站点变体,不会自动成为 Git ref。安装说明与 go.mod 必须使用真实可解析的主题标签。

验证主题仓库

从干净的 pgsty/oink checkout 开始:

  1. 检查源码 diff 与归属信息变更;
  2. 验证 VENDOR.json 中每个文件与 SHA-256;
  3. 确认仓库没有生成的 public/、资源缓存、node_modules/ 或内嵌 example site;
  4. 使用最低版本与当前支持版本的 Hugo Extended,通过 Hugo Module 路径构建最小消费站点;
  5. 检查 module zip,确认布局、资源、翻译、静态文件、许可证与 NOTICE 都存在。

module zip 测试很重要,因为 Go 会从发布模块中排除 vendor 等特殊目录名。Oink 把随附依赖放在 assets/third_party/,确保它们能进入模块发行物。

验证项目站点

pgsty/oinkpgsty/oink.pgsty.com 克隆为同级目录,再用被忽略的 workspace 连接:

cd oink.pgsty.com
go work init .
go work edit -replace=github.com/pgsty/oink=../oink
export HUGO_MODULE_WORKSPACE=go.work
npm install
npm test

检查具有代表性的中英文页面、移动导航、两种颜色模式、本地搜索、打印输出、图表、API 文档与 404 页面。这只能验证候选版本与站点配合正常,不会发布任何仓库。

标记并发布主题

评审完成后,在主题仓库中创建一个不可变的签名根标签:

git tag -s vX.Y.Z -m "Oink vX.Y.Z"
git push origin main vX.Y.Z

推送以及创建 GitHub release 都需要明确授权。标签公开后,从干净环境验证:

hugo mod get github.com/pgsty/[email protected]
hugo mod graph

如果 release 附带离线归档,请发布并独立验证 SHA-256。归档中必须保留 LICENSENOTICEVENDOR.json

更新项目站点

主题标签能够公开解析后,更新独立站点仓库:

hugo mod get github.com/pgsty/[email protected]
hugo mod tidy
npm test

go.modgo.sum、版本参数、变更日志与升级指南一起提交。先验证部署预览,评审通过后再推进生产发布分支。

发布后验证

公开发布后:

  1. 从干净 clone 获取标签并检查签名;
  2. 通过公开 Go proxy 解析模块;
  3. 使用文档命令构建一个全新最小站点;
  4. 打开生产文档,验证模块说明、canonical 链接、语言、搜索与资源;
  5. 验证所有发布归档与 checksum;
  6. 记录最终标签、模块版本、托管 URL 与产物哈希。

热修复与回滚

热修复范围可以更小,但仍要经过同一证据链。绝不能移动或替换已经发布的标签。站点需要回滚时,应恢复到已知产物;主题需要修复时,则发布新的补丁版本。

完成定义

只有批准的标签已经存在、公开模块能够解析、必需检查通过、项目站点固定该标签,并且托管冒烟测试成功,版本才算发布完成。任何未完成项都应按实际状态报告。