From 8d1d79d8fbddaf996afca64077fce84ded7b495c Mon Sep 17 00:00:00 2001 From: Sam Thorogood Date: Wed, 16 Dec 2020 14:34:23 +1100 Subject: [PATCH 1/2] add basic ondemand mode --- cmd.js | 2 ++ src/Eleventy.js | 70 ++++++++++++++++++++++++++++++++++++++----- src/EleventyServe.js | 16 +++++++++- src/Template.js | 10 ++----- src/TemplateWriter.js | 53 ++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 16 deletions(-) diff --git a/cmd.js b/cmd.js index 8aeb5f288..eb834001f 100755 --- a/cmd.js +++ b/cmd.js @@ -24,6 +24,7 @@ try { "quiet", "version", "watch", + "ondemand", "dryrun", "help", "serve", @@ -80,6 +81,7 @@ try { } else if (argv.help) { console.log(elev.getHelp()); } else if (argv.serve) { + elev.setOnDemand(argv.ondemand); elev.watch().then(function () { elev.serve(argv.port); }); diff --git a/src/Eleventy.js b/src/Eleventy.js index 0e210d5db..fad792cce 100644 --- a/src/Eleventy.js +++ b/src/Eleventy.js @@ -79,8 +79,14 @@ class Eleventy { */ this.formatsOverride = null; + /** @member {Boolean} */ + this.isOnDemand = false; + + /** @member {Object} */ + this._onDemandBuild = {}; + /** @member {Object} - tbd. */ - this.eleventyServe = new EleventyServe(); + this.eleventyServe = new EleventyServe(this._requestBuild.bind(this)); /** @member {String} - Holds the path to the input directory. */ this.rawInput = input; @@ -97,6 +103,30 @@ class Eleventy { this.watchTargets.watchJavaScriptDependencies = this.config.watchJavaScriptDependencies; } + async _requestBuild(p) { + if (!this.isOnDemand) { + return; + } + + p = TemplatePath.join(this.outputDir, p); + + let requests = []; + if (p.endsWith('/')) { + requests.push(p + 'index.html'); + } else if (!p.endsWith('.html')) { + requests.push(p, p + '/index.html'); + } else { + requests.push(p); + } + + for (let request of requests) { + let run = this._onDemandBuild[request]; + if (run) { + return run(); + } + } + } + getNewTimestamp() { if (performance) { return performance.now(); @@ -244,6 +274,7 @@ class Eleventy { let writeCount = this.writer.getWriteCount(); let skippedCount = this.writer.getSkippedCount(); let copyCount = this.writer.getCopyCount(); + let onDemandCount = this.writer.getOnDemandCount(); let slashRet = []; @@ -253,11 +284,19 @@ class Eleventy { ); } - slashRet.push( - `Wrote ${writeCount} ${simplePlural(writeCount, "file", "files")}${ - skippedCount ? ` (skipped ${skippedCount})` : "" - }` - ); + if (onDemandCount) { + slashRet.push( + `Prepared ${onDemandCount} ${simplePlural(onDemandCount, "file", "files")}` + ); + } + + if (writeCount || skippedCount || onDemandCount === 0) { + slashRet.push( + `Wrote ${writeCount} ${simplePlural(writeCount, "file", "files")}${ + skippedCount ? ` (skipped ${skippedCount})` : "" + }` + ); + } if (slashRet.length) { ret.push(slashRet.join(" / ")); @@ -380,6 +419,16 @@ Verbose Output: ${this.verboseMode}`); } } + /** + * Updates the verbose mode of Eleventy. + * + * @method + * @param {Boolean} isOnDemand - Shall Eleventy run in on demand mode when serving? + */ + setOnDemand(isOnDemand) { + this.isOnDemand = isOnDemand; + } + /** * Reads the version of Eleventy. * @@ -759,9 +808,14 @@ Arguments: await this.config.events.emit("beforeBuild"); try { - let promise = this.writer.write(); + if (this.isOnDemand) { + this._onDemandBuild = await this.writer.prepareOnDemand(); + debug(`Generated ${Object.keys(this._onDemandBuild).length} on-demand candidates…`); + } else { + let promise = this.writer.write(); + ret = await promise; + } - ret = await promise; await this.config.events.emit("afterBuild"); } catch (e) { EleventyErrorHandler.initialMessage( diff --git a/src/EleventyServe.js b/src/EleventyServe.js index 6730e33e4..051f60eaf 100644 --- a/src/EleventyServe.js +++ b/src/EleventyServe.js @@ -6,7 +6,9 @@ const config = require("./Config"); const debug = require("debug")("EleventyServe"); class EleventyServe { - constructor() {} + constructor(requestBuild) { + this._requestBuild = requestBuild; + } get config() { return this.configOverride || config.getConfig(); @@ -152,6 +154,18 @@ class EleventyServe { this.cleanupRedirect(this.savedPathPrefix); let options = this.getOptions(port); + + // if we're on ondemand mode, this will force a build of the file: otherwise, it's a noop + options.middleware = [ + (req, res, next) => { + let p = req.url.slice(1); + this._requestBuild(p).catch((err) => { + // TODO: do something with this error + debug(`Could not requestBuild for request: ${req.url}`); + }).then(() => next()); + } + ]; + this.server.init(options); // this needs to happen after `.getOptions` diff --git a/src/Template.js b/src/Template.js index ab99c3f67..5afde1752 100755 --- a/src/Template.js +++ b/src/Template.js @@ -653,13 +653,9 @@ class Template extends TemplateContent { return content; } - async writeMapEntry(mapEntry) { - await Promise.all( - mapEntry._pages.map(async (page) => { - let content = await this.renderPageEntry(mapEntry, page); - return this._write(page.outputPath, content); - }) - ); + async writePageEntry(mapEntry, page) { + let content = await this.renderPageEntry(mapEntry, page); + return this._write(page.outputPath, content); } // TODO this but better diff --git a/src/TemplateWriter.js b/src/TemplateWriter.js index f8e9ed3b3..6c1f3a8cf 100755 --- a/src/TemplateWriter.js +++ b/src/TemplateWriter.js @@ -34,6 +34,7 @@ class TemplateWriter { this.isDryRun = false; this.writeCount = 0; this.skippedCount = 0; + this.onDemandCount = 0; // TODO can we get rid of this? It’s only used for tests in `get eleventyFiles`` this.passthroughAll = isPassthroughAll; @@ -61,6 +62,7 @@ class TemplateWriter { restart() { this.writeCount = 0; this.skippedCount = 0; + this.onDemandCount = 0; debugDev("Resetting counts to 0"); } @@ -249,6 +251,53 @@ class TemplateWriter { return promises; } + async prepareOnDemand() { + let onDemandBuild = {}; + + // TODO: this models write() + + let paths = await this._getAllPaths(); + await this.writePassthroughCopy(paths); + + await this._createTemplateMap(paths); + debug("Template map created."); + + if ( + this.incrementalFile && + this.eleventyFiles + .getPassthroughManager() + .isPassthroughCopyFile(paths, this.incrementalFile) + ) { + return onDemandBuild; + } + + for (let mapEntry of this.templateMap.getMap()) { + let {template: tmpl, _pages: pages} = mapEntry; + + for (let page of pages) { + // in ondemand, building with no permalink isn't supported + const {outputPath} = page; + if (outputPath === false) { + continue; + } + this.onDemandCount++; + + // return the same promise if requested multiple times + let p; + onDemandBuild[outputPath] = () => { + if (p === undefined) { + p = tmpl.writePageEntry(mapEntry, page); + } + return p.then(() => { + console.log(`Built ${request}…`); + }); + }; + } + } + + return onDemandBuild; + } + async write() { let paths = await this._getAllPaths(); let promises = []; @@ -302,6 +351,10 @@ class TemplateWriter { getSkippedCount() { return this.skippedCount; } + + getOnDemandCount() { + return this.onDemandCount; + } } module.exports = TemplateWriter; From 5894f3c1699b8405843fa9275a1a2fd59b16c48d Mon Sep 17 00:00:00 2001 From: Sam Thorogood Date: Wed, 16 Dec 2020 14:48:08 +1100 Subject: [PATCH 2/2] fix broken normal mode --- src/TemplateWriter.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/TemplateWriter.js b/src/TemplateWriter.js index 6c1f3a8cf..edd1fef9b 100755 --- a/src/TemplateWriter.js +++ b/src/TemplateWriter.js @@ -186,10 +186,12 @@ class TemplateWriter { async _writeTemplate(mapEntry) { let tmpl = mapEntry.template; - return tmpl.writeMapEntry(mapEntry).then(() => { - this.skippedCount += tmpl.getSkippedCount(); - this.writeCount += tmpl.getWriteCount(); - }); + await Promise.all( + mapEntry._pages.map((page) => tmpl.writePageEntry(mapEntry, page)) + ); + + this.skippedCount += tmpl.getSkippedCount(); + this.writeCount += tmpl.getWriteCount(); } async writePassthroughCopy(paths) {