Smiling Guard
A New Blog

Include and provide JSON data in Hexo EJS Templates

... with a new Helper and an async function

The three main components of a standard installation of the Static Site Generator Hexo are the template system EJS (Embedded JavaScript Templating), Markdown for the content and Stylus for the styles.

In the template files are the three main tags for driving content:

Scriptlet tag for control flow (no output)

<%
  ... my JavaScript code to process data into the template
%>

Output a value as escaped HTML

<%= myVariable %>

Output of a raw value, usually in the form of a JavaScript function

<%- myFunction() %>

Hexo’s helper system is based on the latter. So you can include a JavaScript file in your template that makes use of the JS Helper in node_modules\hexo\lib\plugins\helper\js.js as follows …

<%- js('/js/dist/myFancyFunctions.js') %>

… which will be rendered to:

<script src="/js/dist//js/dist/qr-code-styling.js"></script>

The Problem

So far and short, so good … but I recently tried to use this way to include a JSON file whose data one of my scripts needed as startup options and I noticed that the above mentioned JS helper unfortunately takes care of the possibly missing file extension js. It doesn’t matter if you only pass the path to the file as a string or if all necessary attributes as an object.

<%- js({
  src: 'js/dist/script-options.json',
  type: 'application/json'
}) %>

This code leads to the following wrong code …

<script src="/js/dist//js/dist/qr-code-styling.json.js" 
       type="application/json"></script>

The JSON Helper

Since Hexo’s developers went a bit over my/the target with the helper’s functionality, I had to build my own JSON helper, which is actually just a slightly customized copy of the original:

themes\landscape\scripts\json-helper.js
const { htmlTag, url_for } = require('hexo-util'); hexo.extend.helper.register('json', function(...args){ let result = '\n'; args.flat(Infinity).forEach(item => { if (typeof item === 'string' || item instanceof String) { // args = String only let path = item; if (!path.endsWith('.json')) { path += '.json'; } result += `<script src="${url_for.call(this, path)}" type="application/json"></script>\n`; } else { // args = Object -> Custom Attributes item.src = url_for.call(this, item.src); item.type = "application/json"; if (!item.src.endsWith('.json')) item.src += '.json'; result += htmlTag('script', { ...item }, '') + '\n'; } }); return result; });

You can find the complete file here.

With this its possible to reference the JSON like this:

<%- json('js/dist/myOptions.json') %>

Bring JSON data to life

However, the helper only allowed me to load the file as such. What was still missing was the loading of the data in the JavaScript of the page itself. The easiest way to achieve that, was to perform a FETCH of the already referenced and loaded file in the SCRIPT block of the template as an immediately invoked async function:

EJS File
<%- js('js/dist/myFancyObjectLibrary.js') %> <%- json({ src: 'js/dist/myOptions.json', id: 'my-options' }) %> <script> (async () => { const response = await fetch(document.getElementById('my-options').src); const options = await response.json(); let obj = new myFancyObject(options); //... do something with the initialized object })(); </script>

Et voilà … Job done.

Syndication

You can interact with this article (applause, criticism, whatever) by mention it in one of your posts or by replying to its syndication on Mastodon, which will be shown here as a Webmention.

In case your blog software can't send Webmentions, you can use this form or send it manually via webmention.app or Telegraph:

Webmentions

No Webmentions yet...

Related