This commit is contained in:
mark committed 2026-09-08 08:02:21 -04:00
1 parent f041daeda3
commit 9407c26d2d
6 files changed
+301 -33

No files matched your search

+70 -7
View File
@@ -24,6 +24,26 @@
// template that extends or includes another names only the file.
var TEMPLATES = './templates/';
// All of them. Named here rather than in either caller because this file is
// the one that knows what a template is: sw.js precaches them from this list
// and both renderers load them from it, so there is one place to add the
// next one. A template missing from here is a template nunjucks would have
// to fetch mid-render, which is the one thing loadAll() exists to prevent.
var NAMES = [
'base.njk',
'page.njk',
'book.njk',
'index.njk',
'bookmark.njk',
'reference.njk',
'verses.njk',
'version.njk',
'status.njk',
'notice.njk'
];
scope.TEMPLATE_URLS = NAMES.map(function (name) { return TEMPLATES + name; });
// 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) {
@@ -42,10 +62,50 @@
// worker, so they never go stale.
var env = new nunjucks.Environment(new Loader(), { autoescape: true });
// Every template, in nunjucks' hands before the first render, because an
// include that has to wait for one does not come out whole.
//
// getTemplate answers from the loader's cache by calling straight back,
// and only reaches this loader -- and this loader's promise -- for a
// template it has never seen. Nunjucks compiles {% include %} into a
// waterfall, and the rest of the block it sits in is the waterfall's
// callback while the code after that block runs on regardless. Answered
// in the same tick the two happen in order and the output is whole;
// answered a tick later the render is finished and handed back first,
// and what comes back is missing the include and everything between it
// and the end of its block. The verse list, and then the </select> after
// it -- a picker with a chapter and no verses, on whichever navigation
// happened to be the first to need the template.
//
// Which is why it is all of them and not just the ones that get included:
// whether a template has been reached yet depends on which pages have
// been rendered, and in the worker on whether it has been restarted since.
// That is the whole shape of the bug -- intermittent, and never twice in a
// row. One pass over a few kilobytes already in the cache buys the
// question being settled.
//
// Failures resolve rather than reject: a template that cannot be read now
// will fail again in the render that actually needs it, where the error
// belongs to a request and can be answered with the bare fragment.
var loaded = null;
function loadAll() {
if (!loaded) {
loaded = Promise.all(NAMES.map(function (name) {
return new Promise(function (resolve) {
env.getTemplate(name, true, null, false, function () { resolve(); });
});
}));
}
return loaded;
}
function render(name, context) {
return new Promise(function (resolve, reject) {
env.render(name, context, function (err, out) {
return err ? reject(err) : resolve(out);
return loadAll().then(function () {
return new Promise(function (resolve, reject) {
env.render(name, context, function (err, out) {
return err ? reject(err) : resolve(out);
});
});
});
}
@@ -61,9 +121,11 @@
// base.njk is for.
//
// chrome is what the shell needs and the fragment cannot carry: the
// bookmark, and the navbar picker's model. Both are null from boot.html,
// which has no worker to ask for either, and both render as a hidden
// slot that fills itself if one ever appears.
// bookmark, the navbar picker's model, and the two versions the worker
// was built with. All three are null from boot.html, which has no worker
// to ask for any of them; the first two render as a hidden slot that
// fills itself if one ever appears, and the third renders as nothing,
// because no worker is the whole of what there is to report.
compose: function (fragment, chrome) {
var title = (TITLE_RE.exec(fragment) || ['', ''])[1].trim();
var content = fragment.replace(TITLE_RE, '');
@@ -72,7 +134,8 @@
title: title,
content: content,
mark: (chrome && chrome.mark) || null,
reference: (chrome && chrome.reference) || null
reference: (chrome && chrome.reference) || null,
version: (chrome && chrome.version) || null
});
},