254 lines
7.3 KiB
JavaScript
254 lines
7.3 KiB
JavaScript
|
const { TemplatePath } = require("@11ty/eleventy-utils");
|
|||
|
|
|||
|
const TemplateLayoutPathResolver = require("./TemplateLayoutPathResolver");
|
|||
|
const TemplateContent = require("./TemplateContent");
|
|||
|
const TemplateData = require("./TemplateData");
|
|||
|
|
|||
|
const templateCache = require("./TemplateCache");
|
|||
|
// const debug = require("debug")("Eleventy:TemplateLayout");
|
|||
|
const debugDev = require("debug")("Dev:Eleventy:TemplateLayout");
|
|||
|
|
|||
|
class TemplateLayout extends TemplateContent {
|
|||
|
constructor(key, inputDir, extensionMap, eleventyConfig) {
|
|||
|
if (!eleventyConfig) {
|
|||
|
throw new Error("Expected `eleventyConfig` in TemplateLayout constructor.");
|
|||
|
}
|
|||
|
|
|||
|
let resolver = new TemplateLayoutPathResolver(key, inputDir, extensionMap, eleventyConfig);
|
|||
|
let resolvedPath = resolver.getFullPath();
|
|||
|
|
|||
|
super(resolvedPath, inputDir, eleventyConfig);
|
|||
|
|
|||
|
if (!extensionMap) {
|
|||
|
throw new Error("Expected `extensionMap` in TemplateLayout constructor.");
|
|||
|
}
|
|||
|
|
|||
|
this.extensionMap = extensionMap;
|
|||
|
this.key = resolver.getNormalizedLayoutKey();
|
|||
|
this.dataKeyLayoutPath = key;
|
|||
|
this.inputPath = resolvedPath;
|
|||
|
this.inputDir = inputDir;
|
|||
|
}
|
|||
|
|
|||
|
getKey() {
|
|||
|
return this.key;
|
|||
|
}
|
|||
|
|
|||
|
getFullKey() {
|
|||
|
return TemplateLayout.resolveFullKey(this.dataKeyLayoutPath, this.inputDir);
|
|||
|
}
|
|||
|
|
|||
|
getCacheKeys() {
|
|||
|
return new Set([this.dataKeyLayoutPath, this.getFullKey(), this.key]);
|
|||
|
}
|
|||
|
|
|||
|
static resolveFullKey(key, inputDir) {
|
|||
|
return TemplatePath.join(inputDir, key);
|
|||
|
}
|
|||
|
|
|||
|
static getTemplate(key, inputDir, eleventyConfig, extensionMap) {
|
|||
|
let config = eleventyConfig.getConfig();
|
|||
|
if (!config.useTemplateCache) {
|
|||
|
return new TemplateLayout(key, inputDir, extensionMap, eleventyConfig);
|
|||
|
}
|
|||
|
|
|||
|
let fullKey = TemplateLayout.resolveFullKey(key, inputDir);
|
|||
|
if (!templateCache.has(fullKey)) {
|
|||
|
let layout = new TemplateLayout(key, inputDir, extensionMap, eleventyConfig);
|
|||
|
|
|||
|
templateCache.add(layout);
|
|||
|
debugDev("Added %o to TemplateCache", key);
|
|||
|
|
|||
|
return layout;
|
|||
|
}
|
|||
|
|
|||
|
return templateCache.get(fullKey);
|
|||
|
}
|
|||
|
|
|||
|
async getTemplateLayoutMapEntry() {
|
|||
|
return {
|
|||
|
// Used by `TemplateLayout.getTemplate()`
|
|||
|
key: this.dataKeyLayoutPath,
|
|||
|
inputDir: this.inputDir,
|
|||
|
|
|||
|
// used by `this.getData()`
|
|||
|
frontMatterData: await this.getFrontMatterData(),
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
async getTemplateLayoutMap() {
|
|||
|
if (!this.cachedLayoutMap) {
|
|||
|
this.cachedLayoutMap = new Promise(async (resolve, reject) => {
|
|||
|
try {
|
|||
|
// For both the eleventy.layouts event and cyclical layout chain checking (e.g., a => b => c => a)
|
|||
|
let layoutChain = new Set();
|
|||
|
layoutChain.add(this.inputPath);
|
|||
|
|
|||
|
let cfgKey = this.config.keys.layout;
|
|||
|
let map = [];
|
|||
|
let mapEntry = await this.getTemplateLayoutMapEntry();
|
|||
|
|
|||
|
map.push(mapEntry);
|
|||
|
|
|||
|
while (mapEntry.frontMatterData && cfgKey in mapEntry.frontMatterData) {
|
|||
|
// Layout of the current layout
|
|||
|
let parentLayoutKey = mapEntry.frontMatterData[cfgKey];
|
|||
|
|
|||
|
let layout = TemplateLayout.getTemplate(
|
|||
|
parentLayoutKey,
|
|||
|
mapEntry.inputDir,
|
|||
|
this.eleventyConfig,
|
|||
|
this.extensionMap
|
|||
|
);
|
|||
|
|
|||
|
// Abort if a circular layout chain is detected. Otherwise, we'll time out and run out of memory.
|
|||
|
if (layoutChain.has(layout.inputPath)) {
|
|||
|
throw new Error(
|
|||
|
`Your layouts have a circular reference, starting at ${map[0].key}! The layout at ${layout.inputPath} was specified twice in this layout chain.`
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
// Keep track of this layout so we can detect duplicates in subsequent iterations
|
|||
|
layoutChain.add(layout.inputPath);
|
|||
|
|
|||
|
// reassign for next loop
|
|||
|
mapEntry = await layout.getTemplateLayoutMapEntry();
|
|||
|
|
|||
|
map.push(mapEntry);
|
|||
|
}
|
|||
|
|
|||
|
this.layoutChain = Array.from(layoutChain);
|
|||
|
|
|||
|
resolve(map);
|
|||
|
} catch (e) {
|
|||
|
reject(e);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
return this.cachedLayoutMap;
|
|||
|
}
|
|||
|
|
|||
|
async getLayoutChain() {
|
|||
|
if (!Array.isArray(this.layoutChain)) {
|
|||
|
await this.getTemplateLayoutMap();
|
|||
|
}
|
|||
|
|
|||
|
return this.layoutChain;
|
|||
|
}
|
|||
|
|
|||
|
async getData() {
|
|||
|
if (!this.dataCache) {
|
|||
|
this.dataCache = new Promise(async (resolve, reject) => {
|
|||
|
try {
|
|||
|
let map = await this.getTemplateLayoutMap();
|
|||
|
let dataToMerge = [];
|
|||
|
for (let j = map.length - 1; j >= 0; j--) {
|
|||
|
dataToMerge.push(map[j].frontMatterData);
|
|||
|
}
|
|||
|
|
|||
|
// Deep merge of layout front matter
|
|||
|
let data = TemplateData.mergeDeep(this.config, {}, ...dataToMerge);
|
|||
|
delete data[this.config.keys.layout];
|
|||
|
|
|||
|
resolve(data);
|
|||
|
} catch (e) {
|
|||
|
reject(e);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
return this.dataCache;
|
|||
|
}
|
|||
|
|
|||
|
// Do only cache this layout’s render function and delegate the rest to the other templates.
|
|||
|
async getCachedCompiledLayoutFunction() {
|
|||
|
if (!this.cachedCompiledLayoutFunction) {
|
|||
|
this.cachedCompiledLayoutFunction = new Promise(async (resolve, reject) => {
|
|||
|
try {
|
|||
|
let rawInput = await this.getPreRender();
|
|||
|
let renderFunction = await this.compile(rawInput);
|
|||
|
resolve(renderFunction);
|
|||
|
} catch (e) {
|
|||
|
reject(e);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
return this.cachedCompiledLayoutFunction;
|
|||
|
}
|
|||
|
|
|||
|
async getCompiledLayoutFunctions() {
|
|||
|
let layoutMap = await this.getTemplateLayoutMap();
|
|||
|
let fns = [];
|
|||
|
|
|||
|
try {
|
|||
|
fns.push({
|
|||
|
render: await this.getCachedCompiledLayoutFunction(),
|
|||
|
});
|
|||
|
|
|||
|
if (layoutMap.length > 1) {
|
|||
|
let [currentLayout, parentLayout] = layoutMap;
|
|||
|
let { key, inputDir } = parentLayout;
|
|||
|
|
|||
|
let layoutTemplate = TemplateLayout.getTemplate(
|
|||
|
key,
|
|||
|
inputDir,
|
|||
|
this.eleventyConfig,
|
|||
|
this.extensionMap
|
|||
|
);
|
|||
|
|
|||
|
// The parent already includes the rest of the layout chain
|
|||
|
let upstreamFns = await layoutTemplate.getCompiledLayoutFunctions();
|
|||
|
for (let j = 0, k = upstreamFns.length; j < k; j++) {
|
|||
|
fns.push(upstreamFns[j]);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return fns;
|
|||
|
} catch (e) {
|
|||
|
debugDev("Clearing TemplateCache after error.");
|
|||
|
templateCache.clear();
|
|||
|
throw e;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static augmentDataWithContent(data, templateContent) {
|
|||
|
data = data || {};
|
|||
|
|
|||
|
if (templateContent !== undefined) {
|
|||
|
data.content = templateContent;
|
|||
|
data.layoutContent = templateContent;
|
|||
|
}
|
|||
|
|
|||
|
return data;
|
|||
|
}
|
|||
|
|
|||
|
// Inefficient? We want to compile all the templatelayouts into a single reusable callback?
|
|||
|
// Trouble: layouts may need data variables present downstream/upstream
|
|||
|
// This is called from Template->renderPageEntry
|
|||
|
async render(data, templateContent) {
|
|||
|
data = TemplateLayout.augmentDataWithContent(data, templateContent);
|
|||
|
|
|||
|
let compiledFunctions = await this.getCompiledLayoutFunctions();
|
|||
|
|
|||
|
for (let { render } of compiledFunctions) {
|
|||
|
templateContent = await render(data);
|
|||
|
data = TemplateLayout.augmentDataWithContent(data, templateContent);
|
|||
|
}
|
|||
|
|
|||
|
return templateContent;
|
|||
|
}
|
|||
|
|
|||
|
resetCaches(types) {
|
|||
|
super.resetCaches(types);
|
|||
|
|
|||
|
delete this.dataCache;
|
|||
|
delete this.layoutChain;
|
|||
|
delete this.cachedLayoutMap;
|
|||
|
delete this.cachedCompiledLayoutFunction;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
module.exports = TemplateLayout;
|