Initial commit: 11ty website with Fire/Frost branding

This commit is contained in:
The Trinity
2026-04-02 18:39:00 -05:00
commit 40b45dff2e
1646 changed files with 329080 additions and 0 deletions

339
node_modules/@11ty/eleventy/src/Engines/Custom.js generated vendored Normal file
View File

@@ -0,0 +1,339 @@
import TemplateEngine from "./TemplateEngine.js";
import getJavaScriptData from "../Util/GetJavaScriptData.js";
export default class CustomEngine extends TemplateEngine {
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
this.entry = this.getExtensionMapEntry();
this.needsInit = "init" in this.entry && typeof this.entry.init === "function";
this.setDefaultEngine(undefined);
}
getExtensionMapEntry() {
if ("extensionMap" in this.config) {
let name = this.name.toLowerCase();
// Iterates over only the user config `addExtension` entries
for (let entry of this.config.extensionMap) {
let entryKey = (entry.aliasKey || entry.key || "").toLowerCase();
if (entryKey === name) {
return entry;
}
}
}
throw Error(
`Could not find a custom extension for ${this.name}. Did you add it to your config file?`,
);
}
setDefaultEngine(defaultEngine) {
this._defaultEngine = defaultEngine;
}
get cacheable() {
// Enable cacheability for this template
if (this.entry?.compileOptions?.cache !== undefined) {
return this.entry.compileOptions.cache;
} else if (this.needsToReadFileContents()) {
return true;
} else if (this._defaultEngine?.cacheable !== undefined) {
return this._defaultEngine.cacheable;
}
return super.cacheable;
}
async getInstanceFromInputPath(inputPath) {
if (
"getInstanceFromInputPath" in this.entry &&
typeof this.entry.getInstanceFromInputPath === "function"
) {
// returns Promise
return this.entry.getInstanceFromInputPath(inputPath);
}
// aliased upstream type
if (
this._defaultEngine &&
"getInstanceFromInputPath" in this._defaultEngine &&
typeof this._defaultEngine.getInstanceFromInputPath === "function"
) {
// returns Promise
return this._defaultEngine.getInstanceFromInputPath(inputPath);
}
return false;
}
/**
* Whether to use the module loader directly
*
* @override
*/
useJavaScriptImport() {
if ("useJavaScriptImport" in this.entry) {
return this.entry.useJavaScriptImport;
}
if (
this._defaultEngine &&
"useJavaScriptImport" in this._defaultEngine &&
typeof this._defaultEngine.useJavaScriptImport === "function"
) {
return this._defaultEngine.useJavaScriptImport();
}
return false;
}
/**
* @override
*/
needsToReadFileContents() {
if ("read" in this.entry) {
return this.entry.read;
}
// Handle aliases to `11ty.js` templates, avoid reading files in the alias, see #2279
// Here, we are short circuiting fallback to defaultRenderer, does not account for compile
// functions that call defaultRenderer explicitly
if (this._defaultEngine && "needsToReadFileContents" in this._defaultEngine) {
return this._defaultEngine.needsToReadFileContents();
}
return true;
}
// If we init from multiple places, wait for the first init to finish before continuing on.
async _runningInit() {
if (this.needsInit) {
if (!this._initing) {
this._initBench = this.benchmarks.aggregate.get(`Engine (${this.name}) Init`);
this._initBench.before();
this._initing = this.entry.init.bind({
config: this.config,
bench: this.benchmarks.aggregate,
})();
}
await this._initing;
this.needsInit = false;
if (this._initBench) {
this._initBench.after();
this._initBench = undefined;
}
}
}
async getExtraDataFromFile(inputPath) {
if (this.entry.getData === false) {
return;
}
if (!("getData" in this.entry)) {
// Handle aliases to `11ty.js` templates, use upstream default engine data fetch, see #2279
if (this._defaultEngine && "getExtraDataFromFile" in this._defaultEngine) {
return this._defaultEngine.getExtraDataFromFile(inputPath);
}
return;
}
await this._runningInit();
if (typeof this.entry.getData === "function") {
let dataBench = this.benchmarks.aggregate.get(
`Engine (${this.name}) Get Data From File (Function)`,
);
dataBench.before();
let data = this.entry.getData(inputPath);
dataBench.after();
return data;
}
let keys = new Set();
if (this.entry.getData === true) {
keys.add("data");
} else if (Array.isArray(this.entry.getData)) {
for (let key of this.entry.getData) {
keys.add(key);
}
}
let dataBench = this.benchmarks.aggregate.get(`Engine (${this.name}) Get Data From File`);
dataBench.before();
let inst = await this.getInstanceFromInputPath(inputPath);
if (inst === false) {
dataBench.after();
return Promise.reject(
new Error(
`\`getInstanceFromInputPath\` callback missing from '${this.name}' template engine plugin. It is required when \`getData\` is in use. You can set \`getData: false\` to opt-out of this.`,
),
);
}
// override keys set at the plugin level in the individual template
if (inst.eleventyDataKey) {
keys = new Set(inst.eleventyDataKey);
}
let mixins;
if (this.config) {
// Object.assign usage: see TemplateRenderCustomTest.js: `JavaScript functions should not be mutable but not *that* mutable`
mixins = Object.assign({}, this.config.javascriptFunctions);
}
let promises = [];
for (let key of keys) {
promises.push(
getJavaScriptData(inst, inputPath, key, {
mixins,
isObjectRequired: key === "data",
}),
);
}
let results = await Promise.all(promises);
let data = {};
for (let result of results) {
Object.assign(data, result);
}
dataBench.after();
return data;
}
async compile(str, inputPath, ...args) {
await this._runningInit();
let defaultCompilationFn;
if (this._defaultEngine) {
defaultCompilationFn = async (data) => {
const renderFn = await this._defaultEngine.compile(str, inputPath, ...args);
return renderFn(data);
};
}
// Fall back to default compiler if the user does not provide their own
if (!this.entry.compile) {
if (defaultCompilationFn) {
return defaultCompilationFn;
} else {
throw new Error(
`Missing \`compile\` property for custom template syntax definition eleventyConfig.addExtension("${this.name}"). This is not necessary when aliasing to an existing template syntax.`,
);
}
}
// TODO generalize this (look at JavaScript.js)
let compiledFn = this.entry.compile.bind({
config: this.config,
addDependencies: (from, toArray = []) => {
this.config.uses.addDependency(from, toArray);
},
defaultRenderer: defaultCompilationFn, // bind defaultRenderer to compile function
})(str, inputPath);
// Support `undefined` to skip compile/render
if (compiledFn) {
// Bind defaultRenderer to render function
if ("then" in compiledFn && typeof compiledFn.then === "function") {
// Promise, wait to bind
return compiledFn.then((fn) => {
if (typeof fn === "function") {
return fn.bind({
defaultRenderer: defaultCompilationFn,
});
}
return fn;
});
} else if ("bind" in compiledFn && typeof compiledFn.bind === "function") {
return compiledFn.bind({
defaultRenderer: defaultCompilationFn,
});
}
}
return compiledFn;
}
get defaultTemplateFileExtension() {
return this.entry.outputFileExtension ?? "html";
}
// Whether or not to wrap in Eleventy layouts
useLayouts() {
// TODO future change fallback to `this.defaultTemplateFileExtension === "html"`
return this.entry.useLayouts ?? true;
}
hasDependencies(inputPath) {
if (this.config.uses.getDependencies(inputPath) === false) {
return false;
}
return true;
}
isFileRelevantTo(inputPath, comparisonFile, includeLayouts) {
return this.config.uses.isFileRelevantTo(inputPath, comparisonFile, includeLayouts);
}
getCompileCacheKey(str, inputPath) {
let lastModifiedFile = this.eleventyConfig.getPreviousBuildModifiedFile();
// Return this separately so we know whether or not to use the cached version
// but still return a key to cache this new render for next time
let isRelevant = this.isFileRelevantTo(inputPath, lastModifiedFile, false);
let useCache = !isRelevant;
if (this.entry.compileOptions && "getCacheKey" in this.entry.compileOptions) {
if (typeof this.entry.compileOptions.getCacheKey !== "function") {
throw new Error(
`\`compileOptions.getCacheKey\` must be a function in addExtension for the ${this.name} type`,
);
}
return {
useCache,
key: this.entry.compileOptions.getCacheKey(str, inputPath),
};
}
let { key } = super.getCompileCacheKey(str, inputPath);
return {
useCache,
key,
};
}
permalinkNeedsCompilation(/*str*/) {
if (this.entry.compileOptions && "permalink" in this.entry.compileOptions) {
let p = this.entry.compileOptions.permalink;
if (p === "raw") {
return false;
}
// permalink: false is aliased to permalink: () => false
if (p === false) {
return () => false;
}
return this.entry.compileOptions.permalink;
}
// Breaking: default changed from `true` to `false` in 3.0.0-alpha.13
// Note: `false` is the same as "raw" here.
return false;
}
static shouldSpiderJavaScriptDependencies(entry) {
if (entry.compileOptions && "spiderJavaScriptDependencies" in entry.compileOptions) {
return entry.compileOptions.spiderJavaScriptDependencies;
}
return false;
}
}

View File

@@ -0,0 +1,34 @@
import { RetrieveGlobals } from "node-retrieve-globals";
// `javascript` Front Matter Type
export default function (frontMatterCode, context = {}) {
let { filePath } = context;
// context.language would be nice as a guard, but was unreliable
if (frontMatterCode.trimStart().startsWith("{")) {
return context.engines.jsLegacy.parse(frontMatterCode, context);
}
let vm = new RetrieveGlobals(frontMatterCode, {
filePath,
// ignored if vm.Module is stable (or --experimental-vm-modules)
transformEsmImports: true,
});
// Future warning until vm.Module is stable:
// If the frontMatterCode uses `import` this uses the `experimentalModuleApi`
// option in node-retrieve-globals to workaround https://github.com/zachleat/node-retrieve-globals/issues/2
let data = {
page: {
// Theoretically fileSlug and filePathStem could be added here but require extensionMap
inputPath: filePath,
},
};
// this is async, but its handled in Eleventy upstream.
return vm.getGlobalContext(data, {
reuseGlobal: true,
dynamicImport: true,
// addRequire: true,
});
}

33
node_modules/@11ty/eleventy/src/Engines/Html.js generated vendored Normal file
View File

@@ -0,0 +1,33 @@
import TemplateEngine from "./TemplateEngine.js";
export default class Html extends TemplateEngine {
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
}
get cacheable() {
return true;
}
async #getPreEngine(preTemplateEngine) {
return this.engineManager.getEngine(preTemplateEngine, this.extensionMap);
}
async compile(str, inputPath, preTemplateEngine) {
if (preTemplateEngine) {
let engine = await this.#getPreEngine(preTemplateEngine);
let fnReady = engine.compile(str, inputPath);
return async function (data) {
let fn = await fnReady;
return fn(data);
};
}
return function () {
// do nothing with data if preTemplateEngine is falsy
return str;
};
}
}

240
node_modules/@11ty/eleventy/src/Engines/JavaScript.js generated vendored Normal file
View File

@@ -0,0 +1,240 @@
import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
import TemplateEngine from "./TemplateEngine.js";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
import getJavaScriptData from "../Util/GetJavaScriptData.js";
import { EleventyImport } from "../Util/Require.js";
import { augmentFunction, augmentObject } from "./Util/ContextAugmenter.js";
class JavaScriptTemplateNotDefined extends EleventyBaseError {}
export default class JavaScript extends TemplateEngine {
constructor(name, templateConfig) {
super(name, templateConfig);
this.instances = {};
this.config.events.on("eleventy#templateModified", (inputPath, metadata = {}) => {
let { usedByDependants, relevantLayouts } = metadata;
// Remove from cached instances when modified
let instancesToDelete = [
inputPath,
...(usedByDependants || []),
...(relevantLayouts || []),
].map((entry) => TemplatePath.addLeadingDotSlash(entry));
for (let inputPath of instancesToDelete) {
if (inputPath in this.instances) {
delete this.instances[inputPath];
}
}
});
}
get cacheable() {
return false;
}
normalize(result) {
if (Buffer.isBuffer(result)) {
return result.toString();
}
return result;
}
// String, Buffer, Promise
// Function, Class
// Object
// Module
_getInstance(mod) {
let noop = function () {
return "";
};
let originalModData = mod?.data;
if (typeof mod === "object" && mod.default && this.eleventyConfig.getIsProjectUsingEsm()) {
mod = mod.default;
}
if (typeof mod === "string" || mod instanceof Buffer || mod.then) {
return { render: () => mod };
} else if (typeof mod === "function") {
if (mod.prototype?.data || mod.prototype?.render) {
if (!("render" in mod.prototype)) {
mod.prototype.render = noop;
}
if (!("data" in mod.prototype) && !mod.data && originalModData) {
mod.prototype.data = originalModData;
}
return new mod();
} else {
return {
...(originalModData ? { data: originalModData } : undefined),
render: mod,
};
}
} else if ("data" in mod || "render" in mod) {
if (!mod.render) {
mod.render = noop;
}
if (!mod.data && originalModData) {
mod.data = originalModData;
}
return mod;
}
}
async #getInstanceFromInputPath(inputPath) {
let mod;
let relativeInputPath =
this.eleventyConfig.directories.getInputPathRelativeToInputDirectory(inputPath);
if (this.eleventyConfig.userConfig.isVirtualTemplate(relativeInputPath)) {
mod = this.eleventyConfig.userConfig.virtualTemplates[relativeInputPath].content;
} else {
let isEsm = this.eleventyConfig.getIsProjectUsingEsm();
let cacheBust = !this.cacheable || !this.config.useTemplateCache;
mod = await EleventyImport(inputPath, isEsm ? "esm" : "cjs", {
cacheBust,
});
}
let inst = this._getInstance(mod);
if (inst) {
this.instances[inputPath] = inst;
} else {
throw new JavaScriptTemplateNotDefined(
`No JavaScript template returned from ${inputPath}. Did you assign module.exports (CommonJS) or export (ESM)?`,
);
}
return inst;
}
async getInstanceFromInputPath(inputPath) {
if (!this.instances[inputPath]) {
this.instances[inputPath] = this.#getInstanceFromInputPath(inputPath);
}
return this.instances[inputPath];
}
/**
* JavaScript files defer to the module loader rather than read the files to strings
*
* @override
*/
needsToReadFileContents() {
return false;
}
/**
* Use the module loader directly
*
* @override
*/
useJavaScriptImport() {
return true;
}
async getExtraDataFromFile(inputPath) {
let inst = await this.getInstanceFromInputPath(inputPath);
return getJavaScriptData(inst, inputPath);
}
getJavaScriptFunctions(inst) {
let fns = {};
let configFns = this.config.javascriptFunctions;
for (let key in configFns) {
// prefer pre-existing `page` javascriptFunction, if one exists
fns[key] = augmentFunction(configFns[key], {
source: inst,
overwrite: false,
});
}
return fns;
}
// Backwards compat
static wrapJavaScriptFunction(inst, fn) {
return augmentFunction(fn, {
source: inst,
});
}
addExportsToBundles(inst, url) {
let cfg = this.eleventyConfig.userConfig;
if (!("getBundleManagers" in cfg)) {
return;
}
let managers = cfg.getBundleManagers();
for (let name in managers) {
let mgr = managers[name];
let key = mgr.getBundleExportKey();
if (!key) {
continue;
}
if (typeof inst[key] === "string") {
// export const css = ``;
mgr.addToPage(url, inst[key]);
} else if (isPlainObject(inst[key])) {
if (typeof inst[key][name] === "string") {
// Object with bundle names:
// export const bundle = {
// css: ``
// };
mgr.addToPage(url, inst[key][name]);
} else if (isPlainObject(inst[key][name])) {
// Object with bucket names:
// export const bundle = {
// css: {
// default: ``
// }
// };
for (let bucketName in inst[key][name]) {
mgr.addToPage(url, inst[key][name][bucketName], bucketName);
}
}
}
}
}
async compile(str, inputPath) {
let inst;
if (str) {
// When str has a value, it's being used for permalinks in data
inst = this._getInstance(str);
} else {
// For normal templates, str will be falsy.
inst = await this.getInstanceFromInputPath(inputPath);
}
if (inst?.render) {
return (data = {}) => {
// TODO does this do anything meaningful for non-classes?
// `inst` should have a normalized `render` function from _getInstance
// Map exports to bundles
if (data.page?.url) {
this.addExportsToBundles(inst, data.page.url);
}
augmentObject(inst, {
source: data,
overwrite: false,
});
Object.assign(inst, this.getJavaScriptFunctions(inst));
return this.normalize(inst.render.call(inst, data));
};
}
}
static shouldSpiderJavaScriptDependencies() {
return true;
}
}

331
node_modules/@11ty/eleventy/src/Engines/Liquid.js generated vendored Normal file
View File

@@ -0,0 +1,331 @@
import moo from "moo";
import { Tokenizer, TokenKind, evalToken, Liquid as LiquidJs } from "liquidjs";
import { TemplatePath } from "@11ty/eleventy-utils";
// import debugUtil from "debug";
import TemplateEngine from "./TemplateEngine.js";
import { augmentObject } from "./Util/ContextAugmenter.js";
// const debug = debugUtil("Eleventy:Liquid");
export default class Liquid extends TemplateEngine {
static argumentLexerOptions = {
number: /[0-9]+\.*[0-9]*/,
doubleQuoteString: /"(?:\\["\\]|[^\n"\\])*"/,
singleQuoteString: /'(?:\\['\\]|[^\n'\\])*'/,
keyword: /[a-zA-Z0-9.\-_]+/,
"ignore:whitespace": /[, \t]+/, // includes comma separator
};
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
this.liquidOptions = this.config.liquidOptions || {};
this.setLibrary(this.config.libraryOverrides.liquid);
this.argLexer = moo.compile(Liquid.argumentLexerOptions);
}
get cacheable() {
return true;
}
setLibrary(override) {
// warning, the include syntax supported here does not exactly match what Jekyll uses.
this.liquidLib = override || new LiquidJs(this.getLiquidOptions());
this.setEngineLib(this.liquidLib, Boolean(this.config.libraryOverrides.liquid));
this.addFilters(this.config.liquidFilters);
// TODO these all go to the same place (addTag), add warnings for overwrites
this.addCustomTags(this.config.liquidTags);
this.addAllShortcodes(this.config.liquidShortcodes);
this.addAllPairedShortcodes(this.config.liquidPairedShortcodes);
}
getLiquidOptions() {
let defaults = {
root: [this.dirs.includes, this.dirs.input], // supplemented in compile with inputPath below
extname: ".liquid",
strictFilters: true,
// TODO?
// cache: true,
};
let options = Object.assign(defaults, this.liquidOptions || {});
// debug("Liquid constructor options: %o", options);
return options;
}
static wrapFilter(name, fn) {
/**
* @this {object}
*/
return function (...args) {
// Set this.eleventy and this.page
if (typeof this.context?.get === "function") {
augmentObject(this, {
source: this.context,
getter: (key, context) => context.get([key]),
lazy: this.context.strictVariables,
});
}
// We *dont* wrap this in an EleventyFilterError because Liquid has a better error message with line/column information in the template
return fn.call(this, ...args);
};
}
// Shortcodes
static normalizeScope(context) {
let obj = {};
if (context) {
obj.ctx = context; // Full context available on `ctx`
// Set this.eleventy and this.page
augmentObject(obj, {
source: context,
getter: (key, context) => context.get([key]),
lazy: context.strictVariables,
});
}
return obj;
}
addCustomTags(tags) {
for (let name in tags) {
this.addTag(name, tags[name]);
}
}
addFilters(filters) {
for (let name in filters) {
this.addFilter(name, filters[name]);
}
}
addFilter(name, filter) {
this.liquidLib.registerFilter(name, Liquid.wrapFilter(name, filter));
}
addTag(name, tagFn) {
let tagObj;
if (typeof tagFn === "function") {
tagObj = tagFn(this.liquidLib);
} else {
throw new Error(
"Liquid.addTag expects a callback function to be passed in: addTag(name, function(liquidEngine) { return { parse: …, render: … } })",
);
}
this.liquidLib.registerTag(name, tagObj);
}
addAllShortcodes(shortcodes) {
for (let name in shortcodes) {
this.addShortcode(name, shortcodes[name]);
}
}
addAllPairedShortcodes(shortcodes) {
for (let name in shortcodes) {
this.addPairedShortcode(name, shortcodes[name]);
}
}
static parseArguments(lexer, str) {
let argArray = [];
if (!lexer) {
lexer = moo.compile(Liquid.argumentLexerOptions);
}
if (typeof str === "string") {
lexer.reset(str);
let arg = lexer.next();
while (arg) {
/*{
type: 'doubleQuoteString',
value: '"test 2"',
text: '"test 2"',
toString: [Function: tokenToString],
offset: 0,
lineBreaks: 0,
line: 1,
col: 1 }*/
if (arg.type.indexOf("ignore:") === -1) {
// Push the promise into an array instead of awaiting it here.
// This forces the promises to run in order with the correct scope value for each arg.
// Otherwise they run out of order and can lead to undefined values for arguments in layout template shortcodes.
// console.log( arg.value, scope, engine );
argArray.push(arg.value);
}
arg = lexer.next();
}
}
return argArray;
}
static parseArgumentsBuiltin(args) {
let tokenizer = new Tokenizer(args);
let parsedArgs = [];
let value = tokenizer.readValue();
while (value) {
parsedArgs.push(value);
tokenizer.skipBlank();
if (tokenizer.peek() === ",") {
tokenizer.advance();
}
value = tokenizer.readValue();
}
tokenizer.end();
return parsedArgs;
}
addShortcode(shortcodeName, shortcodeFn) {
let _t = this;
this.addTag(shortcodeName, function (liquidEngine) {
return {
parse(tagToken) {
this.name = tagToken.name;
if (_t.config.liquidParameterParsing === "builtin") {
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
} else {
this.legacyArgs = tagToken.args;
}
},
render: function* (ctx) {
let argArray = [];
if (this.legacyArgs) {
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
for (let arg of rawArgs) {
let b = yield liquidEngine.evalValue(arg, ctx);
argArray.push(b);
}
} else if (this.orderedArgs) {
for (let arg of this.orderedArgs) {
let b = yield evalToken(arg, ctx);
argArray.push(b);
}
}
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), ...argArray);
return ret;
},
};
});
}
addPairedShortcode(shortcodeName, shortcodeFn) {
let _t = this;
this.addTag(shortcodeName, function (liquidEngine) {
return {
parse(tagToken, remainTokens) {
this.name = tagToken.name;
if (_t.config.liquidParameterParsing === "builtin") {
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
} else {
this.legacyArgs = tagToken.args;
}
this.templates = [];
var stream = liquidEngine.parser
.parseStream(remainTokens)
.on("template", (tpl) => this.templates.push(tpl))
.on("tag:end" + shortcodeName, () => stream.stop())
.on("end", () => {
throw new Error(`tag ${tagToken.raw} not closed`);
});
stream.start();
},
render: function* (ctx /*, emitter*/) {
let argArray = [];
if (this.legacyArgs) {
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
for (let arg of rawArgs) {
let b = yield liquidEngine.evalValue(arg, ctx);
argArray.push(b);
}
} else if (this.orderedArgs) {
for (let arg of this.orderedArgs) {
let b = yield evalToken(arg, ctx);
argArray.push(b);
}
}
const html = yield liquidEngine.renderer.renderTemplates(this.templates, ctx);
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), html, ...argArray);
return ret;
},
};
});
}
parseForSymbols(str) {
if (!str) {
return [];
}
let tokenizer = new Tokenizer(str);
/** @type {Array} */
let tokens = tokenizer.readTopLevelTokens();
let symbols = tokens
.filter((token) => token.kind === TokenKind.Output)
.map((token) => {
// manually remove filters 😅
return token.content.split("|").map((entry) => entry.trim())[0];
});
return symbols;
}
// Dont return a boolean if permalink is a function (see TemplateContent->renderPermalink)
/** @returns {boolean|undefined} */
permalinkNeedsCompilation(str) {
if (typeof str === "string") {
return this.needsCompilation(str);
}
}
needsCompilation(str) {
let options = this.liquidLib.options;
return (
str.indexOf(options.tagDelimiterLeft) !== -1 ||
str.indexOf(options.outputDelimiterLeft) !== -1
);
}
async compile(str, inputPath) {
let engine = this.liquidLib;
let tmplReady = engine.parse(str, inputPath);
// Required for relative includes
let options = {};
if (!inputPath || inputPath === "liquid" || inputPath === "md") {
// do nothing
} else {
options.root = [TemplatePath.getDirFromFilePath(inputPath)];
}
return async function (data) {
let tmpl = await tmplReady;
return engine.render(tmpl, data, options);
};
}
}

100
node_modules/@11ty/eleventy/src/Engines/Markdown.js generated vendored Normal file
View File

@@ -0,0 +1,100 @@
import markdownIt from "markdown-it";
import TemplateEngine from "./TemplateEngine.js";
export default class Markdown extends TemplateEngine {
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
this.markdownOptions = {};
this.setLibrary(this.config.libraryOverrides.md);
}
get cacheable() {
return true;
}
setLibrary(mdLib) {
this.mdLib = mdLib || markdownIt(this.getMarkdownOptions());
// Overrides a highlighter set in `markdownOptions`
// This is separate so devs can pass in a new mdLib and still use the official eleventy plugin for markdown highlighting
if (this.config.markdownHighlighter && typeof this.mdLib.set === "function") {
this.mdLib.set({
highlight: this.config.markdownHighlighter,
});
}
if (typeof this.mdLib.disable === "function") {
// Disable indented code blocks by default (Issue #2438)
this.mdLib.disable("code");
}
this.setEngineLib(this.mdLib, Boolean(this.config.libraryOverrides.md));
}
setMarkdownOptions(options) {
this.markdownOptions = options;
}
getMarkdownOptions() {
// work with "mode" presets https://github.com/markdown-it/markdown-it#init-with-presets-and-options
if (typeof this.markdownOptions === "string") {
return this.markdownOptions;
}
return Object.assign(
{
html: true,
},
this.markdownOptions || {},
);
}
// TODO use preTemplateEngine to help inform this
// needsCompilation() {
// return super.needsCompilation();
// }
async #getPreEngine(preTemplateEngine) {
if (typeof preTemplateEngine === "string") {
return this.engineManager.getEngine(preTemplateEngine, this.extensionMap);
}
return preTemplateEngine;
}
async compile(str, inputPath, preTemplateEngine, bypassMarkdown) {
let mdlib = this.mdLib;
if (preTemplateEngine) {
let engine = await this.#getPreEngine(preTemplateEngine);
let fnReady = engine.compile(str, inputPath);
if (bypassMarkdown) {
return async function (data) {
let fn = await fnReady;
return fn(data);
};
} else {
return async function (data) {
let fn = await fnReady;
let preTemplateEngineRender = await fn(data);
let finishedRender = mdlib.render(preTemplateEngineRender, data);
return finishedRender;
};
}
} else {
if (bypassMarkdown) {
return function () {
return str;
};
} else {
return function (data) {
return mdlib.render(str, data);
};
}
}
}
}

482
node_modules/@11ty/eleventy/src/Engines/Nunjucks.js generated vendored Executable file
View File

@@ -0,0 +1,482 @@
import NunjucksLib from "nunjucks";
import debugUtil from "debug";
import { TemplatePath } from "@11ty/eleventy-utils";
import TemplateEngine from "./TemplateEngine.js";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
import { augmentObject } from "./Util/ContextAugmenter.js";
import { withResolvers } from "../Util/PromiseUtil.js";
const debug = debugUtil("Eleventy:Nunjucks");
class EleventyNunjucksError extends EleventyBaseError {}
export default class Nunjucks extends TemplateEngine {
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
this.nunjucksEnvironmentOptions = this.config.nunjucksEnvironmentOptions || { dev: true };
this.nunjucksPrecompiledTemplates = this.config.nunjucksPrecompiledTemplates || {};
this._usingPrecompiled = Object.keys(this.nunjucksPrecompiledTemplates).length > 0;
this.setLibrary(this.config.libraryOverrides.njk);
}
// v3.1.0-alpha.1 weve moved to use Nunjucks internal cache instead of Eleventys
// get cacheable() {
// return false;
// }
#getFileSystemDirs() {
let paths = new Set();
paths.add(super.getIncludesDir());
paths.add(TemplatePath.getWorkingDir());
// Filter out undefined paths
return Array.from(paths).filter(Boolean);
}
#setEnv(override) {
if (override) {
this.njkEnv = override;
} else if (this._usingPrecompiled) {
// Precompiled templates to avoid eval!
const NodePrecompiledLoader = function () {};
NodePrecompiledLoader.prototype.getSource = (name) => {
// https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/precompiled-loader.js#L5
return {
src: {
type: "code",
obj: this.nunjucksPrecompiledTemplates[name],
},
// Maybe add this?
// path,
// noCache: true
};
};
this.njkEnv = new NunjucksLib.Environment(
new NodePrecompiledLoader(),
this.nunjucksEnvironmentOptions,
);
} else {
let fsLoader = new NunjucksLib.FileSystemLoader(this.#getFileSystemDirs());
this.njkEnv = new NunjucksLib.Environment(fsLoader, this.nunjucksEnvironmentOptions);
}
this.config.events.emit("eleventy.engine.njk", {
nunjucks: NunjucksLib,
environment: this.njkEnv,
});
}
setLibrary(override) {
this.#setEnv(override);
// Note that a new Nunjucks engine instance is created for subsequent builds
// Eleventy Nunjucks is set to `cacheable` false above to opt out of Eleventy cache
this.config.events.on("eleventy#templateModified", (templatePath) => {
// NunjucksEnvironment:
// loader.pathToNames: {'ABSOLUTE_PATH/src/_includes/components/possum-home.css': 'components/possum-home.css'}
// loader.cache: { 'components/possum-home.css': [Template] }
// Nunjucks stores these as Operating System native paths
let absTmplPath = TemplatePath.normalizeOperatingSystemFilePath(
TemplatePath.absolutePath(templatePath),
);
for (let loader of this.njkEnv.loaders) {
let nunjucksName = loader.pathsToNames[absTmplPath];
if (nunjucksName) {
debug(
"Match found in Nunjucks cache via templateModified for %o, clearing this entry",
templatePath,
);
delete loader.pathsToNames[absTmplPath];
delete loader.cache[nunjucksName];
}
}
// Behavior prior to v3.1.0-alpha.1:
// this.njkEnv.invalidateCache();
});
this.setEngineLib(this.njkEnv, Boolean(this.config.libraryOverrides.njk));
this.addFilters(this.config.nunjucksFilters);
this.addFilters(this.config.nunjucksAsyncFilters, true);
// TODO these all go to the same place (addTag), add warnings for overwrites
// TODO(zachleat): variableName should work with quotes or without quotes (same as {% set %})
this.addPairedShortcode("setAsync", function (content, variableName) {
this.ctx[variableName] = content;
return "";
});
this.addCustomTags(this.config.nunjucksTags);
this.addAllShortcodes(this.config.nunjucksShortcodes);
this.addAllShortcodes(this.config.nunjucksAsyncShortcodes, true);
this.addAllPairedShortcodes(this.config.nunjucksPairedShortcodes);
this.addAllPairedShortcodes(this.config.nunjucksAsyncPairedShortcodes, true);
this.addGlobals(this.config.nunjucksGlobals);
}
addFilters(filters, isAsync) {
for (let name in filters) {
this.njkEnv.addFilter(name, Nunjucks.wrapFilter(name, filters[name]), isAsync);
}
}
static wrapFilter(name, fn) {
return function (...args) {
try {
augmentObject(this, {
source: this.ctx,
lazy: false, // context.env?.opts.throwOnUndefined,
});
return fn.call(this, ...args);
} catch (e) {
throw new EleventyNunjucksError(
`Error in Nunjucks Filter \`${name}\`${this.page ? ` (${this.page.inputPath})` : ""}`,
e,
);
}
};
}
// Shortcodes
static normalizeContext(context) {
let obj = {};
if (context.ctx) {
obj.ctx = context.ctx;
obj.env = context.env;
augmentObject(obj, {
source: context.ctx,
lazy: false, // context.env?.opts.throwOnUndefined,
});
}
return obj;
}
addCustomTags(tags) {
for (let name in tags) {
this.addTag(name, tags[name]);
}
}
addTag(name, tagFn) {
let tagObj;
if (typeof tagFn === "function") {
tagObj = tagFn(NunjucksLib, this.njkEnv);
} else {
throw new Error(
"Nunjucks.addTag expects a callback function to be passed in: addTag(name, function(nunjucksEngine) {})",
);
}
this.njkEnv.addExtension(name, tagObj);
}
addGlobals(globals) {
for (let name in globals) {
this.addGlobal(name, globals[name]);
}
}
addGlobal(name, globalFn) {
this.njkEnv.addGlobal(name, globalFn);
}
addAllShortcodes(shortcodes, isAsync = false) {
for (let name in shortcodes) {
this.addShortcode(name, shortcodes[name], isAsync);
}
}
addAllPairedShortcodes(shortcodes, isAsync = false) {
for (let name in shortcodes) {
this.addPairedShortcode(name, shortcodes[name], isAsync);
}
}
_getShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {
return function ShortcodeFunction() {
this.tags = [shortcodeName];
this.parse = function (parser, nodes) {
let args;
let tok = parser.nextToken();
args = parser.parseSignature(true, true);
// Nunjucks bug with non-paired custom tags bug still exists even
// though this issue is closed. Works fine for paired.
// https://github.com/mozilla/nunjucks/issues/158
if (args.children.length === 0) {
args.addChild(new nodes.Literal(0, 0, ""));
}
parser.advanceAfterBlockEnd(tok.value);
if (isAsync) {
return new nodes.CallExtensionAsync(this, "run", args);
}
return new nodes.CallExtension(this, "run", args);
};
this.run = function (...args) {
let resolve;
if (isAsync) {
resolve = args.pop();
}
let [context, ...argArray] = args;
if (isAsync) {
let ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);
// #3286 error messaging when the shortcode is not a promise
if (!ret?.then) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks shortcode \`${shortcodeName}\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,
),
);
}
ret.then(
function (returnValue) {
resolve(null, new NunjucksLib.runtime.SafeString("" + returnValue));
},
function (e) {
resolve(
new EleventyNunjucksError(`Error with Nunjucks shortcode \`${shortcodeName}\``, e),
);
},
);
} else {
try {
let ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);
return new NunjucksLib.runtime.SafeString("" + ret);
} catch (e) {
throw new EleventyNunjucksError(
`Error with Nunjucks shortcode \`${shortcodeName}\``,
e,
);
}
}
};
};
}
_getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {
return function PairedShortcodeFunction() {
this.tags = [shortcodeName];
this.parse = function (parser, nodes) {
var tok = parser.nextToken();
var args = parser.parseSignature(true, true);
parser.advanceAfterBlockEnd(tok.value);
var body = parser.parseUntilBlocks("end" + shortcodeName);
parser.advanceAfterBlockEnd();
return new nodes.CallExtensionAsync(this, "run", args, [body]);
};
this.run = function (...args) {
let resolve = args.pop();
let body = args.pop();
let [context, ...argArray] = args;
body(function (e, bodyContent) {
if (e) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
e,
),
);
}
if (isAsync) {
let ret = shortcodeFn.call(
Nunjucks.normalizeContext(context),
bodyContent,
...argArray,
);
// #3286 error messaging when the shortcode is not a promise
if (!ret?.then) {
throw new EleventyNunjucksError(
`Error with Nunjucks shortcode \`${shortcodeName}\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,
);
}
ret.then(
function (returnValue) {
resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
},
function (e) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
e,
),
);
},
);
} else {
try {
resolve(
null,
new NunjucksLib.runtime.SafeString(
shortcodeFn.call(Nunjucks.normalizeContext(context), bodyContent, ...argArray),
),
);
} catch (e) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
e,
),
);
}
}
});
};
};
}
addShortcode(shortcodeName, shortcodeFn, isAsync = false) {
let fn = this._getShortcodeFn(shortcodeName, shortcodeFn, isAsync);
this.njkEnv.addExtension(shortcodeName, new fn());
}
addPairedShortcode(shortcodeName, shortcodeFn, isAsync = false) {
let fn = this._getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync);
this.njkEnv.addExtension(shortcodeName, new fn());
}
// Dont return a boolean if permalink is a function (see TemplateContent->renderPermalink)
permalinkNeedsCompilation(str) {
if (typeof str === "string") {
return this.needsCompilation(str);
}
}
needsCompilation(str) {
// Defend against syntax customisations:
// https://mozilla.github.io/nunjucks/api.html#customizing-syntax
let optsTags = this.njkEnv.opts.tags || {};
let blockStart = optsTags.blockStart || "{%";
let variableStart = optsTags.variableStart || "{{";
let commentStart = optsTags.variableStart || "{#";
return (
str.indexOf(blockStart) !== -1 ||
str.indexOf(variableStart) !== -1 ||
str.indexOf(commentStart) !== -1
);
}
_getParseExtensions() {
if (this._parseExtensions) {
return this._parseExtensions;
}
// add extensions so the parser knows about our custom tags/blocks
let ext = [];
for (let name in this.config.nunjucksTags) {
let fn = this._getShortcodeFn(name, () => {});
ext.push(new fn());
}
for (let name in this.config.nunjucksShortcodes) {
let fn = this._getShortcodeFn(name, () => {});
ext.push(new fn());
}
for (let name in this.config.nunjucksAsyncShortcodes) {
let fn = this._getShortcodeFn(name, () => {}, true);
ext.push(new fn());
}
for (let name in this.config.nunjucksPairedShortcodes) {
let fn = this._getPairedShortcodeFn(name, () => {});
ext.push(new fn());
}
for (let name in this.config.nunjucksAsyncPairedShortcodes) {
let fn = this._getPairedShortcodeFn(name, () => {}, true);
ext.push(new fn());
}
this._parseExtensions = ext;
return ext;
}
/* Outputs an Array of lodash get selectors */
parseForSymbols(str) {
if (!str) {
return [];
}
const { parser, nodes } = NunjucksLib;
let obj = parser.parse(str, this._getParseExtensions());
if (!obj) {
return [];
}
let linesplit = str.split("\n");
let values = obj.findAll(nodes.Value);
let symbols = obj.findAll(nodes.Symbol).map((entry) => {
let name = [entry.value];
let nestedIndex = -1;
for (let val of values) {
if (nestedIndex > -1) {
/* deep.object.syntax */
if (linesplit[val.lineno].charAt(nestedIndex) === ".") {
name.push(val.value);
nestedIndex += val.value.length + 1;
} else {
nestedIndex = -1;
}
} else if (
val.lineno === entry.lineno &&
val.colno === entry.colno &&
val.value === entry.value
) {
nestedIndex = entry.colno + entry.value.length;
}
}
return name.join(".");
});
let uniqueSymbols = Array.from(new Set(symbols));
return uniqueSymbols;
}
async compile(str, inputPath) {
let tmpl;
// *All* templates are precompiled to avoid runtime eval
if (this._usingPrecompiled) {
tmpl = this.njkEnv.getTemplate(str, true);
} else if (!inputPath || inputPath === "njk" || inputPath === "md") {
tmpl = new NunjucksLib.Template(str, this.njkEnv, null, false);
} else {
tmpl = new NunjucksLib.Template(str, this.njkEnv, inputPath, false);
}
return function (data) {
let { promise, resolve, reject } = withResolvers();
tmpl.render(data, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
return promise;
};
}
}

View File

@@ -0,0 +1,206 @@
import debugUtil from "debug";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
class TemplateEngineConfigError extends EleventyBaseError {}
const debug = debugUtil("Eleventy:TemplateEngine");
const AMENDED_INSTANCES = new Set();
export default class TemplateEngine {
#extensionMap;
#engineManager;
#benchmarks;
constructor(name, eleventyConfig) {
this.name = name;
this.engineLib = null;
if (!eleventyConfig) {
throw new TemplateEngineConfigError("Missing `eleventyConfig` argument.");
}
this.eleventyConfig = eleventyConfig;
}
get cacheable() {
return false;
}
get dirs() {
return this.eleventyConfig.directories;
}
get inputDir() {
return this.dirs.input;
}
get includesDir() {
return this.dirs.includes;
}
get config() {
if (this.eleventyConfig.constructor.name !== "TemplateConfig") {
throw new Error("Expecting a TemplateConfig instance.");
}
return this.eleventyConfig.getConfig();
}
get benchmarks() {
if (!this.#benchmarks) {
this.#benchmarks = {
aggregate: this.config.benchmarkManager.get("Aggregate"),
};
}
return this.#benchmarks;
}
get engineManager() {
return this.#engineManager;
}
set engineManager(manager) {
this.#engineManager = manager;
}
get extensionMap() {
if (!this.#extensionMap) {
throw new Error("Internal error: missing `extensionMap` in TemplateEngine.");
}
return this.#extensionMap;
}
set extensionMap(map) {
this.#extensionMap = map;
}
get extensions() {
if (!this._extensions) {
this._extensions = this.extensionMap.getExtensionsFromKey(this.name);
}
return this._extensions;
}
get extensionEntries() {
if (!this._extensionEntries) {
this._extensionEntries = this.extensionMap.getExtensionEntriesFromKey(this.name);
}
return this._extensionEntries;
}
getName() {
return this.name;
}
// Backwards compat
getIncludesDir() {
return this.includesDir;
}
/**
* @protected
*/
setEngineLib(engineLib, isOverrideViaSetLibrary = false) {
this.engineLib = engineLib;
// Run engine amendments (via issue #2438)
// Issue #3816: this isnt ideal but there is no other way to reset a markdown instance if it was also overridden by addLibrary
if (AMENDED_INSTANCES.has(engineLib)) {
return;
}
if (isOverrideViaSetLibrary) {
AMENDED_INSTANCES.add(engineLib);
}
debug(
"Running amendLibrary for %o (number of amendments: %o)",
this.name,
this.config.libraryAmendments[this.name]?.length,
);
for (let amendment of this.config.libraryAmendments[this.name] || []) {
// TODO itd be nice if this were async friendly
amendment(engineLib);
}
}
getEngineLib() {
return this.engineLib;
}
async _testRender(str, data) {
// @ts-ignore
let fn = await this.compile(str);
return fn(data);
}
useJavaScriptImport() {
return false;
}
// JavaScript files defer to the module loader rather than read the files to strings
needsToReadFileContents() {
return true;
}
getExtraDataFromFile() {
return {};
}
getCompileCacheKey(str, inputPath) {
// Changing to use inputPath and contents, using only file contents (`str`) caused issues when two
// different files had identical content (2.0.0-canary.16)
// Caches are now segmented based on inputPath so using inputPath here is superfluous (2.0.0-canary.19)
// But we do want a non-falsy value here even if `str` is an empty string.
return {
useCache: true,
key: inputPath + str,
};
}
get defaultTemplateFileExtension() {
return "html";
}
// Whether or not to wrap in Eleventy layouts
useLayouts() {
return true;
}
/** @returns {boolean|undefined} */
permalinkNeedsCompilation(str) {
return this.needsCompilation();
}
// whether or not compile is needed or can we return the plaintext?
needsCompilation(str) {
return true;
}
/**
* Make sure compile is implemented downstream.
* @abstract
* @return {Promise}
*/
async compile() {
throw new Error("compile() must be implemented by engine");
}
// See https://v3.11ty.dev/docs/watch-serve/#watch-javascript-dependencies
static shouldSpiderJavaScriptDependencies() {
return false;
}
hasDependencies(inputPath) {
if (this.config.uses.getDependencies(inputPath) === false) {
return false;
}
return true;
}
isFileRelevantTo(inputPath, comparisonFile) {
return this.config.uses.isFileRelevantTo(inputPath, comparisonFile);
}
}

View File

@@ -0,0 +1,193 @@
import debugUtil from "debug";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
const debug = debugUtil("Eleventy:TemplateEngineManager");
class TemplateEngineManager {
constructor(eleventyConfig) {
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
throw new EleventyBaseError("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);
}
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 = import("./Markdown.js").then((mod) => mod.default);
} else if (extension === "html") {
promise = import("./Html.js").then((mod) => mod.default);
} else if (extension === "njk") {
promise = import("./Nunjucks.js").then((mod) => mod.default);
} else if (extension === "liquid") {
promise = import("./Liquid.js").then((mod) => mod.default);
} else if (extension === "11ty.js") {
promise = import("./JavaScript.js").then((mod) => mod.default);
} else {
promise = this.getCustomEngineClass();
}
this.importCache[extension] = promise;
return promise;
}
async getCustomEngineClass() {
if (!this._CustomEngine) {
this._CustomEngine = import("./Custom.js").then((mod) => mod.default);
}
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;
}
isEngineRemovedFromCore(name) {
return ["ejs", "hbs", "mustache", "haml", "pug"].includes(name) && !this.hasEngine(name);
}
async getEngine(name, extensionMap) {
// Bundled 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 engine 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;

View File

@@ -0,0 +1,67 @@
const DATA_KEYS = ["page", "eleventy"];
function augmentFunction(fn, options = {}) {
let t = typeof fn;
if (t !== "function") {
throw new Error(
"Invalid type passed to `augmentFunction`. A function was expected and received: " + t,
);
}
/** @this {object} */
return function (...args) {
let context = augmentObject(this || {}, options);
return fn.call(context, ...args);
};
}
function augmentObject(targetObject, options = {}) {
options = Object.assign(
{
source: undefined, // where to copy from
overwrite: true,
lazy: false, // lazily fetch the property
// getter: function() {},
},
options,
);
for (let key of DATA_KEYS) {
// Skip if overwrite: false and prop already exists on target
if (!options.overwrite && targetObject[key]) {
continue;
}
if (options.lazy) {
let value;
if (typeof options.getter == "function") {
value = () => options.getter(key, options.source);
} else {
value = () => options.source?.[key];
}
// lazy getter important for Liquid strictVariables support
Object.defineProperty(targetObject, key, {
writable: true,
configurable: true,
enumerable: true,
value,
});
} else {
let value;
if (typeof options.getter == "function") {
value = options.getter(key, options.source);
} else {
value = options.source?.[key];
}
if (value) {
targetObject[key] = value;
}
}
}
return targetObject;
}
export { DATA_KEYS as augmentKeys, augmentFunction, augmentObject };