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/TemplateWriter.js | 508 ++++++++++++++++++++++ 1 file changed, 508 insertions(+) create mode 100755 node_modules/@11ty/eleventy/src/TemplateWriter.js (limited to 'node_modules/@11ty/eleventy/src/TemplateWriter.js') diff --git a/node_modules/@11ty/eleventy/src/TemplateWriter.js b/node_modules/@11ty/eleventy/src/TemplateWriter.js new file mode 100755 index 0000000..79b0fee --- /dev/null +++ b/node_modules/@11ty/eleventy/src/TemplateWriter.js @@ -0,0 +1,508 @@ +import { TemplatePath } from "@11ty/eleventy-utils"; +import debugUtil from "debug"; + +import Template from "./Template.js"; +import TemplateMap from "./TemplateMap.js"; +import EleventyFiles from "./EleventyFiles.js"; +import EleventyBaseError from "./Errors/EleventyBaseError.js"; +import { EleventyErrorHandler } from "./Errors/EleventyErrorHandler.js"; +import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js"; +import FileSystemSearch from "./FileSystemSearch.js"; +import ConsoleLogger from "./Util/ConsoleLogger.js"; + +const debug = debugUtil("Eleventy:TemplateWriter"); + +class TemplateWriterMissingConfigArgError extends EleventyBaseError {} +class EleventyPassthroughCopyError extends EleventyBaseError {} +class EleventyTemplateError extends EleventyBaseError {} + +class TemplateWriter { + #eleventyFiles; + #passthroughManager; + #errorHandler; + #extensionMap; + + constructor( + templateFormats, // TODO remove this, see `get eleventyFiles` first + templateData, + templateConfig, + ) { + if (!templateConfig) { + throw new TemplateWriterMissingConfigArgError("Missing config argument."); + } + this.templateConfig = templateConfig; + this.config = templateConfig.getConfig(); + this.userConfig = templateConfig.userConfig; + + this.templateFormats = templateFormats; + + this.templateData = templateData; + this.isVerbose = true; + this.isDryRun = false; + this.writeCount = 0; + this.renderCount = 0; + this.skippedCount = 0; + this.isRunInitialBuild = true; + + this._templatePathCache = new Map(); + } + + get dirs() { + return this.templateConfig.directories; + } + + get inputDir() { + return this.dirs.input; + } + + get outputDir() { + return this.dirs.output; + } + + get templateFormats() { + return this._templateFormats; + } + + set templateFormats(value) { + this._templateFormats = value; + } + + /* Getter for error handler */ + get errorHandler() { + if (!this.#errorHandler) { + this.#errorHandler = new EleventyErrorHandler(); + this.#errorHandler.isVerbose = this.verboseMode; + this.#errorHandler.logger = this.logger; + } + + return this.#errorHandler; + } + + /* Getter for Logger */ + get logger() { + if (!this._logger) { + this._logger = new ConsoleLogger(); + this._logger.isVerbose = this.verboseMode; + } + + return this._logger; + } + + /* Setter for Logger */ + set logger(logger) { + this._logger = logger; + } + + /* For testing */ + overrideConfig(config) { + this.config = config; + } + + restart() { + this.writeCount = 0; + this.renderCount = 0; + this.skippedCount = 0; + } + + set extensionMap(extensionMap) { + this.#extensionMap = extensionMap; + } + + get extensionMap() { + if (!this.#extensionMap) { + throw new Error("Internal error: missing `extensionMap` in TemplateWriter."); + } + return this.#extensionMap; + } + + setPassthroughManager(mgr) { + this.#passthroughManager = mgr; + } + + setEleventyFiles(eleventyFiles) { + this.#eleventyFiles = eleventyFiles; + } + + get eleventyFiles() { + // usually Eleventy.js will setEleventyFiles with the EleventyFiles manager + if (!this.#eleventyFiles) { + // if not, we can create one (used only by tests) + this.#eleventyFiles = new EleventyFiles(this.templateFormats, this.templateConfig); + + this.#eleventyFiles.setFileSystemSearch(new FileSystemSearch()); + this.#eleventyFiles.init(); + } + + return this.#eleventyFiles; + } + + async _getAllPaths() { + // this is now cached upstream by FileSystemSearch + return this.eleventyFiles.getFiles(); + } + + _createTemplate(path, to = "fs") { + let tmpl = this._templatePathCache.get(path); + let wasCached = false; + + if (tmpl) { + wasCached = true; + // Update config for https://github.com/11ty/eleventy/issues/3468 + // TODO reset other constructor things here like inputDir/outputDir + tmpl.resetCachedTemplate({ + templateData: this.templateData, + extensionMap: this.extensionMap, + eleventyConfig: this.templateConfig, + }); + } else { + tmpl = new Template(path, this.templateData, this.extensionMap, this.templateConfig); + tmpl.setOutputFormat(to); + tmpl.logger = this.logger; + this._templatePathCache.set(path, tmpl); + } + + tmpl.setTransforms(this.config.transforms); + tmpl.setLinters(this.config.linters); + tmpl.setDryRun(this.isDryRun); + tmpl.setIsVerbose(this.isVerbose); + tmpl.reset(); + + return { + template: tmpl, + wasCached, + }; + } + + // incrementalFileShape is `template` or `copy` (for passthrough file copy) + async _addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to = "fs") { + // Render overrides are only used when `--ignore-initial` is in play and an initial build is not run + let ignoreInitialBuild = !this.isRunInitialBuild; + let secondOrderRelevantLookup = {}; + let templates = []; + + let promises = []; + for (let path of paths) { + let { template: tmpl } = this._createTemplate(path, to); + + // Note: removed a fix here to fetch missing templateRender instances + // that was tested as no longer needed (Issue #3170) + // Related: #3870, improved configuration reset + + templates.push(tmpl); + + // This must happen before data is generated for the incremental file only + if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) { + tmpl.resetCaches(); + } else if ( + // Issue #3824 #3870 + tmpl.isFileRelevantToThisTemplate(this.incrementalFile, { + isFullTemplate: incrementalFileShape === "template", + }) + ) { + tmpl.resetCaches(); + } + + // IMPORTANT: This is where the data is first generated for the template + promises.push(this.templateMap.add(tmpl)); + } + + // Important to set up template dependency relationships first + await Promise.all(promises); + + // Delete incremental file from the dependency graph so we get fresh entries! + // This _must_ happen before any additions, the other ones are in Custom.js and GlobalDependencyMap.js (from the eleventy.layouts Event) + this.config.uses.resetNode(this.incrementalFile); + + // write new template relationships to the global dependency graph for next time + this.templateMap.addAllToGlobalDependencyGraph(); + + // Always disable render for --ignore-initial + if (ignoreInitialBuild) { + for (let tmpl of templates) { + tmpl.setRenderableOverride(false); // disable render + } + return; + } + + for (let tmpl of templates) { + if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) { + tmpl.setRenderableOverride(undefined); // unset, probably render + } else if ( + tmpl.isFileRelevantToThisTemplate(this.incrementalFile, { + isFullTemplate: incrementalFileShape === "template", + }) + ) { + // changed file is used by template + // template uses the changed file + tmpl.setRenderableOverride(undefined); // unset, probably render + secondOrderRelevantLookup[tmpl.inputPath] = true; + } else if (this.config.uses.isFileUsedBy(this.incrementalFile, tmpl.inputPath)) { + // changed file uses this template + tmpl.setRenderableOverride("optional"); + } else { + // For incremental, always disable render on irrelevant templates + tmpl.setRenderableOverride(false); // disable render + } + } + + let secondOrderRelevantArray = this.config.uses + .getTemplatesRelevantToTemplateList(Object.keys(secondOrderRelevantLookup)) + .map((entry) => TemplatePath.addLeadingDotSlash(entry)); + let secondOrderTemplates = Object.fromEntries( + Object.entries(secondOrderRelevantArray).map(([index, value]) => [value, true]), + ); + + for (let tmpl of templates) { + // second order templates must also be rendered if not yet already rendered at least once and available in cache. + if (secondOrderTemplates[tmpl.inputPath]) { + if (tmpl.isRenderableDisabled()) { + tmpl.setRenderableOverride("optional"); + } + } + } + + // Order of templates does not matter here, they’re reordered later based on dependencies in TemplateMap.js + for (let tmpl of templates) { + if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) { + // Cache is reset above (to invalidate data cache at the right time) + tmpl.setDryRunViaIncremental(false); + } else if (!tmpl.isRenderableDisabled() && !tmpl.isRenderableOptional()) { + // Related to the template but not the template (reset the render cache, not the read cache) + tmpl.resetCaches({ + data: true, + render: true, + }); + + tmpl.setDryRunViaIncremental(false); + } else { + // During incremental we only reset the data cache for non-matching templates, see https://github.com/11ty/eleventy/issues/2710 + // Keep caches for read/render + tmpl.resetCaches({ + data: true, + }); + + tmpl.setDryRunViaIncremental(true); + + this.skippedCount++; + } + } + } + + async _addToTemplateMapFullBuild(paths, to = "fs") { + if (this.incrementalFile) { + return []; + } + + let ignoreInitialBuild = !this.isRunInitialBuild; + let promises = []; + for (let path of paths) { + let { template: tmpl, wasCached } = this._createTemplate(path, to); + // Render overrides are only used when `--ignore-initial` is in play and an initial build is not run + if (ignoreInitialBuild) { + tmpl.setRenderableOverride(false); // disable render + } else { + tmpl.setRenderableOverride(undefined); // unset, render + } + + if (wasCached) { + tmpl.resetCaches(); + } + + // IMPORTANT: This is where the data is first generated for the template + promises.push(this.templateMap.add(tmpl)); + } + + return Promise.all(promises); + } + + async _addToTemplateMap(paths, to = "fs") { + let incrementalFileShape = this.eleventyFiles.getFileShape(paths, this.incrementalFile); + + // Filter out passthrough copy files + paths = paths.filter((path) => { + if (!this.extensionMap.hasEngine(path)) { + return false; + } + if (incrementalFileShape === "copy") { + this.skippedCount++; + // Filters out templates if the incremental file is a passthrough copy file + return false; + } + return true; + }); + + if (this.incrementalFile) { + // Top level async to get at the promises returned. + return await this._addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to); + } + + // Full Build + let ret = await this._addToTemplateMapFullBuild(paths, to); + + // write new template relationships to the global dependency graph for next time + this.templateMap.addAllToGlobalDependencyGraph(); + + return ret; + } + + async _createTemplateMap(paths, to) { + this.templateMap = new TemplateMap(this.templateConfig); + + await this._addToTemplateMap(paths, to); + await this.templateMap.cache(); + + // Return is used by tests + return this.templateMap; + } + + async _generateTemplate(mapEntry, to) { + let tmpl = mapEntry.template; + + return tmpl.generateMapEntry(mapEntry, to).then((pages) => { + this.renderCount += tmpl.getRenderCount(); + this.writeCount += tmpl.getWriteCount(); + return pages; + }); + } + + async writePassthroughCopy(templateExtensionPaths) { + if (!this.#passthroughManager) { + throw new Error("Internal error: Missing `passthroughManager` instance."); + } + + return this.#passthroughManager.copyAll(templateExtensionPaths).catch((e) => { + this.errorHandler.warn(e, "Error with passthrough copy"); + return Promise.reject(new EleventyPassthroughCopyError("Having trouble copying", e)); + }); + } + + async generateTemplates(paths, to = "fs") { + let promises = []; + // TODO optimize await here + await this._createTemplateMap(paths, to); + debug("Template map created."); + + let usedTemplateContentTooEarlyMap = []; + for (let mapEntry of this.templateMap.getMap()) { + promises.push( + this._generateTemplate(mapEntry, to).catch(function (e) { + // Premature templateContent in layout render, this also happens in + // TemplateMap.populateContentDataInMap for non-layout content + if (EleventyErrorUtil.isPrematureTemplateContentError(e)) { + usedTemplateContentTooEarlyMap.push(mapEntry); + } else { + let outputPaths = `"${mapEntry._pages.map((page) => page.outputPath).join(`", "`)}"`; + return Promise.reject( + new EleventyTemplateError( + `Having trouble writing to ${outputPaths} from "${mapEntry.inputPath}"`, + e, + ), + ); + } + }), + ); + } + + for (let mapEntry of usedTemplateContentTooEarlyMap) { + promises.push( + this._generateTemplate(mapEntry, to).catch(function (e) { + return Promise.reject( + new EleventyTemplateError( + `Having trouble writing to (second pass) "${mapEntry.outputPath}" from "${mapEntry.inputPath}"`, + e, + ), + ); + }), + ); + } + + return promises; + } + + async write() { + let paths = await this._getAllPaths(); + + // This must happen before writePassthroughCopy + this.templateConfig.userConfig.emit("eleventy#beforerender"); + + let aggregatePassthroughCopyPromise = this.writePassthroughCopy(paths); + + let templatesPromise = Promise.all(await this.generateTemplates(paths)).then((results) => { + this.templateConfig.userConfig.emit("eleventy#render"); + + return results; + }); + + return Promise.all([aggregatePassthroughCopyPromise, templatesPromise]).then( + async ([passthroughCopyResults, templateResults]) => { + return { + passthroughCopy: passthroughCopyResults, + // New in 3.0: flatten and filter out falsy templates + templates: templateResults.flat().filter(Boolean), + }; + }, + (e) => { + return Promise.reject(e); + }, + ); + } + + // Passthrough copy not supported in JSON output. + // --incremental not supported in JSON output. + async getJSON(to = "json") { + let paths = await this._getAllPaths(); + let promises = await this.generateTemplates(paths, to); + + return Promise.all(promises).then( + (templateResults) => { + return { + // New in 3.0: flatten and filter out falsy templates + templates: templateResults.flat().filter(Boolean), + }; + }, + (e) => { + return Promise.reject(e); + }, + ); + } + + setVerboseOutput(isVerbose) { + this.isVerbose = isVerbose; + this.errorHandler.isVerbose = isVerbose; + } + + setDryRun(isDryRun) { + this.isDryRun = Boolean(isDryRun); + } + + setRunInitialBuild(runInitialBuild) { + this.isRunInitialBuild = runInitialBuild; + } + setIncrementalBuild(isIncremental) { + this.isIncremental = isIncremental; + } + setIncrementalFile(incrementalFile) { + this.incrementalFile = incrementalFile; + this.#passthroughManager.setIncrementalFile(incrementalFile); + } + resetIncrementalFile() { + this.incrementalFile = null; + this.#passthroughManager.resetIncrementalFile(); + } + + getMetadata() { + return { + // copyCount, copySize + ...(this.#passthroughManager?.getMetadata() || {}), + skipCount: this.skippedCount, + writeCount: this.writeCount, + renderCount: this.renderCount, + }; + } + + get caches() { + return ["_templatePathCache"]; + } +} + +export default TemplateWriter; -- cgit v1.2.3