Custom Exporter Extension
With relatively little effort, you can create your own exporter extension for Assembler.
That’s because Assembler exposes a configure
function that sets and runs the assembly process.
All you need to do is provide the converter.
Primer
An exporter is an Antora extension. Thus, to start, you create the basic structure of an Antora extension.
module.exports.register = function ({ config }) {}
Next, you define a converter. A converter consists of the following properties:
- convert
-
A function that converts the assembly file to the target format. Typically, this function passes the AsciiDoc source (by way of stdin) to an external command such as
asciidoctor-pdf
to convert it to the target format. You can use Antora’srunCommand
helper function to invoke the external command. - backend
-
The target backend (the backend that will be set during conversion). If not specified, defaults to the file extension without the leading dot (e.g.,
epub
). - extname
-
The file extension of the target format (e.g.,
.epub
) - mediaType
-
The MIME type of the target format (e.g.,
application/epub+zip
).
const converter = {
convert,
backend: 'ext',
extname: '.ext',
mediaType: 'application/ext',
}
The convert function has the following API:
async function convert (file, convertAttributes, buildConfig)
The file parameter is the assembly file.
You are typically only interested in the file.contents
property, which contains the AsciiDoc source buffer.
The convertAttributes parameter is an object of AsciiDoc attributes to pass to the converter.
These attributes can be converted to CLI options by calling .toArgs('-a', command)
, where -a
is the CLI option flag and command
is the external command.
The buildConfig parameter provides both the command and the cwd.
You then pass generator context, converter, and extension config to the configure
function provided by Assembler.
this.require('@antora/assembler').configure(this, converter, config)
Here’s how it looks all together:
module.exports.register = function ({ config }) {
const converter = {
convert,
backend: 'ext',
extname: '.ext',
mediaType: 'application/ext',
}
this.require('@antora/assembler').configure(this, converter, config)
}
The example in the next section provides a full working implementation.
Example
Here’s an example of how to create an EPUB extension that uses Asciidoctor EPUB3 by default.
'use strict'
const runCommand = require('@antora/run-command-helper')
const fsp = require('node:fs/promises')
const ospath = require('node:path')
const DEFAULT_COMMAND = 'asciidoctor-epub3'
module.exports.register = function ({ config }) {
const converter = {
convert,
backend: 'epub3',
extname: '.epub',
mediaType: 'application/epub+zip',
}
this.require('@antora/assembler').configure(this, converter, config)
}
async function convert (doc, convertAttributes, buildConfig) {
const { cwd = process.cwd(), command = await getDefaultCommand(cwd) } = buildConfig
const args = convertAttributes
.toArgs('-a', command)
.concat('-o', convertAttributes.outfile, '-')
await runCommand(
command,
args,
{ parse: true, cwd, stdin: doc.contents, stdout: 'print', stderr: 'print' }
)
}
function getDefaultCommand (cwd) {
return fsp.access(ospath.join(cwd, 'Gemfile.lock')).then(
() => `bundle exec ${DEFAULT_COMMAND}`,
() => DEFAULT_COMMAND
)
}
Notice that the extension delegates to Assembler to configure itself.
It passes the convert
function alongside metadata about the target format.
For now, an exporter extension has to depend on Antora’s runCommand helper package (@antora/run-command-helper).
In the future, it will be possible to retrieve this function from the bound generator context (e.g., this.getHelpers().runCommand
).