diff options
| author | Shipwreckt <me@shipwreckt.co.uk> | 2025-10-31 20:02:14 +0000 |
|---|---|---|
| committer | Shipwreckt <me@shipwreckt.co.uk> | 2025-10-31 20:02:14 +0000 |
| commit | 7a52ddeba2a68388b544f529d2d92104420f77b0 (patch) | |
| tree | 15ddd47457a2cb4a96060747437d36474e4f6b4e /node_modules/@11ty/eleventy/src/TemplateLayout.js | |
| parent | 53d6ae2b5568437afa5e4995580a3fb679b7b91b (diff) | |
Changed from static to 11ty!
Diffstat (limited to 'node_modules/@11ty/eleventy/src/TemplateLayout.js')
| -rw-r--r-- | node_modules/@11ty/eleventy/src/TemplateLayout.js | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/node_modules/@11ty/eleventy/src/TemplateLayout.js b/node_modules/@11ty/eleventy/src/TemplateLayout.js new file mode 100644 index 0000000..f8526f2 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/TemplateLayout.js @@ -0,0 +1,240 @@ +import { TemplatePath } from "@11ty/eleventy-utils"; +import debugUtil from "debug"; + +import TemplateLayoutPathResolver from "./TemplateLayoutPathResolver.js"; +import TemplateContent from "./TemplateContent.js"; +import TemplateData from "./Data/TemplateData.js"; +import layoutCache from "./LayoutCache.js"; + +// const debug = debugUtil("Eleventy:TemplateLayout"); +const debugDev = debugUtil("Dev:Eleventy:TemplateLayout"); + +class TemplateLayout extends TemplateContent { + constructor(key, extensionMap, eleventyConfig) { + if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") { + throw new Error("Expected `eleventyConfig` in TemplateLayout constructor."); + } + + let resolver = new TemplateLayoutPathResolver(key, extensionMap, eleventyConfig); + let resolvedPath = resolver.getFullPath(); + + super(resolvedPath, eleventyConfig); + + if (!extensionMap) { + throw new Error("Expected `extensionMap` in TemplateLayout constructor."); + } + + this.extensionMap = extensionMap; + this.key = resolver.getNormalizedLayoutKey(); + this.dataKeyLayoutPath = key; + this.inputPath = resolvedPath; + } + + 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, eleventyConfig, extensionMap) { + let config = eleventyConfig.getConfig(); + if (!config.useTemplateCache) { + return new TemplateLayout(key, extensionMap, eleventyConfig); + } + + let inputDir = eleventyConfig.directories.input; + let fullKey = TemplateLayout.resolveFullKey(key, inputDir); + if (!layoutCache.has(fullKey)) { + let layout = new TemplateLayout(key, extensionMap, eleventyConfig); + + layoutCache.add(layout); + debugDev("Added %o to LayoutCache", key); + + return layout; + } + + return layoutCache.get(fullKey); + } + + async getTemplateLayoutMapEntry() { + let { data: frontMatterData } = await this.getFrontMatterData(); + return { + // Used by `TemplateLayout.getTemplate()` + key: this.dataKeyLayoutPath, + + // used by `this.getData()` + frontMatterData, + }; + } + + async #getTemplateLayoutMap() { + // 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, + 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); + + return map; + } + + async getTemplateLayoutMap() { + if (!this.cachedLayoutMap) { + this.cachedLayoutMap = this.#getTemplateLayoutMap(); + } + + return this.cachedLayoutMap; + } + + async getLayoutChain() { + if (!Array.isArray(this.layoutChain)) { + await this.getTemplateLayoutMap(); + } + + return this.layoutChain; + } + + async #getData() { + 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.dataDeepMerge, {}, ...dataToMerge); + delete data[this.config.keys.layout]; + + return data; + } + + async getData() { + if (!this.dataCache) { + this.dataCache = this.#getData(); + } + + return this.dataCache; + } + + async #getCachedCompiledLayoutFunction() { + let rawInput = await this.getPreRender(); + return this.compile(rawInput); + } + + // Do only cache this layout’s render function and delegate the rest to the other templates. + async getCachedCompiledLayoutFunction() { + if (!this.cachedCompiledLayoutFunction) { + this.cachedCompiledLayoutFunction = this.#getCachedCompiledLayoutFunction(); + } + + 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 } = parentLayout; + + let layoutTemplate = TemplateLayout.getTemplate( + key, + 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 LayoutCache after error."); + layoutCache.clear(); + throw e; + } + } + + async render() { + throw new Error("Internal error: `render` was removed from TemplateLayout.js in Eleventy 3.0."); + } + + // 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 renderPageEntry(pageEntry) { + let templateContent = pageEntry.templateContent; + let compiledFunctions = await this.getCompiledLayoutFunctions(); + for (let { render } of compiledFunctions) { + let data = { + content: templateContent, + ...pageEntry.data, + }; + + templateContent = await render(data); + } + + // Don’t set `templateContent` on pageEntry because collection items should not have layout markup + return templateContent; + } + + resetCaches(types) { + super.resetCaches(types); + delete this.dataCache; + delete this.layoutChain; + delete this.cachedLayoutMap; + delete this.cachedCompiledLayoutFunction; + } +} + +export default TemplateLayout; |
