Skip to content

Commit

Permalink
Adds support for dependencyMap event. Working example of automatic …
Browse files Browse the repository at this point in the history
…SSR 🏆
  • Loading branch information
zachleat committed Apr 9, 2021
1 parent b078b2d commit 1e47d66
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 103 deletions.
5 changes: 5 additions & 0 deletions src/Errors/TemplateContentUnrenderedTemplateError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const EleventyBaseError = require("../EleventyBaseError");

class TemplateContentUnrenderedTemplateError extends EleventyBaseError {}

module.exports = TemplateContentUnrenderedTemplateError;
123 changes: 79 additions & 44 deletions src/Template.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const TemplateFileSlug = require("./TemplateFileSlug");
const ComputedData = require("./ComputedData");
const Pagination = require("./Plugins/Pagination");
const TemplateContentPrematureUseError = require("./Errors/TemplateContentPrematureUseError");
const TemplateContentUnrenderedTemplateError = require("./Errors/TemplateContentUnrenderedTemplateError");
const ConsoleLogger = require("./Util/ConsoleLogger");

const debug = require("debug")("Eleventy:Template");
Expand Down Expand Up @@ -58,6 +59,8 @@ class Template extends TemplateContent {
);
this.fileSlugStr = this.fileSlug.getSlug();
this.filePathStem = this.fileSlug.getFullPathWithoutExtension();

this.outputFormat = "fs";
}

get logger() {
Expand All @@ -73,6 +76,10 @@ class Template extends TemplateContent {
this._logger = logger;
}

setOutputFormat(to) {
this.outputFormat = to;
}

setIsVerbose(isVerbose) {
this.isVerbose = isVerbose;
this.logger.isVerbose = isVerbose;
Expand Down Expand Up @@ -159,24 +166,29 @@ class Template extends TemplateContent {
permalinkValue = permalink;
} else if (isPlainObject(permalink)) {
let promises = [];
let order = [];
for (let key in permalink) {
order.push(key);
promises.push(super.render(permalink[key], data, true));
let keys = [];
if(permalink.build) {
keys.push("build");
promises.push(super.render(permalink.build, data, true));
}
if(permalink.external) {
keys.push("external");
promises.push(super.render(permalink.external, data, true));
}

let results = await Promise.all(promises);

permalinkValue = {};
for (let j = 0, k = order.length; j < k; j++) {
permalinkValue[order[j]] = results[j];
permalinkValue = Object.assign({}, permalink);
for (let j = 0, k = keys.length; j < k; j++) {
let key = keys[j];
permalinkValue[key] = results[j];
debug(
"Rendering permalink.%o for %o: %s becomes %o",
order[j],
key,
this.inputPath,
permalink,
permalink[key],
results[j]
);
debugDev("Permalink rendered with data: %o", data);
}
} else if (permalink) {
// render variables inside permalink front matter, bypass markdown
Expand Down Expand Up @@ -204,6 +216,7 @@ class Template extends TemplateContent {
);
}

// TODO add support for a key inside the `permalink` object for this
async usePermalinkRoot() {
if (this._usePermalinkRoot === undefined) {
// TODO this only works with immediate front matter and not data files
Expand All @@ -215,12 +228,6 @@ class Template extends TemplateContent {
return this._usePermalinkRoot;
}

isProcessible(data) {
let rawPermalinkValue = data[this.config.keys.permalink];
let link = this._getRawPermalinkInstance(rawPermalinkValue);
return link.isProcessible;
}

// TODO instead of htmlIOException, do a global search to check if output path = input path and then add extra suffix
async getOutputLocations(data) {
let link = await this._getLink(data);
Expand Down Expand Up @@ -565,7 +572,7 @@ class Template extends TemplateContent {
await this.computedData.processRemainingData(data);
}

async getTemplates(data) {
async getTemplates(data, shouldRender = true) {
if (!Pagination.hasPagination(data)) {
await this.addComputedData(data);

Expand All @@ -583,11 +590,15 @@ class Template extends TemplateContent {
this._templateContent = content;
},
get templateContent() {
if (this._templateContent === undefined) {
// should at least warn here
throw new TemplateContentPrematureUseError(
`Tried to use templateContent too early (${this.inputPath})`
);
if(shouldRender) {
if (this._templateContent === undefined) {
// should at least warn here
throw new TemplateContentPrematureUseError(
`Tried to use templateContent too early (${this.inputPath})`
);
}
} else {
throw new TemplateContentUnrenderedTemplateError(`Tried to use templateContent on unrendered template. You need a valid permalink (or permalink object) to use templateContent on ${this.inputPath}`);
}
return this._templateContent;
},
Expand Down Expand Up @@ -625,10 +636,14 @@ class Template extends TemplateContent {
this._templateContent = content;
},
get templateContent() {
if (this._templateContent === undefined) {
throw new TemplateContentPrematureUseError(
`Tried to use templateContent too early (${this.inputPath} page ${this.pageNumber})`
);
if(shouldRender) {
if (this._templateContent === undefined) {
throw new TemplateContentPrematureUseError(
`Tried to use templateContent too early (${this.inputPath} page ${this.pageNumber})`
);
}
} else {
throw new TemplateContentUnrenderedTemplateError(`Tried to use templateContent on unrendered template. You need a valid permalink (or permalink object) to use templateContent on ${this.inputPath} page ${this.pageNumber}`);
}
return this._templateContent;
},
Expand All @@ -643,34 +658,21 @@ class Template extends TemplateContent {
let pages = await this.getTemplates(data);
await Promise.all(
pages.map(async (page) => {
let content = await page.template._getContent(page.data);
let content = await page.template.render(page.data);

page.templateContent = content;
})
);
return pages;
}

async _getContent(data) {
return await this.render(data);
}

async _write(outputPath, finalContent) {
let shouldWriteFile = true;

if (this.isDryRun) {
shouldWriteFile = false;
}

if (outputPath === false) {
debug(
"Ignored %o from %o (permalink: false).",
outputPath,
this.inputPath
);
return;
}

let lang = {
start: "Writing",
finished: "written.",
Expand Down Expand Up @@ -726,7 +728,14 @@ class Template extends TemplateContent {
async generateMapEntry(mapEntry, to) {
return Promise.all(
mapEntry._pages.map(async (page) => {
let content = await this.renderPageEntry(mapEntry, page);
let content;

// Note that behavior.rendered is overridden when using json or ndjson output
if(mapEntry.behavior.rendered) {
// this reuses page.templateContent, it doesn’t render it
content = await this.renderPageEntry(mapEntry, page);
}

if (to === "json" || to === "ndjson") {
let obj = {
url: page.url,
Expand All @@ -744,6 +753,24 @@ class Template extends TemplateContent {
return obj;
}

if(!mapEntry.behavior.rendered) {
debug(
"Template not written %o from %o (via permalink.behavior).",
page.outputPath,
mapEntry.template.inputPath
);
return;
}

if (!mapEntry.behavior.writeable) {
debug(
"Template not written %o from %o (via permalink: false, permalink.build: false, or a permalink object without a build property).",
page.outputPath,
mapEntry.template.inputPath
);
return;
}

return this._write(page.outputPath, content);
})
);
Expand Down Expand Up @@ -849,24 +876,32 @@ class Template extends TemplateContent {

async getTemplateMapContent(pageMapEntry) {
pageMapEntry.template.setWrapWithLayouts(false);
let content = await pageMapEntry.template._getContent(pageMapEntry.data);
let content = await pageMapEntry.template.render(pageMapEntry.data);
pageMapEntry.template.setWrapWithLayouts(true);

return content;
}

async getTemplateMapEntries() {
async getTemplateMapEntries(dataOverride) {
debugDev("%o getMapped()", this.inputPath);

// Important reminder: This is where the template data is first generated via TemplateMap
let data = await this.getData();
let data = dataOverride || await this.getData();

let rawPermalinkValue = data[this.config.keys.permalink];
let link = this._getRawPermalinkInstance(rawPermalinkValue);

let entries = [];
// does not return outputPath or url, we don’t want to render permalinks yet
entries.push({
template: this,
inputPath: this.inputPath,
data,
behavior: {
ignored: link.isTemplateIgnored(),
rendered: this.outputFormat === "json" || this.outputFormat === "ndjson" || link.isTemplateRendered(),
writeable: link.isTemplateWriteable(),
}
});
return entries;
}
Expand Down
2 changes: 1 addition & 1 deletion src/TemplateConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ class TemplateConfig {
Object.keys(localConfig.filters).length
) {
throw new EleventyConfigError(
`The \`filters\` configuration option was renamed in Eleventy 0.3.3 and removed in Eleventy 1.0. Please use the \`addTransform\` configuration method instead. Read more: https://www.11ty.dev/docs/config/#transforms`
"The `filters` configuration option was renamed in Eleventy 0.3.3 and removed in Eleventy 1.0. Please use the `addTransform` configuration method instead. Read more: https://www.11ty.dev/docs/config/#transforms"
);
}
} catch (err) {
Expand Down
37 changes: 34 additions & 3 deletions src/TemplateMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,20 @@ class TemplateMap {

get userConfig() {
if (!this._userConfig) {
this.config = this.eleventyConfig.getConfig();
// TODO use this.config for this, need to add collections to mergable props in userconfig
this._userConfig = this.eleventyConfig.userConfig;
}

return this._userConfig;
}

get config() {
if(!this._config) {
this._config = this.eleventyConfig.getConfig();
}
return this._config;
}

get tagPrefix() {
return "___TAG___";
}
Expand Down Expand Up @@ -264,10 +270,10 @@ class TemplateMap {
} else {
// is a template entry
let map = this.getMapEntryForInputPath(depEntry);
if (!map.template.isProcessible(map.data)) {
if (map.behavior.ignored) {
map._pages = [];
} else {
map._pages = await map.template.getTemplates(map.data);
map._pages = await map.template.getTemplates(map.data, map.behavior.rendered);

let counter = 0;
for (let page of map._pages) {
Expand Down Expand Up @@ -326,12 +332,34 @@ class TemplateMap {
return this.getMapEntryForInputPath(inputPath);
}.bind(this)
);

await this.populateContentDataInMap(orderedMap);

this.populateCollectionsWithContent();
this.cached = true;

this.checkForDuplicatePermalinks();

await this.config.events.emit("dependencyMap", this.generateDependencyMapEventObject(orderedMap));
}

generateDependencyMapEventObject(orderedMap) {
let entries = [];
for(let entry of orderedMap) {
let ret = {
inputPath: entry.inputPath,
isExternal: !!(entry.data.permalink && entry.data.permalink.external)
};

// TODO `needs: []` array of inputPath or glob? this template uses

for(let page of entry._pages) {
entries.push(Object.assign({}, ret, {
url: page.url
}));
}
}
return entries;
}

// TODO(slightlyoff): hot inner loop?
Expand Down Expand Up @@ -381,6 +409,9 @@ class TemplateMap {
if (!map._pages) {
throw new Error(`Content pages not found for ${map.inputPath}`);
}
if(!map.behavior.rendered) {
continue;
}
try {
for (let pageEntry of map._pages) {
pageEntry.templateContent = await map.template.getTemplateMapContent(
Expand Down
Loading

0 comments on commit 1e47d66

Please sign in to comment.