The jsTACS template engine
(updated )
1,340 words, 7-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,undefinedandNaNare rendered as an empty string.true,falseand0are 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 HTML conversion.
Double bracket ${{ expressions }} #
Simpler expressions will work as expected, but you can use double-bracket ${{ expressions }} and !{{ expressions }} when necessary. These denote real expressions irrespective of where they reside in the markdown.
Use double brackets in code blocks when an expression must be parsed rather than rendered as syntax:
markdown code block
```js
// render expression as code:
// console.log( '${ data.title }' );
console.log( '${ data.title }' );
// parse expression:
// console.log( 'This Page Heading' );
console.log( '${{ data.title }}' );
```Single line expressions #
An expression on a single line such as:
markdown source
${ data.title }
is rendered inside an HTML paragraph tag:
HTML output
<p>This Page's Title</p>
If you need a different tag, you can use an HTML snippet, e.g.
markdown source
<div>${ data.title }</div>
Or you can use HTML comments when no tags are required. These are ignored by the browser and removed during minification.
markdown source
<!-- -->${ data.title }<!-- -->
HTML entities #
You can use HTML entities to avoid expression parsing:
- for
!{, use!{ - for
${, use${ - for
`backticks, use` - for
{and}brackets, use{and}
Further options #
If you still encounter problems, you can:
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.
Template literals in JavaScript #
Publican can process JavaScript files like any other text file, e.g.
example.js
---
alert: Hello world!
---
console.log('${ data.alert }');
results in:
console.log('Hello World!');
Simpler expressions will work as expected, but you can not use backtick (`) characters anywhere in your code. This will cause an error:
example.js fails
---
alert: Hello world!
---
// conditional comment
${ data.alert ? `// ${ data.alert }` : ''}
Use string concatenation to fix the issue:
example.js works
---
alert: Hello world!
---
// conditional comment
${ data.alert ? '// ' + data.alert : ''}
Runtime expressions #
You can define !{ expressions } that are ignored during the build but converted to ${ expression } at the end so they remain in the rendered file. Publican can create sites that are mostly static, with islands of dynamic content rendered at runtime (also using jsTACS).
Consider the following content:
src/content/index.md
---
title: Home page
---
My home page.
It uses a 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. It renders ${ data.title } and ${ data.content }, but !{ data.today } becomes ${ 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>
You can use this partially-rendered template in a framework such as Express.js with jsTACS as its rendering engine. The dynamic data.today value updates 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}`);
});
Simpler !{ expressions } work as expected, but you can not use backtick (`) characters anywhere inside them. This fails:
src/template/_partials/footer.html fails
<footer>
!{ data.today ? `<p>Today's date is ${ data.today }</p>` : '' }
</footer>
One way around this restriction is to define global jsTACS functions that process values at runtime, e.g.
import express from 'express';
import { tacs, templateEngine } from 'jstacs';
tacs.fn = {
today( date => date ?
`<p>Today's date is ${ date }</p>` :
''
)
};
You can use global functions inside content or templates:
src/template/_partials/footer.html works
<footer>
!{ tacs.fn.today( data.today ) }
</footer>