81 lines
3.3 KiB
JavaScript
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);
|