Files
bible/assets/js/render.js
T
2026-09-04 11:59:56 -04:00

81 lines
3.3 KiB
JavaScript

// The renderer, shared by the two things that ever build a page: the service
// worker, which composes every navigation once it is installed, and boot.html,
// which composes the one navigation that arrives before there is a worker to
// do it.
//
// Sharing it is the whole point. A shell only the worker could build is what
// made a first visit show a spinner and reload; with the same templates and
// the same code reachable from the page, the first visit is already the app
// and the worker is what makes the second one fast and the hundredth one
// offline.
//
// Loaded as a plain script by both -- importScripts in the worker, a <script>
// tag in the page -- so it defines a global rather than exporting. Nunjucks
// must already be loaded.
(function (scope) {
'use strict';
// A fragment is a <title> and one root element. The root's data-template
// names the template that wraps it; compile_bible.py writes both.
var TITLE_RE = /<title>([\s\S]*?)<\/title>/i;
var TEMPLATE_RE = /data-template="([a-z0-9_-]+)"/i;
// Templates live in one directory and are named by file within it, so a
// template that extends or includes another names only the file.
var TEMPLATES = './templates/';
// readText(path) -> Promise<string> is the only thing the two callers do
// differently: the worker reads through its cache, the page over the network.
scope.createRenderer = function (readText) {
// Nunjucks ships a WebLoader built on XMLHttpRequest, which does not exist
// in a service worker. This one is readText and nothing else.
function Loader() {}
Loader.prototype.async = true;
Loader.prototype.getSource = function (name, callback) {
readText(TEMPLATES + name).then(
function (src) { callback(null, { src: src, path: name, noCache: false }); },
function (err) { callback(err); });
};
// Compiled templates are held for the lifetime of whatever loaded this. In
// the worker an edit to any of them changes VERSION, which starts a new
// worker, so they never go stale.
var env = new nunjucks.Environment(new Loader(), { autoescape: true });
function render(name, context) {
return new Promise(function (resolve, reject) {
env.render(name, context, function (err, out) {
return err ? reject(err) : resolve(out);
});
});
}
return {
render: render,
// A fragment becomes a whole document. page.njk is the fallback for one
// that names no template of its own.
//
// content is already escaped -- compile_bible.py escapes as it writes and
// notice.njk comes out of nunjucks -- which is what the |safe on it in
// base.njk is for.
compose: function (fragment, mark) {
var title = (TITLE_RE.exec(fragment) || ['', ''])[1].trim();
var content = fragment.replace(TITLE_RE, '');
var name = (TEMPLATE_RE.exec(content) || ['', 'page'])[1] + '.njk';
return render(name, {
title: title,
content: content,
mark: mark || null
});
},
// A page that could not be delivered, in the shape of a real fragment so
// that it composes into a document and swaps into #content like one.
notice: function (heading, text) {
return render('notice.njk', { heading: heading, text: text });
}
};
};
})(self);