Template Converter
On this page, you’ll learn how to use templates to have full control over the output.
Built-in template engines
By default, Asciidoctor.js supports the following template engines with the corresponding file extensions:
- EJS
-
.ejs
- Handlebars
-
.handlebars
,.hbs
- Nunjucks
-
.nunjucks
,.njk
- Pug
-
.pug
Please note that the dependencies are optional, so you will need to install them explicitly.
For instance, if you want to use Nunjucks, you will need to install the $ npm i nunjucks |
Once the dependency is installed, you can create template files in a directory.
Plain JavaScript templates
Asciidoctor.js also supports templates written in plain JavaScript. In this case, you should write JavaScript files that export a default function:
module.exports = ({ node }) => `<p class="paragraph">${node.getContent()}</p>`
This function will be called with a Template context as argument.
Naming convention
Let’s say, we want to use Nunjucks to write templates. We create a directory named templates and a file named paragraph.njk:
<p class="paragraph">{{ node.getContent() | safe }}</p>
By default, Nunjucks will automatically escape all output for safety.
Here, we are using the built-in |
As mentioned above, the file extension njk is important because it tells Asciidoctor.js that this file is a Nunjucks template. Moreover, the name paragraph is also important as it matches a node name. For reference, here’s the complete list of node’s name:
-
document
-
embedded
-
outline
-
section
-
admonition
-
audio
-
colist
-
dlist
-
example
-
floating-title
-
image
-
listing
-
literal
-
stem
-
olist
-
open
-
page_break
-
paragraph
-
preamble
-
quote
-
thematic_break
-
sidebar
-
table
-
toc
-
ulist
-
verse
-
video
-
inline_anchor
-
inline_break
-
inline_button
-
inline_callout
-
inline_footnote
-
inline_image
-
inline_indexterm
-
inline_kbd
-
inline_menu
-
inline_quoted
You don’t need to create a template for all the nodes. Asciidoctor.js can fallback on a built-in converter. For instance, we can use the built-in HTML 5 converter for every node except for paragraph nodes where we use a custom template.
Templates directory
You can instruct Asciidoctor.js to use a template directory from the CLI with the --template-dir
option (or -T
for short):
$ asciidoctor --template-dir ./templates doc.adoc
You can also configure the template directory using the API:
asciidoctor.convertFile('doc.adoc', { safe: 'safe', backend: 'html5', template_dir: './templates' })
Multiple templates directories
It’s also possible to use more than one template directory.
In this case, we can repeat the --template-dir
option from the CLI:
$ asciidoctor --template-dir ./templates-a --template-dir ./templates-b doc.adoc
In the above command, we are using two templates directories named templates-a and templates-b.
From the API, we will need to define the template_dirs
option:
asciidoctor.convertFile('doc.adoc', { safe: 'safe', backend: 'html5', template_dirs: ['./templates-a', './templates-b'] })
Conflicts resolution
Here’s how Asciidoctor.js resolve conflicts in the following situations:
- Two or more templates are defined for the same node name in the same directory
-
For instance, you have a paragraph.njk and a paragraph.hbs file in your template directory. In this case, the rule is "last one wins" in alphabetical order. Since njk is after hbs in alphabetical order, Asciidoctor.js will use the Nunjucks template over the Handlebars template.
- Two or more templates are defined for the same node in different directories
-
For instance, we have a paragraph.njk in template-a directory and a paragraph.njk in template-b directory. In this case, the rule is still "last one wins" but the order in the
template_dirs
option is important.
If I declare the following:const options = { template_dirs: ['template-a', 'template-b'] }
Then template-b/paragraph.njk will win because it’s effectively the last one. Now, if I change the order in the
template_dirs
option:const options = { template_dirs: ['template-b', 'template-a'] }
Then template-a/paragraph.njk will win!
Please note that it’s not a recommended practice, and you should try to avoid conflicts upstream.
helpers.js file
You can create a helpers.js
file in your template directory.
This file can be used to declare utility functions that can be used in the templates.
For instance, if you are using Handlebars, you might want to register partials or helpers.
Similarly, if you are using Nunjucks, you might want to add custom filters.
If this file exists, Asciidoctor.js will load it (using the Node.js require
directive) and call the configure
function if it’s exported:
module.exports.configure = (context) => {
// ...
}
The context
object will contain an isolated environement (if supported) template engine keyed by template engine name:
handlebars.environment
-
An isolated Handlebars environment obtained via
Handlebars.create()
nunjucks.environment
-
An isolated Nunjucks environment obtained via
nunjucks.configure()
Here’s a concrete example where we add a Nunjucks filter shorten
which returns the first count characters in a string, with count defaulting to 5:
module.exports.configure = (context) => {
context.nunjucks.environment.addFilter('shorten', (str, count) => str.slice(0, count || 5))
}
Isolated environment
An isolated environment means that each environment has its own helpers, partials, filters…
It’s worth noting that an environment is isolated per template directory.
For instance, if we define a value with the same name in two directories the last one won’t overwrite the first one:
module.exports.configure = (context) => {
context.nunjucks.environment.addGlobal('cdn', '//cdn.web.com')
}
module.exports.configure = (context) => {
context.nunjucks.environment.addGlobal('cdn', '//cdn.blog.io')
}
With the above definition, the value cdn
will be equals to:
//cdn.web.com
|
if we are using the template directory web |
//cdn.blog.io
|
if we are using the template directory blog |
Stateless
EJS, Plain JavaScript and Pug templates do not rely on an "environment".
As a result, you don’t need to define a configure
function.
Instead, you can use the helpers.js
file to export values and functions that will be accessible in all templates:
let assetUriScheme
module.exports.version = '1.0.0'
module.exports.getAssetUriScheme = (document) => {
if (assetUriScheme) {
return assetUriScheme
}
const scheme = document.getAttribute('asset-uri-scheme', 'https')
assetUriScheme = (scheme && scheme.trim() !== '') ? `${scheme}:` : ''
return assetUriScheme
}
In the above example, the value version
and the function getAssetUriScheme
will be available on the helpers
key in the template context:
module.exports = function ({ node, _, helpers }) {
const target = node.getAttribute('target')
const document = node.getDocument()
const src = `${helpers.getAssetUriScheme(document)}//www.youtube.com/embed/${target}}` (1)
return `<figure class="video"><iframe src="${src}" frameborder="0"/></figure>`
}
1 | Use the getAssetUriScheme function defined in the helpers.js file |
Template context
Asciidoctor.js will pass the following context to the template:
node
-
An AbstractNode from the Asciidoctor.js AST. Depending on the context, it can be a Section, a Document, a Block…
We recommend reading the JS API documentation to find out what it’s available on each Node. opts
-
An optional JSON of options.
helpers
-
The functions and values exported from the helpers.js file.
Template options
You can configure the template engine using the template_engine_options
option.
Here’s a few examples:
const options = {
template_engine_options: {
nunjucks: {
autoescape: false
},
handlebars: {
noEscape: true
},
pug: {
doctype: 'xml'
},
ejs: {
delimiter: '?',
openDelimiter: '[',
closeDelimiter: ']'
}
}
}
To find out which options you can use, please read the official documentation of your template engine:
Template cache
For performance reasons, templates are cached, but you can disable this feature using the template_cache
option:
asciidoctor.convert(input, { template_cache: false })
It might be useful when you want to configure the same template directory with different options.
In the following example, we want to an XML doctype.
We need to disable the cache otherwise the second conversion will not reconfigure the templates with the doctype: 'xml'
option:
const options = {
safe: 'safe',
doctype: 'inline',
backend: 'html5',
template_dir: '/path/to/templates/pug',
template_cache: false, // disable template cache
}
console.log(asciidoctor.convert(`image:cat.png[]`, options)) // <img src="cat.png"/>
console.log(asciidoctor.convert('image:cat.png[]', Object.assign(options, {
template_engine_options: {
pug: {
doctype: 'xml'
}
}
}))) // <img src="cat.png"></img>