Files
homepage/node_modules/@11ty/eleventy/src/Engines/TemplateEngineManager.js
2024-11-03 17:16:20 +01:00

198 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import debugUtil from "debug";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
import { EleventyImportFromEleventy } from "../Util/Require.js";
const debug = debugUtil("Eleventy:TemplateEngineManager");
class TemplateEngineManagerConfigError extends EleventyBaseError {}
class TemplateEngineManager {
constructor(eleventyConfig) {
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
throw new TemplateEngineManagerConfigError("Missing or invalid `config` argument.");
}
this.eleventyConfig = eleventyConfig;
this.engineCache = {};
this.importCache = {};
}
get config() {
return this.eleventyConfig.getConfig();
}
static isAlias(entry) {
if (entry.aliasKey) {
return true;
}
return entry.key !== entry.extension;
}
static isSimpleAlias(entry) {
if (!this.isAlias(entry)) {
return false;
}
// has keys other than key, extension, and aliasKey
return (
Object.keys(entry).some((key) => {
return key !== "key" && key !== "extension" && key !== "aliasKey";
}) === false
);
}
get keyToClassNameMap() {
if (!this._keyToClassNameMap) {
this._keyToClassNameMap = {
md: "Markdown",
html: "Html",
njk: "Nunjucks",
liquid: "Liquid",
"11ty.js": "JavaScript",
};
// Custom entries *can* overwrite default entries above
if ("extensionMap" in this.config) {
for (let entry of this.config.extensionMap) {
// either the key does not already exist or it is not a simple alias and is an override: https://v3.11ty.dev/docs/languages/custom/#overriding-an-existing-template-language
let existingTarget = this._keyToClassNameMap[entry.key];
let isAlias = TemplateEngineManager.isAlias(entry);
if (!existingTarget && isAlias) {
throw new Error(
`An attempt to alias ${entry.aliasKey} to ${entry.key} was made, but ${entry.key} is not a recognized template syntax.`,
);
}
if (isAlias) {
// only `key` and `extension`, not `compile` or other options
if (!TemplateEngineManager.isSimpleAlias(entry)) {
this._keyToClassNameMap[entry.aliasKey] = "Custom";
} else {
this._keyToClassNameMap[entry.aliasKey] = this._keyToClassNameMap[entry.key];
}
} else {
// not an alias, so `key` and `extension` are the same here.
// *can* override a built-in extension!
this._keyToClassNameMap[entry.key] = "Custom";
}
}
}
}
return this._keyToClassNameMap;
}
reset() {
this.engineCache = {};
}
getClassNameFromTemplateKey(key) {
return this.keyToClassNameMap[key];
}
hasEngine(name) {
return !!this.getClassNameFromTemplateKey(name);
}
isEngineRemovedFromCore(name) {
return ["ejs", "hbs", "mustache", "haml", "pug"].includes(name) && !this.hasEngine(name);
}
async getEngineClassByExtension(extension) {
if (this.importCache[extension]) {
return this.importCache[extension];
}
let promise;
// We include these as raw strings (and not more readable variables) so theyre parsed by a bundler.
if (extension === "md") {
promise = EleventyImportFromEleventy("./src/Engines/Markdown.js");
} else if (extension === "html") {
promise = EleventyImportFromEleventy("./src/Engines/Html.js");
} else if (extension === "njk") {
promise = EleventyImportFromEleventy("./src/Engines/Nunjucks.js");
} else if (extension === "liquid") {
promise = EleventyImportFromEleventy("./src/Engines/Liquid.js");
} else if (extension === "11ty.js") {
promise = EleventyImportFromEleventy("./src/Engines/JavaScript.js");
} else {
promise = this.getCustomEngineClass();
}
this.importCache[extension] = promise;
return promise;
}
async getCustomEngineClass() {
if (!this._CustomEngine) {
this._CustomEngine = EleventyImportFromEleventy("./src/Engines/Custom.js");
}
return this._CustomEngine;
}
async #getEngine(name, extensionMap) {
let cls = await this.getEngineClassByExtension(name);
let instance = new cls(name, this.eleventyConfig);
instance.extensionMap = extensionMap;
instance.engineManager = this;
let extensionEntry = extensionMap.getExtensionEntry(name);
// Override a built-in extension (md => md)
// If provided a "Custom" engine using addExtension, but that engine's instance is *not* custom,
// The user must be overriding a built-in engine i.e. addExtension('md', { ...overrideBehavior })
let className = this.getClassNameFromTemplateKey(name);
if (className === "Custom" && instance.constructor.name !== "CustomEngine") {
let CustomEngine = await this.getCustomEngineClass();
let overrideCustomEngine = new CustomEngine(name, this.eleventyConfig);
// Keep track of the "default" engine 11ty would normally use
// This allows the user to access the default engine in their override
overrideCustomEngine.setDefaultEngine(instance);
instance = overrideCustomEngine;
// Alias to a built-in extension (11ty.tsx => 11ty.js)
} else if (
instance.constructor.name === "CustomEngine" &&
TemplateEngineManager.isAlias(extensionEntry)
) {
// add defaultRenderer for complex aliases with their own compile functions.
let originalEngineInstance = await this.getEngine(extensionEntry.key, extensionMap);
instance.setDefaultEngine(originalEngineInstance);
}
return instance;
}
async getEngine(name, extensionMap) {
// Warning about engine deprecation
if (this.isEngineRemovedFromCore(name)) {
throw new Error(
`Per the 11ty Community Survey (2023), the "${name}" template language was moved from core to an officially supported plugin in v3.0. These plugins live here: https://github.com/11ty/eleventy-plugin-template-languages and are documented on their respective template language docs at https://v3.11ty.dev/docs/languages/ You are also empowered to implement *any* template language yourself using https://v3.11ty.dev/docs/languages/custom/`,
);
}
if (!this.hasEngine(name)) {
throw new Error(`Template Engine ${name} does not exist in getEngine()`);
}
// TODO these cached engines should be based on extensions not name, then we can remove the error in
// "Double override (not aliases) throws an error" test in TemplateRenderCustomTest.js
if (!this.engineCache[name]) {
debug("Engine cache miss %o (should only happen once per type)", name);
// Make sure cache key is based on name and not path
// Custom class is used for all plugins, cache once per plugin
this.engineCache[name] = this.#getEngine(name, extensionMap);
}
return this.engineCache[name];
}
}
export default TemplateEngineManager;