The jsTACS template engine
940 words, 5-minute read
Publican uses jsTACS – JavaScript Templating and Caching System – as its templating engine. Unlike other engines, there’s no special files or syntax. You just use JavaScript template literals such as:
${ data.title }
String literal expressions #
An ${ expression }
must return a value to render something on the page, e.g.
${ "str" + "ing" } // string
${ 2 + 2 } // 4
${ (new Date()).getUTCFullYear() } // current year (four digits)
Strings and numbers are rendered as-is. The values:
null
,undefined
andNaN
are rendered as an empty string.true
,false
and0
are rendered as text.- Arrays, Sets, and Maps have every value output. There’s no need for
.join('')
unless you want a specific character between each item.
Content data
properties #
Content values for each page are available in a data
object. For example, show the page title, word count, and content:
<h1>${ data.title }</h1>
<p>This article has ${ data.wordCount } words.</p>
${ data.content }
Global tacs
properties #
Global values which apply throughout the site are available in a tacs
object. For example, ensure links use the correct server root:
<p><a href="${ tacs.root }">home page</a></p>
The tacs
value is available in publican.config.js
so you can append other global values and functions. The following example sets today’s date, the site language, and a tacs.formatDate()
function:
publican.config.js
import { Publican, tacs } from 'publican';
// create Publican object
const publican = new Publican();
// global date and language
tacs.today = new Date();
tacs.language = 'en-US';
// global format date function
tacs.formatDate = d => {
return new Intl.DateTimeFormat(
tacs.language,
{ dateStyle: 'long' }
).format( d )
};
// build site
await publican.build();
You can use these to show the current date in human-readable format:
<p>Last build on ${ tacs.formatDate( tacs.today ) }</p>
toArray()
conversion #
The toArray()
function converts any value, object, Set, or Map to an array so you can apply methods such as .map()
and .filter()
, e.g.
<!-- output any value -->
${ toArray( data.something ).map(m => `Value ${ m }`).join(', ') }
include()
templates #
Templates can include other template files with:
${ include(<filename>) }
where <filename>
is relative to the template directory. The example default.html
below includes _partials/header.html
:
src/template/default.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>${ data.title }</title>
</head>
<body>
${ include('_partials/header.html') }
<main>
${ data.content }
</main>
</body>
</html>
_partials/header.html
includes _partials/nav.html
:
src/template/_partials/header.html
<header>
${ include('_partials/nav.html') }
<h1>${ data.title }</h1>
</header>
_partials/nav.html
has no further includes:
src/template/_partials/nav.html
<nav><a href="${ tacs.root }">Home</a><nav>
Template literals in markdown #
You can use template literals in markdown content but some care may be necessary to avoid problems with the HTML conversion. Simple expressions will often work as expected:
<!-- this should work -->
## ${ data.title }
However, expressions inside or between code blocks may not execute and more complex expressions break the markdown to HTML parser.
<!-- this will fail -->
${ data.all.map(i => i.title) }
You can work around these issues in a number of ways:
Use a double-bracket
${{ expression }}
. This denotes a real expression irrespective of where it resides in the markdown (they are stripped from the content before HTML conversion).Use HTML snippets in your markdown file, e.g.
A markdown paragraph. <p>This ${ "HTML block" } is skipped by the markdown parser.</p>
Simplify expressions using custom jsTACS functions.
Only use complex expressions in HTML content or template files. These are not processed by the markdown parser.
Runtime expressions #
!{ expression }
identifies expressions that are ignored during the build but converted to ${ expression }
at the end and remain in the rendered file. Publican can therefore create sites that are mostly static, with islands of dynamic values rendered at runtime.
Consider the following content:
src/content/index.md
---
title: Home page
---
My home page.
It uses the default template:
src/template/default.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>${ data.title }</title>
</head>
<body>
<main>
<h1>${ data.title }</h1>
${ data.content }
</main>
<footer>
<p>Today's date is !{ data.today }</p>
</footer>
</body>
</html>
Publican builds the following static HTML page. The title and content have rendered, but !{ data.today }
has become ${ data.today }
:
build/index.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Home page</title>
</head>
<body>
<main>
<h1>Home page</h1>
<p>My home page.</p>
</main>
<footer>
<p>Today's date is ${ data.today }</p>
</footer>
</body>
</html>
This partially-rendered template can be used in a framework such as Express.js with jsTACS as its rendering engine. It can dynamically set the data.today
property on every page visit:
import express from 'express';
import { templateEngine } from 'jstacs';
const
app = express(),
port = 8181;
app.engine('html', templateEngine);
app.set('views', './build/');
app.set('view engine', 'html');
// render template at ./templates/index.html
app.get('/', (req, res) => {
res.render('index', {
today: (new Date()).toUTCString()
});
});
app.listen(port, () => {
console.log(`Express started on port ${port}`);
});