扩展教程

如果您了解Antora扩展的工作原理,并对相关概念有扎实的掌握,本页面提供了一个端到端教程,通过深入示例引导您全面了解并充分利用Antora的这一功能。

在本教程中,我们将创建一个定位未列出页面的扩展,这些页面无法从导航中访问。该扩展将首先检索每个组件版本的导航树。然后,它将遍历该组件版本中的页面,并定位任何未在该导航树中找到的页面。如果找到任何未列出的页面,它将为每个页面记录警告。如果配置为这样做,它还将在导航中的专用类别下添加这些页面。

这个示例让您有机会使用扩展可用的大部分功能。我们将创建扩展,在playbook中注册它,配置它,最后启用Antora运行。让我们开始吧。

创建扩展

首先,您需要创建扩展。让我们将扩展文件命名为 unlisted-pages-extension.js,并将其放在playbook旁边的 lib/ 文件夹中,以便在playbook存储库中整洁地组织。

使用示例1中显示的源代码填充扩展文件。接下来的部分将分析此代码的功能。

示例1. lib/unlisted-pages-extension.js
module.exports.register = function ({ config }) {
  const { addToNavigation, unlistedPagesHeading = '未列出的页面' } = config
  const logger = this.getLogger('unlisted-pages-extension')
  this
    .on('navigationBuilt', ({ contentCatalog }) => {
      contentCatalog.getComponents().forEach(({ versions }) => {
        versions.forEach(({ name: component, version, navigation: nav, url: defaultUrl }) => {
          const navEntriesByUrl = getNavEntriesByUrl(nav)
          const unlistedPages = contentCatalog
            .findBy({ component, version, family: 'page' })
            .filter((page) => page.out)
            .reduce((collector, page) => {
              if ((page.pub.url in navEntriesByUrl) || page.pub.url === defaultUrl) return collector
              logger.warn({ file: page.src, source: page.src.origin }, '检测到未列出的页面')
              return collector.concat(page)
            }, [])
          if (unlistedPages.length && addToNavigation) {
            nav.push({
              content: unlistedPagesHeading,
              items: unlistedPages.map((page) => {
                return { content: page.asciidoc.navtitle, url: page.pub.url, urlType: 'internal' }
              }),
              root: true,
            })
          }
        })
      })
    })
}

function getNavEntriesByUrl (items = [], accum = {}) {
  items.forEach((item) => {
    if (item.urlType === 'internal') accum[item.url.split('#')[0]] = item
    getNavEntriesByUrl(item.items, accum)
  })
  return accum
}

让我们暂停一下,逐步分解这个扩展的功能。

扩展如何工作

扩展首先通过导出注册函数开始,Antora在需要扩展文件后立即调用该函数。注册函数绑定到生成器上下文,可以使用该上下文添加监听器。该函数通过对象解构接受扩展的配置对象作为唯一参数。然后,它继续从配置对象中提取几个配置键,以自定义其行为。

module.exports.register = function ({ config }) {
  const { addToNavigation, unlistedPagesHeading = '未列出的页面' } = config
}

接下来,扩展创建一个命名记录器,用于报告未列出的页面。它通过在上下文上调用getLogger来创建一个命名记录器。这个方法反过来需要Antora提供的@antora/logger模块,然后将名称传递给其默认函数以创建子记录器。

const logger = this.getLogger('unlisted-pages-extension')

然后,扩展为navigationBuilt事件添加了一个监听器。由于扩展需要访问导航,这是生成器中检查导航树的正确机会。为了访问导航和页面,监听器使用对象解构从上下文变量中检索contentCatalog对象。在页面转换后,会发出navigationBuilt事件,这样可以访问每个页面的导航标题。

this
  .on('navigationBuilt', ({ contentCatalog }) => {
  })

当调用navigationBuilt事件的监听器时,它从内容目录中检索每个组件版本的导航树,以及有关组件版本的一些信息,以定位其页面。

contentCatalog.getComponents().forEach(({ versions }) => {
  versions.forEach(({ name: component, version, navigation: nav, url: defaultUrl }) => {
  })
})

为了更容易在导航中找到页面,扩展提供了一个辅助函数,通过URL为每个导航条目创建查找表,忽略任何重复项。

function getNavEntriesByUrl (items = [], accum = {}) {
  items.forEach((item) => {
    if (item.urlType === 'internal') accum[item.url.split('#')[0]] = item
    getNavEntriesByUrl(item.items, accum)
  })
  return accum
}

然后,扩展使用此辅助函数为每个导航创建查找表:

const navEntriesByUrl = getNavEntriesByUrl(nav)

现在真正的工作开始了。扩展返回到内容目录中,查找当前组件版本中的所有页面,将该列表过滤,仅查找可发布的页面(即具有out属性的页面)。然后,通过比较资源URL来检查页面是否在导航中找到。如果找不到匹配项,它将使用记录器记录警告,并将页面添加到返回的收集器中。

const unlistedPages = contentCatalog
  .findBy({ component, version, family: 'page' })
  .filter((page) => page.out)
  .reduce((collector, page) => {
    if ((page.pub.url in navEntriesByUrl) || page.pub.url === defaultUrl) return collector
    logger.warn({ file: page.src, source: page.src.origin }, '检测到未列出的页面')
    return collector.concat(page)
  }, [])

让我们更仔细地看一下这个警告消息。

logger.warn({ file: page.src, source: page.src.origin }, '检测到未列出的页面')

请注意,我们将一个对象作为第一个参数传递,将消息作为第二个参数。作为第一个参数传递的对象的键将合并到结构化日志消息中。Antora的记录器为filesource键提供了自定义格式化程序,用于输出漂亮的日志消息。file键应指向具有path键的对象,如果适用,还应具有abspath键。提供此内容的最简单方法是传递虚拟文件的src属性,该属性包含有关文件位置的所有必要信息。origin键应指向虚拟文件上的src.origin属性,该属性提供有关内容来源的信息。还可以使用line键传递可选的行号。自定义格式化程序将所有这些信息编译成格式化消息,以帮助用户定位相关文件。

最后,如果扩展找到未列出的页面,它将根据配置添加到导航中的新类别,并添加特殊标题。

if (unlistedPages.length && addToNavigation) {
  nav.push({
    content: unlistedPagesHeading,
    items: unlistedPages.map((page) => {
      return { content: page.asciidoc.navtitle, url: page.pub.url, urlType: 'internal' }
    }),
    root: true,
  })
}

addToNavigation变量来自扩展条目上的配置键add_to_navigation。Antora会自动将配置键名称转换为驼峰命名法,以使其与JavaScript中的变量命名约定一致。

移除未列出的页面

与其将未列出的页面添加到导航中,您可以选择从站点中将它们移除。这是限制发布页面的一种方式。

unlistedPages.forEach((page) => contentCatalog.removeFile(page))

如果选择这种方式,您可能希望删除未列出页面的警告,或将其降级为信息或调试级别。

现在扩展已编写,并且您了解它的功能,是时候注册它了。

注册扩展

要注册扩展,您需要在playbook的antora.extensions键中添加一个对其的require请求条目。在我们的情况下,require请求是从playbook文件到扩展文件的相对路径。

antora:
  extensions:
  - ./lib/unlisted-pages-extension.js

扩展将在下次运行Antora时被调用。但是,由于此扩展是可配置的,我们将使用更正式的条目格式,以便为这些配置键腾出空间。

配置扩展

要注册带有配置的扩展,您需要在playbook的antora.extensions键中为其添加一个映射条目。这样做时,您将在require键中定义require请求,为其他配置键腾出空间。

antora:
  extensions:
  - require: ./lib/unlisted-pages-extension.js
    add_to_navigation: true
    unlisted_pages_heading: 孤立页面

如果希望扩展仅在使用--extension CLI选项指定时才使用,还需要设置idenabled键。

antora:
  extensions:
  - id: unlisted-pages
    enabled: false
    require: ./lib/unlisted-pages-extension.js
    add_to_navigation: true
    unlisted_pages_heading: 孤立页面

现在,只有在运行Antora时通过--extension=unlisted-pages传递扩展时,扩展才会运行。

当扩展接受配置时,将其注册到playbook中总是明智的,即使您不希望默认启用它。

使用扩展

剩下的就是在运行Antora时使用扩展。如果扩展已启用(默认情况下是启用的),您只需要像往常一样运行Antora并传递playbook文件即可:

$ antora antora-playbook

如果扩展未启用,您需要在运行Antora时使用--extension CLI选项来启用它:

$ antora --extension=unlisted-pages antora-playbook.yml

如果playbook中有未列出的页面,您将看到类似于以下警告消息:

[12:02:02.532] WARN (unlisted-pages-extension): detected unlisted page
    source: /path/to/worktree (refname: main <worktree>, start path: docs)
    file: modules/ROOT/pages/name-of-page.adoc

如果add_to_navigation键为true,则您还会在导航树底部的未列出页面类别中找到该页面。

要解决未列出页面的问题,请找到适当的导航文件并为未列出的页面添加条目,然后再次运行Antora以检查您的工作。

恭喜!您已经创建了您的第一个Antora扩展。