模板转换器
在本页面中,您将学习如何使用模板来完全控制输出。
内置模板引擎
默认情况下,Asciidoctor.js 支持以下模板引擎及其对应的文件扩展名:
-
EJS
-
.ejs
-
-
Handlebars
-
.handlebars
,.hbs
-
-
Nunjucks
-
.nunjucks
,.njk
-
-
Pug
-
.pug
-
请注意,这些依赖项是可选的,因此您需要明确安装它们。例如,如果您想使用
|
安装完依赖项后,您可以在目录中创建模板文件。
普通 JavaScript 模板
Asciidoctor.js 还支持用纯 JavaScript 编写的模板。在这种情况下,您应该编写一个导出默认函数的 JavaScript 文件:
module.exports = ({ node }) => `<p class="paragraph">${node.getContent()}</p>`
该函数将作为 模板上下文 的参数被调用。
命名约定
假设我们希望使用 Nunjucks 编写模板,我们创建一个名为 templates 的目录,并创建一个名为 paragraph.njk 的文件:
<p class="paragraph">{{ node.getContent() | safe }}</p>
默认情况下,Nunjucks 会自动对所有输出进行转义以保证安全。在此,我们使用内置的 safe 过滤器将输出标记为安全,这样 Nunjucks 就不会对该输出进行转义。 |
如上所述,文件扩展名 .njk 很重要,它告诉 Asciidoctor.js 这是一个 Nunjucks 模板。而且,文件名 paragraph 也很重要,因为它与节点名匹配。以下是节点名的完整列表:
-
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
您不需要为所有节点创建模板。Asciidoctor.js 可以使用内置的转换器来回退。例如,我们可以使用内置的 HTML5 转换器处理所有节点,只有 paragraph 节点使用自定义模板。
模板目录
您可以通过 CLI 使用 --template-dir
选项(或者简写为 -T
)来指示 Asciidoctor.js 使用模板目录:
$ asciidoctor --template-dir ./templates doc.adoc
您还可以使用 API 配置模板目录:
asciidoctor.convertFile('doc.adoc', { safe: 'safe', backend: 'html5', template_dir: './templates' })
多个模板目录
您也可以使用多个模板目录。在这种情况下,您可以重复使用 CLI 中的 --template-dir
选项:
$ asciidoctor --template-dir ./templates-a --template-dir ./templates-b doc.adoc
在上述命令中,我们使用了两个模板目录,分别为 templates-a 和 templates-b。
在 API 中,我们需要定义 template_dirs
选项:
asciidoctor.convertFile('doc.adoc', { safe: 'safe', backend: 'html5', template_dirs: ['./templates-a', './templates-b'] })
冲突解决
Asciidoctor.js 在以下情况中如何解决冲突:
- 同一目录中定义了两个或更多相同节点名的模板
-
例如,您在模板目录中有 paragraph.njk 和 paragraph.hbs 文件。在这种情况下,规则是 “字母顺序中最后一个获胜”。由于 .njk 在字母顺序中排在 .hbs 后面,Asciidoctor.js 会使用 Nunjucks 模板而不是 Handlebars 模板。
- 不同目录中定义了相同节点的模板
-
例如,template-a 目录中有 paragraph.njk,而 template-b 目录中也有 paragraph.njk。在这种情况下,规则仍然是 “字母顺序中最后一个获胜”,但模板目录中的顺序很重要。如果我声明如下:
const options = { template_dirs: ['template-a', 'template-b'] }
那么 template-b/paragraph.njk 会获胜,因为它实际上是最后一个。如果我改变 template_dirs 选项中的顺序:
const options = { template_dirs: ['template-b', 'template-a'] }
那么 template-a/paragraph.njk 会获胜!
请注意,这并不是推荐的做法,您应该尽量避免上游冲突。
helpers.js 文件
您可以在模板目录中创建一个 helpers.js 文件,用于声明可以在模板中使用的工具函数。例如,如果您使用 Handlebars,您可能希望注册部分或帮助函数;类似地,如果您使用 Nunjucks,您可能希望添加自定义过滤器。
如果此文件存在,Asciidoctor.js 将加载它(使用 Node.js require 指令),并在导出时调用 configure 函数:
module.exports.configure = (context) => {
// ...
}
上下文对象将包含一个隔离的环境(如果支持),其中包含按模板引擎名称键入的模板引擎:
- handlebars.environment
-
通过 Handlebars.create() 获取的隔离 Handlebars 环境
- nunjucks.environment
-
通过 nunjucks.configure() 获取的隔离 Nunjucks 环境
以下是一个具体示例,我们添加了一个名为 shorten 的 Nunjucks 过滤器,它返回字符串的前 count 个字符,count 默认为 5:
module.exports.configure = (context) => {
context.nunjucks.environment.addFilter('shorten', (str, count) => str.slice(0, count || 5))
}
隔离环境
隔离环境意味着每个环境都有自己的帮助函数、部分、过滤器等。需要注意的是,每个模板目录都有自己的隔离环境。例如,如果我们在两个目录中定义了相同名称的值,最后一个定义的值不会覆盖第一个:
module.exports.configure = (context) => {
context.nunjucks.environment.addGlobal('cdn', '//cdn.web.com')
}
module.exports.configure = (context) => {
context.nunjucks.environment.addGlobal('cdn', '//cdn.blog.io')
}
在上述定义中,cdn 的值将会是:
-
//cdn.web.com,如果我们使用模板目录 web
-
//cdn.blog.io,如果我们使用模板目录 blog
无状态
EJS、普通 JavaScript 和 Pug 模板不依赖于 “环境”。因此,您不需要定义 configure 函数。相反,您可以在 helpers.js 文件中导出值和函数,这些函数和变量将可以在所有模板中访问:
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
}
在上面的示例中,version
和 getAssetUriScheme
函数将会在模板上下文中的 helpers
键下使用:
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 | 使用在 helpers.js 文件中定义的 getAssetUriScheme 函数 |
模板上下文
Asciidoctor.js 会将以下上下文传递给模板:
- node
-
Asciidoctor.js AST 中的 AbstractNode。根据上下文,它可以是 Section、Document、Block 等。 我们建议阅读 JS API 文档 以了解每个节点中可用的内容。
- opts
-
可选的选项 JSON。
- helpers
-
从 helpers.js 文件中导出的函数和变量。
模板选项
您可以使用 template_engine_options
选项来配置模板引擎。以下是一些示例:
const options = {
template_engine_options: {
nunjucks: {
autoescape: false
},
handlebars: {
noEscape: true
},
pug: {
doctype: 'xml'
},
ejs: {
delimiter: '?',
openDelimiter: '[',
closeDelimiter: ']'
}
}
}
要了解可以使用哪些选项,请阅读您所使用的模板引擎的官方文档:
模板缓存
出于性能考虑,模板会被缓存,但您可以使用 template_cache
选项禁用此功能:
asciidoctor.convert(input, { template_cache: false })
当您希望使用不同的选项配置相同的模板目录时,这可能会很有用。在以下示例中,我们希望设置 XML 文档类型。如果不禁用缓存,第二次转换将不会重新配置带有 doctype: 'xml'
选项的模板:
const options = {
safe: 'safe',
doctype: 'inline',
backend: 'html5',
template_dir: '/path/to/templates/pug',
template_cache: false, // 禁用模板缓存
}
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>