Awesome Asciidoctor.js: Server Side Rendering with Font Awesome 5
With Font Awesome 5 icons can be rendered as SVG elements using JavaScript. This implementation includes an API that can be used to do Server Side Rendering. By rendering server side you save the browser the effort required to download additional files or perform the rendering calculations.
For reference, the built-in HTML5 converter in Asciidoctor.js 1.5.9 is using Font Awesome 4.7.0 and the icons are rendered as CSS pseudo-elements and styled with the Font Awesome
font-family.
In a previous article, we explained how to create a custom HTML5 converter. In this article, we are going to see how we can use this knowledge to render icons as SVG using the Font Awesome JavaScript API.
Using the Font Awesome JavaScript API
Let’s take a quick look at the Font Awesome JavaScript API.
You can install the library using npm
:
$ npm i @fortawesome/fontawesome-svg-core
In addition we also need to install the SVG icons:
$ npm i @fortawesome/free-solid-svg-icons \ @fortawesome/free-regular-svg-icons \ @fortawesome/free-brands-svg-icons
Font Awesome provides 3 sets of icons:
-
solid
-
regular
-
brands
Here we install them all but you can choose to install just one set of icons.
So now we can start using the Font Awesome JavaScript API.
The first thing to do is to add icons to the library:
const library = require('@fortawesome/fontawesome-svg-core').library;
const fas = require('@fortawesome/free-solid-svg-icons').fas
const far = require('@fortawesome/free-regular-svg-icons').far
const fab = require('@fortawesome/free-brands-svg-icons').fab
library.add(fas, far, fab)
Then we can use the icon
function to find an icon by name and get the SVG:
const icon = require('@fortawesome/fontawesome-svg-core').icon
const flaskIcon = icon({ iconName: 'flask' })
console.log(flaskIcon) // { type: 'icon', prefix: 'fa', iconName: 'flask', icon: [...] }
console.log(flaskIcon.html) // [ '<svg...><path ...></path></svg>' ]
If the icon does not exist, the function will return undefined
:
console.log(icon({ iconName: '404' })) // undefined
Also make sure to specify the prefix
attribute when you search for a brand icon:
console.log(icon({ iconName: 'gitlab' })) // undefined
console.log(icon({ prefix: 'fab', iconName: 'gitlab' })) // { type: 'icon', prefix: 'fab', iconName: 'gitlab', icon: [...] }
Or when the icon is available in two styles (regular far
and solid fas
):
console.log(icon({ prefix: 'far', iconName: 'address-book' })) // { type: 'icon', prefix: 'far', iconName: 'address-book', icon: [...] }
console.log(icon({ prefix: 'fas', iconName: 'address-book' })) // { type: 'icon', prefix: 'fas', iconName: 'address-book', icon: [...] }
Server-side Rendering with a custom HTML5 converter
Now that we have learned how to use the Font Awesome JavaScript API, let’s write a custom converter to use it:
class TemplateConverter {
constructor () {
this.baseConverter = asciidoctor.Html5Converter.$new()
const inlineImage = (node) => { /* */ }
this.templates = {
inline_image: inlineImage
}
}
convert (node, transform, opts) {
const template = this.templates[transform || node.node_name]
if (template) {
return template(node)
}
return this.baseConverter.convert(node, transform, opts)
}
}
asciidoctor.ConverterFactory.register(new TemplateConverter(), ['html5'])
1 | This function controls how the inline_image element will be converted to HTML. |
Here I will focus on the implementation of the inlineImage
function.
Now that we know how the Font Awesome API is working, we can implement the inlineImage
function.
const inlineImage = (node) => {
if (node.getType() === 'icon'
&& node.getDocument().isAttribute('icons', 'svg')) {
const search = {}
search.iconName = node.getTarget()
if (node.hasAttribute('prefix')) {
search.prefix = node.getAttribute('prefix')
}
const faIcon = icon(search)
if (faIcon) {
return faIcon.html
}
} else {
return this.baseConverter.$inline_image(node)
}
}
1 | Check that the node is an icon (ie. inline_image can also convert images) |
2 | Check that the document’s attribute named icons is equals to svg |
3 | Get the icon’s name using getTarget() function |
4 | If defined, add the prefix to the search |
5 | If the icon was found, return the SVG representation |
6 | If the node is not an icon or if the document’s attribute named icons is not svg , delegate to the default HTML5 converter |
And a bit of CSS
To render an SVG icon effectively we need to add a few styles in the page. To do that, we are using a Docinfo processor:
const dom = require('@fortawesome/fontawesome-svg-core').dom;
const registry = asciidoctor.Extensions.create()
registry.docinfoProcessor(function () {
const self = this
self.atLocation('head')
self.process(function () {
return `<style>${dom.css()}</style>`
})
})
1 | dom.css() returns a String representing all the styles required to display icons at the correct size |
Usage
const input = `You can enable icon:flask[] experimental features on icon:gitlab[prefix=fab] GitLab.`
const options = {
header_footer: true,
safe: 'safe',
extension_registry: registry,
attributes: { icons: 'svg' }
}
console.log(asciidoctor.convert(input, options))
1 | It will produce a full HTML5 page with SVG icons |
And here’s the result:
You can enable experimental features on GitLab.
Bonus: Adding transformations
We can go one step further and add transformations:
const inlineImage = (node) => {
if (node.getType() === 'icon' && node.getDocument().isAttribute('icons', 'svg')) {
const transform = {}
if (node.hasAttribute('rotate')) {
transform.rotate = node.getAttribute('rotate')
}
if (node.hasAttribute('flip')) {
const flip = node.getAttribute('flip')
if (flip === 'vertical' || flip === 'y' || flip === 'v') {
transform.flipY = true
} else {
transform.flipX = true
}
}
const options = {}
options.transform = transform
if (node.hasAttribute('title')) {
options.title = node.getAttribute('title')
}
options.classes = []
if (node.hasAttribute('size')) {
options.classes.push(`fa-${node.getAttribute('size')}`)
}
if (node.getRoles()) {
options.classes = node.getRoles().map(value => value.trim())
}
const meta = {}
if (node.hasAttribute('prefix')) {
meta.prefix = node.getAttribute('prefix')
}
meta.iconName = node.getTarget()
const faIcon = icon(meta, options)
if (faIcon) {
return faIcon.html
}
} else {
return this.baseConverter.$inline_image(node)
}
}
1 | Use the rotate attribute to rotate the icon |
2 | Use the flip attribute to flip the icon vertically or horizontally |
3 | Set the title attribute if the title is defined |
4 | Add the size attribute as a class (prefixed by fa- ) |
5 | Add the roles as HTML classes |
Here’s an example of what we can do:
.Size & title
Do you want to drink a small icon:cocktail[sm] or a tall icon:beer[2x,title=pint] ?
.Fixed-width
icon:ruler-vertical[fw] vertical ruler +
icon:ruler-horizontal[fw] horizontal ruler
.Rotate
icon:flag[rotate=90] +
icon:flag[rotate=180] +
icon:flag[rotate=270] +
icon:flag[flip=horizontal] +
icon:flag[flip=vertical]
.Spin and pulse
We are working on it icon:cog[spin], please wait icon:spinner[role=fa-pulse]
.Roles
icon:heart[role=is-primary] icon:heart[role=is-success] icon:heart[role=is-warning] icon:heart[role=is-danger]
And here’s the result:
Do you want to drink a small or a tall ?
vertical ruler
horizontal ruler
We are working on it , please wait