Update Pipeline Variables

While most extensions read pipeline variables and interact with the methods of the referenced object, they can also add or replace pipeline variables. One use case is to define new variables that other extensions or listeners of the same extension can access. This is a way to pass additional data through the pipeline. Another use case is to replace a built-in variable in Antora’s pipeline, perhaps by proxying it. You may want to do this if you need to drastically alter Antora’s behavior and you can’t do it by adding or removed files from a catalog.

Let’s consider the case where we want to modify the playbook to remove private content sources. We can’t change properties on the playbook because the object is frozen. But we can create a new playbook, change it’s properties, and pass our copy back to the pipeline. The following example shows how this would work by listening to the playbookBuilt event (when the playbook is not yet locked) and creating a replacement.

Example 1. exclude-private-content-sources-extension.js
module.exports.register = (pipeline) => {
  pipeline.on('playbookBuilt', function ({ playbook }) {
    playbook = JSON.parse(JSON.stringify(playbook))
    playbook.content.sources = playbook.content.sources.filter((source) => !source.url.startsWith('git@'))
    this.updateVars({ playbook })
  })
}

Notice that the example uses the formal function keyword to declare the listener instead of an arrow function. Defining the function this way gives us access to the standard this keyword, which is a reference to the Pipeline object. When the listener is registered, Antora binds the function to the Pipeline object, allowing the pipeline object to be referenced within the function using the standard this keyword.

Let’s consider another case where we proxy the content catalog to prevent it from registering any aliases. In Example 2, we will listen for the contentClassified event, retrieve the contentCatalog pipeline variable, and replace the variable with a proxy of the object.

Example 2. Replace variable with a proxy of the object
module.exports.register = (pipeline) => {
  pipeline.on('contentClassified', function ({ contentCatalog }) {
    contentCatalog = new Proxy(contentCatalog, {
      get(target, property) {
        return property === 'registerPageAlias' ? () => undefined : target[property]
      },
    })
    this.updateVars({ contentCatalog })
  })
}

Example 2 gives you the starting point to replace the registerPageAlias function with your own implementation.

Variable locking

Once a built-in pipeline variable is deemed established, which is typically after the event in which it was introduced is emitted, that variable becomes locked. There are exceptions to this rule, but by-in-large it holds. A variable that is locked can’t be replaced. Any attempt to do so results in an error.

The built-in variables that are locked, and when they’re locked, are indicated on the Pipeline Event Reference page.

The reason built-in variables are locked is two fold. First, it signals when a variable should be replaced if it must be. Second, it allows the site generator and other extensions to store a local reference to that variable without having to worry about checking whether it was replaced.

A locked variable only prevents that variable itself from being replaced. It’s still possible to modify the object that the variable references, such as to add, update, or remove a property of the object. The one exception is the playbook, which is a frozen object.