From 7a52ddeba2a68388b544f529d2d92104420f77b0 Mon Sep 17 00:00:00 2001 From: Shipwreckt Date: Fri, 31 Oct 2025 20:02:14 +0000 Subject: Changed from static to 11ty! --- node_modules/@11ty/eleventy/src/TemplateContent.js | 748 +++++++++++++++++++++ 1 file changed, 748 insertions(+) create mode 100644 node_modules/@11ty/eleventy/src/TemplateContent.js (limited to 'node_modules/@11ty/eleventy/src/TemplateContent.js') diff --git a/node_modules/@11ty/eleventy/src/TemplateContent.js b/node_modules/@11ty/eleventy/src/TemplateContent.js new file mode 100644 index 0000000..97d440f --- /dev/null +++ b/node_modules/@11ty/eleventy/src/TemplateContent.js @@ -0,0 +1,748 @@ +import os from "node:os"; + +import fs from "node:fs"; +import matter from "gray-matter"; +import lodash from "@11ty/lodash-custom"; +import { TemplatePath } from "@11ty/eleventy-utils"; +import debugUtil from "debug"; + +import TemplateData from "./Data/TemplateData.js"; +import TemplateRender from "./TemplateRender.js"; +import EleventyBaseError from "./Errors/EleventyBaseError.js"; +import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js"; +import eventBus from "./EventBus.js"; + +import { withResolvers } from "./Util/PromiseUtil.js"; + +const { set: lodashSet } = lodash; +const debug = debugUtil("Eleventy:TemplateContent"); +const debugDev = debugUtil("Dev:Eleventy:TemplateContent"); + +class TemplateContentFrontMatterError extends EleventyBaseError {} +class TemplateContentCompileError extends EleventyBaseError {} +class TemplateContentRenderError extends EleventyBaseError {} + +class TemplateContent { + #initialized = false; + #config; + #templateRender; + #preprocessorEngine; + #extensionMap; + #configOptions; + + constructor(inputPath, templateConfig) { + if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") { + throw new Error("Missing or invalid `templateConfig` argument"); + } + this.eleventyConfig = templateConfig; + this.inputPath = inputPath; + } + + async asyncTemplateInitialization() { + if (!this.hasTemplateRender()) { + await this.getTemplateRender(); + } + + if (this.#initialized) { + return; + } + this.#initialized = true; + + let preprocessorEngineName = this.templateRender.getPreprocessorEngineName(); + if (preprocessorEngineName && this.templateRender.engine.getName() !== preprocessorEngineName) { + let engine = await this.templateRender.getEngineByName(preprocessorEngineName); + this.#preprocessorEngine = engine; + } + } + + resetCachedTemplate({ eleventyConfig }) { + this.eleventyConfig = eleventyConfig; + } + + get dirs() { + return this.eleventyConfig.directories; + } + + get inputDir() { + return this.dirs.input; + } + + get outputDir() { + return this.dirs.output; + } + + getResetTypes(types) { + if (types) { + return Object.assign( + { + data: false, + read: false, + render: false, + }, + types, + ); + } + + return { + data: true, + read: true, + render: true, + }; + } + + // Called during an incremental build when the template instance is cached but needs to be reset because it has changed + resetCaches(types) { + types = this.getResetTypes(types); + + if (types.read) { + delete this.readingPromise; + delete this.inputContent; + delete this._frontMatterDataCache; + } + if (types.render) { + this.#templateRender = undefined; + } + } + + get extensionMap() { + if (!this.#extensionMap) { + throw new Error("Internal error: Missing `extensionMap` in TemplateContent."); + } + return this.#extensionMap; + } + + set extensionMap(map) { + this.#extensionMap = map; + } + + set eleventyConfig(config) { + this.#config = config; + + if (this.#config.constructor.name === "TemplateConfig") { + this.#configOptions = this.#config.getConfig(); + } else { + throw new Error("Tried to get an TemplateConfig but none was found."); + } + } + + get eleventyConfig() { + if (this.#config.constructor.name === "TemplateConfig") { + return this.#config; + } + throw new Error("Tried to get an TemplateConfig but none was found."); + } + + get config() { + if (this.#config.constructor.name === "TemplateConfig" && !this.#configOptions) { + this.#configOptions = this.#config.getConfig(); + } + + return this.#configOptions; + } + + get bench() { + return this.config.benchmarkManager.get("Aggregate"); + } + + get engine() { + return this.templateRender.engine; + } + + get templateRender() { + if (!this.hasTemplateRender()) { + throw new Error(`\`templateRender\` has not yet initialized on ${this.inputPath}`); + } + + return this.#templateRender; + } + + hasTemplateRender() { + return !!this.#templateRender; + } + + async getTemplateRender() { + if (!this.#templateRender) { + this.#templateRender = new TemplateRender(this.inputPath, this.eleventyConfig); + this.#templateRender.extensionMap = this.extensionMap; + + return this.#templateRender.init().then(() => { + return this.#templateRender; + }); + } + + return this.#templateRender; + } + + // For monkey patchers + get frontMatter() { + if (this.frontMatterOverride) { + return this.frontMatterOverride; + } else { + throw new Error( + "Unfortunately you’re using code that monkey patched some Eleventy internals and it isn’t async-friendly. Change your code to use the async `read()` method on the template instead!", + ); + } + } + + // For monkey patchers + set frontMatter(contentOverride) { + this.frontMatterOverride = contentOverride; + } + + getInputPath() { + return this.inputPath; + } + + getInputDir() { + return this.inputDir; + } + + isVirtualTemplate() { + let def = this.getVirtualTemplateDefinition(); + return !!def; + } + + getVirtualTemplateDefinition() { + let inputDirRelativeInputPath = + this.eleventyConfig.directories.getInputPathRelativeToInputDirectory(this.inputPath); + return this.config.virtualTemplates[inputDirRelativeInputPath]; + } + + async #read() { + let content = await this.inputContent; + + if (content || content === "") { + let tr = await this.getTemplateRender(); + if (tr.engine.useJavaScriptImport()) { + return { + data: {}, + content, + }; + } + + let options = this.config.frontMatterParsingOptions || {}; + let fm; + try { + // Added in 3.0, passed along to front matter engines + options.filePath = this.inputPath; + fm = matter(content, options); + } catch (e) { + throw new TemplateContentFrontMatterError( + `Having trouble reading front matter from template ${this.inputPath}`, + e, + ); + } + + if (options.excerpt && fm.excerpt) { + let excerptString = fm.excerpt + (options.excerpt_separator || "---"); + if (fm.content.startsWith(excerptString + os.EOL)) { + // with an os-specific newline after excerpt separator + fm.content = fm.excerpt.trim() + "\n" + fm.content.slice((excerptString + os.EOL).length); + } else if (fm.content.startsWith(excerptString + "\n")) { + // with a newline (\n) after excerpt separator + // This is necessary for some git configurations on windows + fm.content = fm.excerpt.trim() + "\n" + fm.content.slice((excerptString + 1).length); + } else if (fm.content.startsWith(excerptString)) { + // no newline after excerpt separator + fm.content = fm.excerpt + fm.content.slice(excerptString.length); + } + + // alias, defaults to page.excerpt + let alias = options.excerpt_alias || "page.excerpt"; + lodashSet(fm.data, alias, fm.excerpt); + } + + // For monkey patchers that used `frontMatter` 🤧 + // https://github.com/11ty/eleventy/issues/613#issuecomment-999637109 + // https://github.com/11ty/eleventy/issues/2710#issuecomment-1373854834 + // Removed this._frontMatter monkey patcher help in 3.0.0-alpha.7 + + return fm; + } else { + return { + data: {}, + content: "", + excerpt: "", + }; + } + } + + async read() { + if (!this.readingPromise) { + if (!this.inputContent) { + // @cachedproperty + this.inputContent = this.getInputContent(); + } + + // @cachedproperty + this.readingPromise = this.#read(); + } + + return this.readingPromise; + } + + /* Incremental builds cache the Template instances (in TemplateWriter) but + * these template specific caches are important for Pagination */ + static cache(path, content) { + this._inputCache.set(TemplatePath.absolutePath(path), content); + } + + static getCached(path) { + return this._inputCache.get(TemplatePath.absolutePath(path)); + } + + static deleteFromInputCache(path) { + this._inputCache.delete(TemplatePath.absolutePath(path)); + } + + // Used via clone + setInputContent(content) { + this.inputContent = content; + } + + async getInputContent() { + let tr = await this.getTemplateRender(); + + let virtualTemplateDefinition = this.getVirtualTemplateDefinition(); + if (virtualTemplateDefinition) { + let { content } = virtualTemplateDefinition; + return content; + } + + if ( + tr.engine.useJavaScriptImport() && + typeof tr.engine.getInstanceFromInputPath === "function" + ) { + return tr.engine.getInstanceFromInputPath(this.inputPath); + } + + if (!tr.engine.needsToReadFileContents()) { + return ""; + } + + let templateBenchmark = this.bench.get("Template Read"); + templateBenchmark.before(); + + let content; + + if (this.config.useTemplateCache) { + content = TemplateContent.getCached(this.inputPath); + } + + if (!content && content !== "") { + let contentBuffer = fs.readFileSync(this.inputPath); + + content = contentBuffer.toString("utf8"); + + if (this.config.useTemplateCache) { + TemplateContent.cache(this.inputPath, content); + } + } + + templateBenchmark.after(); + + return content; + } + + async _testGetFrontMatter() { + let fm = this.frontMatterOverride ? this.frontMatterOverride : await this.read(); + + return fm; + } + + async getPreRender() { + let fm = this.frontMatterOverride ? this.frontMatterOverride : await this.read(); + + return fm.content; + } + + async #getFrontMatterData() { + let fm = await this.read(); + + // gray-matter isn’t async-friendly but can return a promise from custom front matter + if (fm.data instanceof Promise) { + fm.data = await fm.data; + } + + let tr = await this.getTemplateRender(); + let extraData = await tr.engine.getExtraDataFromFile(this.inputPath); + + let virtualTemplateDefinition = this.getVirtualTemplateDefinition(); + let virtualTemplateData; + if (virtualTemplateDefinition) { + virtualTemplateData = virtualTemplateDefinition.data; + } + + let data = Object.assign(fm.data, extraData, virtualTemplateData); + + TemplateData.cleanupData(data, { + file: this.inputPath, + isVirtualTemplate: Boolean(virtualTemplateData), + }); + + return { + data, + excerpt: fm.excerpt, + }; + } + + async getFrontMatterData() { + if (!this._frontMatterDataCache) { + // @cachedproperty + this._frontMatterDataCache = this.#getFrontMatterData(); + } + + return this._frontMatterDataCache; + } + + async getEngineOverride() { + return this.getFrontMatterData().then((data) => { + return data[this.config.keys.engineOverride]; + }); + } + + // checks engines + isTemplateCacheable() { + if (this.#preprocessorEngine) { + return this.#preprocessorEngine.cacheable; + } + return this.engine.cacheable; + } + + _getCompileCache(str) { + // Caches used to be bifurcated based on engine name, now they’re based on inputPath + // TODO does `cacheable` need to help inform whether a cache is used here? + let inputPathMap = TemplateContent._compileCache.get(this.inputPath); + if (!inputPathMap) { + inputPathMap = new Map(); + TemplateContent._compileCache.set(this.inputPath, inputPathMap); + } + + let cacheable = this.isTemplateCacheable(); + let { useCache, key } = this.engine.getCompileCacheKey(str, this.inputPath); + + // We also tie the compile cache key to the UserConfig instance, to alleviate issues with global template cache + // Better to move the cache to the Eleventy instance instead, no? + // (This specifically failed I18nPluginTest cases with filters being cached across tests and not having access to each plugin’s options) + key = this.eleventyConfig.userConfig._getUniqueId() + key; + + return [cacheable, key, inputPathMap, useCache]; + } + + async compile(str, options = {}) { + let { type, bypassMarkdown, engineOverride } = options; + + // Must happen before cacheable fetch below + // Likely only necessary for Eleventy Layouts, see TemplateMap->initDependencyMap + await this.asyncTemplateInitialization(); + + // this.templateRender is guaranteed here + let tr = await this.getTemplateRender(); + if (engineOverride !== undefined) { + debugDev("%o overriding template engine to use %o", this.inputPath, engineOverride); + await tr.setEngineOverride(engineOverride, bypassMarkdown); + } else { + tr.setUseMarkdown(!bypassMarkdown); + } + if (bypassMarkdown && !this.engine.needsCompilation(str)) { + return function () { + return str; + }; + } + + debugDev("%o compile() using engine: %o", this.inputPath, tr.engineName); + + try { + let res; + if (this.config.useTemplateCache) { + let [cacheable, key, cache, useCache] = this._getCompileCache(str); + if (cacheable && key) { + if (useCache && cache.has(key)) { + this.bench.get("(count) Template Compile Cache Hit").incrementCount(); + return cache.get(key); + } + + this.bench.get("(count) Template Compile Cache Miss").incrementCount(); + + // Compile cache is cleared when the resource is modified (below) + + // Compilation is async, so we eagerly cache a Promise that eventually + // resolves to the compiled function + let withRes = withResolvers(); + res = withRes.resolve; + + cache.set(key, withRes.promise); + } + } + + let typeStr = type ? ` ${type}` : ""; + let templateBenchmark = this.bench.get(`Template Compile${typeStr}`); + let inputPathBenchmark = this.bench.get(`> Compile${typeStr} > ${this.inputPath}`); + templateBenchmark.before(); + inputPathBenchmark.before(); + + let fn = await tr.getCompiledTemplate(str); + inputPathBenchmark.after(); + templateBenchmark.after(); + debugDev("%o getCompiledTemplate function created", this.inputPath); + if (this.config.useTemplateCache && res) { + res(fn); + } + return fn; + } catch (e) { + let [cacheable, key, cache] = this._getCompileCache(str); + if (cacheable && key) { + cache.delete(key); + } + debug(`Having trouble compiling template ${this.inputPath}: %O`, str); + throw new TemplateContentCompileError( + `Having trouble compiling template ${this.inputPath}`, + e, + ); + } + } + + getParseForSymbolsFunction(str) { + let engine = this.engine; + + // Don’t use markdown as the engine to parse for symbols + // TODO pass in engineOverride here + if (this.#preprocessorEngine) { + engine = this.#preprocessorEngine; + } + + if ("parseForSymbols" in engine) { + return () => { + if (Array.isArray(str)) { + return str + .filter((entry) => typeof entry === "string") + .map((entry) => engine.parseForSymbols(entry)) + .flat(); + } + if (typeof str === "string") { + return engine.parseForSymbols(str); + } + return []; + }; + } + } + + // used by computed data or for permalink functions + async _renderFunction(fn, ...args) { + let mixins = Object.assign({}, this.config.javascriptFunctions); + let result = await fn.call(mixins, ...args); + + // normalize Buffer away if returned from permalink + if (Buffer.isBuffer(result)) { + return result.toString(); + } + + return result; + } + + async renderComputedData(str, data) { + if (typeof str === "function") { + return this._renderFunction(str, data); + } + + return this._render(str, data, { + type: "Computed Data", + bypassMarkdown: true, + }); + } + + async renderPermalink(permalink, data) { + let tr = await this.getTemplateRender(); + let permalinkCompilation = tr.engine.permalinkNeedsCompilation(permalink); + + // No string compilation: + // ({ compileOptions: { permalink: "raw" }}) + // These mean `permalink: false`, which is no file system writing: + // ({ compileOptions: { permalink: false }}) + // ({ compileOptions: { permalink: () => false }}) + // ({ compileOptions: { permalink: () => (() = > false) }}) + if (permalinkCompilation === false && typeof permalink !== "function") { + return permalink; + } + + /* Custom `compile` function for permalinks, usage: + permalink: function(permalinkString, inputPath) { + return async function(data) { + return "THIS IS MY RENDERED PERMALINK"; + } + } + */ + if (permalinkCompilation && typeof permalinkCompilation === "function") { + permalink = await this._renderFunction(permalinkCompilation, permalink, this.inputPath); + } + + // Raw permalink function (in the app code data cascade) + if (typeof permalink === "function") { + return this._renderFunction(permalink, data); + } + + return this._render(permalink, data, { + type: "Permalink", + bypassMarkdown: true, + }); + } + + async render(str, data, bypassMarkdown) { + return this._render(str, data, { + type: "Content", + bypassMarkdown, + }); + } + + _getPaginationLogSuffix(data) { + let suffix = []; + if ("pagination" in data) { + suffix.push(" ("); + if (data.pagination.pages) { + suffix.push( + `${data.pagination.pages.length} page${data.pagination.pages.length !== 1 ? "s" : ""}`, + ); + } else { + suffix.push("Pagination"); + } + suffix.push(")"); + } + return suffix.join(""); + } + + async _render(str, data, options = {}) { + let { bypassMarkdown, type } = options; + + try { + if (bypassMarkdown && !this.engine.needsCompilation(str)) { + return str; + } + + let fn = await this.compile(str, { + bypassMarkdown, + engineOverride: data[this.config.keys.engineOverride], + type, + }); + + if (fn === undefined) { + return; + } else if (typeof fn !== "function") { + throw new Error(`The \`compile\` function did not return a function. Received ${fn}`); + } + + // Benchmark + let templateBenchmark = this.bench.get("Render"); + let inputPathBenchmark = this.bench.get( + `> Render${type ? ` ${type}` : ""} > ${this.inputPath}${this._getPaginationLogSuffix(data)}`, + ); + + templateBenchmark.before(); + if (inputPathBenchmark) { + inputPathBenchmark.before(); + } + + let rendered = await fn(data); + + if (inputPathBenchmark) { + inputPathBenchmark.after(); + } + templateBenchmark.after(); + debugDev("%o getCompiledTemplate called, rendered content created", this.inputPath); + return rendered; + } catch (e) { + if (EleventyErrorUtil.isPrematureTemplateContentError(e)) { + return Promise.reject(e); + } else { + let tr = await this.getTemplateRender(); + let engine = tr.getReadableEnginesList(); + debug(`Having trouble rendering ${engine} template ${this.inputPath}: %O`, str); + return Promise.reject( + new TemplateContentRenderError( + `Having trouble rendering ${engine} template ${this.inputPath}`, + e, + ), + ); + } + } + } + + getExtensionEntries() { + return this.engine.extensionEntries; + } + + isFileRelevantToThisTemplate(incrementalFile, metadata = {}) { + // always relevant if incremental file not set (build everything) + if (!incrementalFile) { + return true; + } + + let hasDependencies = this.engine.hasDependencies(incrementalFile); + + let isRelevant = this.engine.isFileRelevantTo(this.inputPath, incrementalFile); + + debug( + "Test dependencies to see if %o is relevant to %o: %o", + this.inputPath, + incrementalFile, + isRelevant, + ); + + let extensionEntries = this.getExtensionEntries().filter((entry) => !!entry.isIncrementalMatch); + if (extensionEntries.length) { + for (let entry of extensionEntries) { + if ( + entry.isIncrementalMatch.call( + { + inputPath: this.inputPath, + isFullTemplate: metadata.isFullTemplate, + isFileRelevantToInputPath: isRelevant, + doesFileHaveDependencies: hasDependencies, + }, + incrementalFile, + ) + ) { + return true; + } + } + + return false; + } else { + // Not great way of building all templates if this is a layout, include, JS dependency. + // TODO improve this for default template syntaxes + + // This is the fallback way of determining if something is incremental (no isIncrementalMatch available) + // This will be true if the inputPath and incrementalFile are the same + if (isRelevant) { + return true; + } + + // only return true here if dependencies are not known + if (!hasDependencies && !metadata.isFullTemplate) { + return true; + } + } + + return false; + } +} + +TemplateContent._inputCache = new Map(); +TemplateContent._compileCache = new Map(); +eventBus.on("eleventy.resourceModified", (path) => { + // delete from input cache + TemplateContent.deleteFromInputCache(path); + + // delete from compile cache + let normalized = TemplatePath.addLeadingDotSlash(path); + let compileCache = TemplateContent._compileCache.get(normalized); + if (compileCache) { + compileCache.clear(); + } +}); + +// Used when the configuration file reset https://github.com/11ty/eleventy/issues/2147 +eventBus.on("eleventy.compileCacheReset", () => { + TemplateContent._compileCache = new Map(); +}); + +export default TemplateContent; -- cgit v1.2.3