Replace Generator Functions

Antora’s site generator is implemented as a sequence of steps. Each step is performed by a function. These functions are known as generator functions. Generator functions may contain functions for key substeps a generator function may perform, such as convertDocument.

The generator retrieves these functions from the generator context. These functions can also be retrieved and replaced programmatically by an Antora extension. As such, these generator functions are designed to be replaceable, providing a more powerful way to extend Antora.

Replacing functions in an extension does put the code at greater risk of breaking between major versions of Antora.

Get the functions

Like with context variables, generator functions are available from the generator context. The generator functions can be retrieved by calling the getFunctions method inside the listener of the contextStarted event, or at any point thereafter. This method returns an object of key-value pairs, where the keys are the function names and the values are the function objects.

The built-in generator functions are not available until the contextStarted event is emitted.

Here’s an example that shows how to retrieve the aggregateContent function in an extension, assuming this is bound to the generator content.

this.once('contextStarted', () => {
  const { aggregateContent } = this.getFunctions()
})

One reason to retrieve a generator function is to use it as a utility. For example, if you need your extension to load AsciiDoc as Antora does, you might retrieve the loadAsciiDoc function for this purpose. Here’s an example:

this.once('contentClassified', (contentCatalog, siteAsciiDocConfig) => {
  const { loadAsciiDoc } = this.getFunctions()
  const page = contentCatalog.resolvePage('ROOT::index.adoc')
  const scopedAsciiDocConfig = contentCatalog.getComponentVersion(page.src.component, page.src.version).asciidoc
  const doc = loadAsciiDoc(page, contentCatalog, scopedAsciiDocConfig || siteAsciiDocConfig)
  const sections = doc.findBy({ context: 'section' }, (it) => it !== doc.getHeader())
  console.log(sections.map((it) => it.getTitle()))
})

Another reason to retrieve a generator function is to decorate (aka wrap) it. In this case, you’ll need to replace the original function with the decorated one. Alternately, you can replace the original function with your own implementation.

Replace a function

Replacing generator functions gives you the ability to override steps in Antora’s site generator.

The generator functions can be replaced by passing functions to the replaceFunctions method. This method accepts the same object signature that the getFunctions method returns, where the keys are the function names and the values are the function objects.

You only have to pass functions to replaceFunctions that you want to replace. The generator will default to using the built-in functions for any function that’s not replaced.

Here’s an example that shows how to replace the publishFiles function in an extension:

module.exports.register = function () {
  this.replaceFunctions({
    async publishFiles () {
      console.log('Not publishing today')
      return []
    }
  })
}

When the functions are replaced matters. If you replace a function directly in the register method (before the contextStarted event is emitted), it will stop Antora from requiring and registering the corresponding built-in function. If you replace a function in the contextStarted listener, the function will replace the corresponding built-in function that has already been required and registered.

The contextStarted event gives you the opportunity to delegate to (i.e., wrap) a built-in function, as shown here:

module.exports.register = function () {
  this.once('contextStarted', () => {
    const { publishFiles: publishFilesDelegate } = this.getFunctions()
    this.replaceFunctions({
      async publishFiles (playbook, catalogs) {
        console.log('It\'s publish time!')
        return publishFilesDelegate.call(this, playbook, catalogs)
      }
    })
  })
}

When replacing a function, you must adhere to the function’s signature as defined in Function reference. Like with the register function and event listener functions, the generator functions are automatically bound to the generator context.

Function reference

The list of functions that can be replaced by an extension, shown along with their signatures, are as follows:

  • aggregateContent(playbook): Promise<Object>

  • buildNavigation(contentCatalog, siteAsciiDocConfig): NavigationCatalog

  • classifyContent(playbook, contentAggregate, siteAsciiDocConfig): ContentCatalog

  • convertDocument(file, contentCatalog, siteAsciiDocConfig): File

  • convertDocuments(contentCatalog, siteAsciiDocConfig): void

  • createPageComposer(playbook, contentCatalog, uiCatalog, env): Function

  • extractAsciiDocMetadata(doc): Object

  • loadAsciiDoc(file, contentCatalog, config): Document

  • loadUi(uiCatalog): Promise<UiCatalog>

  • mapSite(playbook, publishablePages): File[]

  • produceRedirects(playbook, contentCatalog): File[]

  • publishFiles(playbook, catalogs): Promise<Object[]>

  • resolveAsciiDocConfig(playbook): Object

To learn more about these functions, consult the Antora source code.