summaryrefslogtreecommitdiff
path: root/node_modules/@11ty/eleventy/src/UserConfig.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@11ty/eleventy/src/UserConfig.js')
-rw-r--r--node_modules/@11ty/eleventy/src/UserConfig.js1339
1 files changed, 1339 insertions, 0 deletions
diff --git a/node_modules/@11ty/eleventy/src/UserConfig.js b/node_modules/@11ty/eleventy/src/UserConfig.js
new file mode 100644
index 0000000..215327f
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/UserConfig.js
@@ -0,0 +1,1339 @@
+import chalk from "kleur";
+import { DateTime } from "luxon";
+import yaml from "js-yaml";
+import matter from "gray-matter";
+import debugUtil from "debug";
+
+import { DeepCopy, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
+
+import HtmlBasePlugin from "./Plugins/HtmlBasePlugin.js";
+import RenderPlugin from "./Plugins/RenderPlugin.js";
+import InputPathToUrlPlugin from "./Plugins/InputPathToUrl.js";
+
+import isAsyncFunction from "./Util/IsAsyncFunction.js";
+import objectFilter from "./Util/Objects/ObjectFilter.js";
+import EventEmitter from "./Util/AsyncEventEmitter.js";
+import EleventyCompatibility from "./Util/Compatibility.js";
+import EleventyBaseError from "./Errors/EleventyBaseError.js";
+import BenchmarkManager from "./Benchmark/BenchmarkManager.js";
+import JavaScriptFrontMatter from "./Engines/FrontMatter/JavaScript.js";
+import { augmentFunction } from "./Engines/Util/ContextAugmenter.js";
+
+const debug = debugUtil("Eleventy:UserConfig");
+
+class UserConfigError extends EleventyBaseError {}
+
+/**
+ * Eleventy’s user-land Configuration API
+ * @module 11ty/eleventy/UserConfig
+ */
+class UserConfig {
+ /** @type {boolean} */
+ #pluginExecution = false;
+ /** @type {boolean} */
+ #quietModeLocked = false;
+ /** @type {boolean} */
+ #dataDeepMergeModified = false;
+ /** @type {number|undefined} */
+ #uniqueId;
+ /** @type {number} */
+ #concurrency = 1;
+ // Before using os.availableParallelism(); see https://github.com/11ty/eleventy/issues/3596
+
+ constructor() {
+ // These are completely unnecessary lines to satisfy TypeScript
+ this.plugins = [];
+ this.templateFormatsAdded = [];
+ this.additionalWatchTargets = [];
+ this.watchTargetsConfigReset = new Set();
+ this.extensionMap = new Set();
+ this.dataExtensions = new Map();
+ this.urlTransforms = [];
+ this.customDateParsingCallbacks = new Set();
+ this.ignores = new Set();
+ this.events = new EventEmitter();
+
+ /** @type {object} */
+ this.directories = {};
+ /** @type {undefined} */
+ this.logger;
+ /** @type {string} */
+ this.dir;
+ /** @type {string} */
+ this.pathPrefix;
+ /** @type {object} */
+ this.errorReporting = {};
+ /** @type {object} */
+ this.templateHandling = {};
+
+ this.reset();
+ this.#uniqueId = Math.random();
+ }
+
+ // Internally used in TemplateContent for cache keys
+ _getUniqueId() {
+ return this.#uniqueId;
+ }
+
+ reset() {
+ debug("Resetting EleventyConfig to initial values.");
+
+ /** @type {EventEmitter} */
+ this.events = new EventEmitter();
+ this.events.setMaxListeners(25); // defaults to 10
+
+ /** @type {BenchmarkManager} */
+ this.benchmarkManager = new BenchmarkManager();
+
+ /** @type {object} */
+ this.benchmarks = {
+ /** @type {import('./Benchmark/BenchmarkGroup.js')} */
+ config: this.benchmarkManager.get("Configuration"),
+ /** @type {import('./Benchmark/BenchmarkGroup.js')} */
+ aggregate: this.benchmarkManager.get("Aggregate"),
+ };
+
+ /** @type {object} */
+ this.directoryAssignments = {};
+ /** @type {object} */
+ this.collections = {};
+ /** @type {object} */
+ this.precompiledCollections = {};
+ this.templateFormats = undefined;
+ this.templateFormatsAdded = [];
+
+ /** @type {object} */
+ this.universal = {
+ filters: {},
+ shortcodes: {},
+ pairedShortcodes: {},
+ };
+
+ /** @type {object} */
+ this.liquid = {
+ options: {},
+ tags: {},
+ filters: {},
+ shortcodes: {},
+ pairedShortcodes: {},
+ parameterParsing: "legacy", // or builtin
+ };
+
+ /** @type {object} */
+ this.nunjucks = {
+ // `dev: true` gives us better error messaging
+ environmentOptions: { dev: true },
+ precompiledTemplates: {},
+ filters: {},
+ asyncFilters: {},
+ tags: {},
+ globals: {},
+ shortcodes: {},
+ pairedShortcodes: {},
+ asyncShortcodes: {},
+ asyncPairedShortcodes: {},
+ };
+
+ /** @type {object} */
+ this.javascript = {
+ functions: {},
+ filters: {},
+ shortcodes: {},
+ pairedShortcodes: {},
+ };
+
+ this.markdownHighlighter = null;
+
+ /** @type {object} */
+ this.libraryOverrides = {};
+
+ /** @type {object} */
+ this.passthroughCopies = {};
+ this.passthroughCopiesHtmlRelative = new Set();
+
+ /** @type {object} */
+ this.layoutAliases = {};
+ this.layoutResolution = true; // extension-less layout files
+
+ /** @type {object} */
+ this.linters = {};
+ /** @type {object} */
+ this.transforms = {};
+ /** @type {object} */
+ this.preprocessors = {};
+
+ this.activeNamespace = "";
+ this.DateTime = DateTime;
+ this.dynamicPermalinks = true;
+
+ this.useGitIgnore = true;
+
+ let defaultIgnores = new Set();
+ defaultIgnores.add("**/node_modules/**");
+ defaultIgnores.add(".git/**"); // TODO `**/.git/**`
+ this.ignores = new Set(defaultIgnores);
+ this.watchIgnores = new Set(defaultIgnores);
+
+ this.dataDeepMerge = true;
+ this.extensionMap = new Set();
+ /** @type {object} */
+ this.extensionConflictMap = {};
+ this.watchJavaScriptDependencies = true;
+ this.additionalWatchTargets = [];
+ this.watchTargetsConfigReset = new Set();
+ /** @type {object} */
+ this.serverOptions = {};
+ /** @type {object} */
+ this.globalData = {};
+ /** @type {object} */
+ this.chokidarConfig = {};
+ this.watchThrottleWaitTime = 0; //ms
+
+ // using Map to preserve insertion order
+ this.dataExtensions = new Map();
+
+ this.quietMode = false;
+
+ this.plugins = [];
+
+ this.useTemplateCache = true;
+ this.dataFilterSelectors = new Set();
+
+ /** @type {object} */
+ this.libraryAmendments = {};
+ this.serverPassthroughCopyBehavior = "copy"; // or "passthrough"
+ this.urlTransforms = [];
+
+ // Defaults in `defaultConfig.js`
+ this.dataFileSuffixesOverride = false;
+ this.dataFileDirBaseNameOverride = false;
+
+ /** @type {object} */
+ this.frontMatterParsingOptions = {
+ // Set a project-wide default.
+ // language: "yaml",
+
+ // Supplementary engines
+ engines: {
+ yaml: yaml.load.bind(yaml),
+
+ // Backwards compatible with `js` object front matter
+ // https://github.com/11ty/eleventy/issues/2819
+ javascript: JavaScriptFrontMatter,
+
+ // Needed for fallback behavior in the new `javascript` engine
+ // @ts-ignore
+ jsLegacy: matter.engines.javascript,
+
+ node: function () {
+ throw new Error(
+ "The `node` front matter type was a 3.0.0-alpha.x only feature, removed for stable release. Rename to `js` or `javascript` instead!",
+ );
+ },
+ },
+ };
+
+ /** @type {object} */
+ this.virtualTemplates = {};
+ this.freezeReservedData = true;
+ this.customDateParsingCallbacks = new Set();
+
+ /** @type {object} */
+ this.errorReporting = {};
+ /** @type {object} */
+ this.templateHandling = {};
+
+ // Before using os.availableParallelism(); see https://github.com/11ty/eleventy/issues/3596
+ this.#concurrency = 1;
+ }
+
+ // compatibleRange is optional in 2.0.0-beta.2
+ versionCheck(compatibleRange) {
+ let compat = new EleventyCompatibility(compatibleRange);
+
+ if (!compat.isCompatible()) {
+ throw new UserConfigError(compat.getErrorMessage());
+ }
+ }
+
+ /*
+ * Events
+ */
+
+ // Duplicate event bindings are avoided with the `reset` method above.
+ // A new EventEmitter instance is created when the config is reset.
+ on(eventName, callback) {
+ return this.events.on(eventName, callback);
+ }
+
+ once(eventName, callback) {
+ return this.events.once(eventName, callback);
+ }
+
+ emit(eventName, ...args) {
+ return this.events.emit(eventName, ...args);
+ }
+
+ setEventEmitterMode(mode) {
+ this.events.setHandlerMode(mode);
+ }
+
+ /*
+ * Universal getters
+ */
+ getFilter(name) {
+ // JavaScript functions are included here for backwards compatibility https://github.com/11ty/eleventy/issues/3365
+ return this.universal.filters[name] || this.javascript.functions[name];
+ }
+
+ getFilters(options = {}) {
+ if (options.type) {
+ return objectFilter(
+ this.universal.filters,
+ (entry) => entry.__eleventyInternal?.type === options.type,
+ );
+ }
+
+ return this.universal.filters;
+ }
+
+ getShortcode(name) {
+ return this.universal.shortcodes[name];
+ }
+
+ getShortcodes(options = {}) {
+ if (options.type) {
+ return objectFilter(
+ this.universal.shortcodes,
+ (entry) => entry.__eleventyInternal?.type === options.type,
+ );
+ }
+
+ return this.universal.shortcodes;
+ }
+
+ getPairedShortcode(name) {
+ return this.universal.pairedShortcodes[name];
+ }
+
+ getPairedShortcodes(options = {}) {
+ if (options.type) {
+ return objectFilter(
+ this.universal.pairedShortcodes,
+ (entry) => entry.__eleventyInternal?.type === options.type,
+ );
+ }
+ return this.universal.pairedShortcodes;
+ }
+
+ /*
+ * Private utilities
+ */
+ #add(target, originalName, callback, options) {
+ let { description, functionName } = options;
+
+ if (typeof callback !== "function") {
+ throw new Error(`Invalid definition for "${originalName}" ${description}.`);
+ }
+
+ let name = this.getNamespacedName(originalName);
+
+ if (target[name]) {
+ debug(
+ chalk.yellow(`Warning, overwriting previous ${description} "%o" via \`%o(%o)\``),
+ name,
+ functionName,
+ originalName,
+ );
+ } else {
+ debug(`Adding new ${description} "%o" via \`%o(%o)\``, name, functionName, originalName);
+ }
+
+ target[name] = this.#decorateCallback(`"${name}" ${description}`, callback);
+ }
+
+ #decorateCallback(type, callback) {
+ return this.benchmarks.config.add(type, callback);
+ }
+
+ /*
+ * Markdown
+ */
+
+ // This is a method for plugins, probably shouldn’t use this in projects.
+ // Projects should use `setLibrary` as documented here:
+ // https://github.com/11ty/eleventy/blob/master/docs/engines/markdown.md#use-your-own-options
+ addMarkdownHighlighter(highlightFn) {
+ this.markdownHighlighter = highlightFn;
+ }
+
+ /*
+ * Filters
+ */
+
+ addLiquidFilter(name, callback) {
+ this.#add(this.liquid.filters, name, callback, {
+ description: "Liquid Filter",
+ functionName: "addLiquidFilter",
+ });
+ }
+
+ addNunjucksAsyncFilter(name, callback) {
+ this.#add(this.nunjucks.asyncFilters, name, callback, {
+ description: "Nunjucks Filter",
+ functionName: "addNunjucksAsyncFilter",
+ });
+ }
+
+ // Support the nunjucks style syntax for asynchronous filter add
+ addNunjucksFilter(name, callback, isAsync = false) {
+ if (isAsync) {
+ // namespacing happens downstream
+ this.addNunjucksAsyncFilter(name, callback);
+ } else {
+ this.#add(this.nunjucks.filters, name, callback, {
+ description: "Nunjucks Filter",
+ functionName: "addNunjucksFilter",
+ });
+ }
+ }
+
+ addJavaScriptFilter(name, callback) {
+ this.#add(this.javascript.filters, name, callback, {
+ description: "JavaScript Filter",
+ functionName: "addJavaScriptFilter",
+ });
+
+ // Backwards compat for a time before `addJavaScriptFilter` existed.
+ this.addJavaScriptFunction(name, callback);
+ }
+
+ addFilter(name, callback) {
+ // This method *requires* `async function` and will not work with `function` that returns a promise
+ if (isAsyncFunction(callback)) {
+ this.addAsyncFilter(name, callback);
+ return;
+ }
+
+ // namespacing happens downstream
+ this.#add(this.universal.filters, name, callback, {
+ description: "Universal Filter",
+ functionName: "addFilter",
+ });
+
+ this.addLiquidFilter(name, callback);
+ this.addJavaScriptFilter(name, callback);
+ this.addNunjucksFilter(
+ name,
+ /** @this {any} */
+ function (...args) {
+ // Note that `callback` is already a function as the `#add` method throws an error if not.
+ let ret = callback.call(this, ...args);
+ if (ret instanceof Promise) {
+ throw new Error(
+ `Nunjucks *is* async-friendly with \`addFilter("${name}", async function() {})\` but you need to supply an \`async function\`. You returned a promise from \`addFilter("${name}", function() {})\`. Alternatively, use the \`addAsyncFilter("${name}")\` configuration API method.`,
+ );
+ }
+ return ret;
+ },
+ );
+ }
+
+ // Liquid, Nunjucks, and JS only
+ addAsyncFilter(name, callback) {
+ // namespacing happens downstream
+ this.#add(this.universal.filters, name, callback, {
+ description: "Universal Filter",
+ functionName: "addAsyncFilter",
+ });
+
+ this.addLiquidFilter(name, callback);
+ this.addJavaScriptFilter(name, callback);
+ this.addNunjucksAsyncFilter(
+ name,
+ /** @this {any} */
+ async function (...args) {
+ let cb = args.pop();
+ // Note that `callback` is already a function as the `#add` method throws an error if not.
+ let ret = await callback.call(this, ...args);
+ cb(null, ret);
+ },
+ );
+ }
+
+ /*
+ * Shortcodes
+ */
+
+ addShortcode(name, callback) {
+ // This method *requires* `async function` and will not work with `function` that returns a promise
+ if (isAsyncFunction(callback)) {
+ this.addAsyncShortcode(name, callback);
+ return;
+ }
+
+ this.#add(this.universal.shortcodes, name, callback, {
+ description: "Universal Shortcode",
+ functionName: "addShortcode",
+ });
+
+ this.addLiquidShortcode(name, callback);
+ this.addJavaScriptShortcode(name, callback);
+ this.addNunjucksShortcode(name, callback);
+ }
+
+ addAsyncShortcode(name, callback) {
+ this.#add(this.universal.shortcodes, name, callback, {
+ description: "Universal Shortcode",
+ functionName: "addAsyncShortcode",
+ });
+
+ // Related: #498
+ this.addNunjucksAsyncShortcode(name, callback);
+ this.addLiquidShortcode(name, callback);
+ this.addJavaScriptShortcode(name, callback);
+ }
+
+ addNunjucksAsyncShortcode(name, callback) {
+ this.#add(this.nunjucks.asyncShortcodes, name, callback, {
+ description: "Nunjucks Async Shortcode",
+ functionName: "addNunjucksAsyncShortcode",
+ });
+ }
+
+ addNunjucksShortcode(name, callback, isAsync = false) {
+ if (isAsync) {
+ this.addNunjucksAsyncShortcode(name, callback);
+ } else {
+ this.#add(this.nunjucks.shortcodes, name, callback, {
+ description: "Nunjucks Shortcode",
+ functionName: "addNunjucksShortcode",
+ });
+ }
+ }
+
+ addLiquidShortcode(name, callback) {
+ this.#add(this.liquid.shortcodes, name, callback, {
+ description: "Liquid Shortcode",
+ functionName: "addLiquidShortcode",
+ });
+ }
+
+ addPairedShortcode(name, callback) {
+ // This method *requires* `async function` and will not work with `function` that returns a promise
+ if (isAsyncFunction(callback)) {
+ this.addPairedAsyncShortcode(name, callback);
+ return;
+ }
+
+ this.#add(this.universal.pairedShortcodes, name, callback, {
+ description: "Universal Paired Shortcode",
+ functionName: "addPairedShortcode",
+ });
+
+ this.addPairedNunjucksShortcode(name, callback);
+ this.addPairedLiquidShortcode(name, callback);
+ this.addPairedJavaScriptShortcode(name, callback);
+ }
+
+ // Related: #498
+ addPairedAsyncShortcode(name, callback) {
+ this.#add(this.universal.pairedShortcodes, name, callback, {
+ description: "Universal Paired Async Shortcode",
+ functionName: "addPairedAsyncShortcode",
+ });
+
+ this.addPairedNunjucksAsyncShortcode(name, callback);
+ this.addPairedLiquidShortcode(name, callback);
+ this.addPairedJavaScriptShortcode(name, callback);
+ }
+
+ addPairedNunjucksAsyncShortcode(name, callback) {
+ this.#add(this.nunjucks.asyncPairedShortcodes, name, callback, {
+ description: "Nunjucks Async Paired Shortcode",
+ functionName: "addPairedNunjucksAsyncShortcode",
+ });
+ }
+
+ addPairedNunjucksShortcode(name, callback, isAsync = false) {
+ if (isAsync) {
+ this.addPairedNunjucksAsyncShortcode(name, callback);
+ } else {
+ this.#add(this.nunjucks.pairedShortcodes, name, callback, {
+ description: "Nunjucks Paired Shortcode",
+ functionName: "addPairedNunjucksShortcode",
+ });
+ }
+ }
+
+ addPairedLiquidShortcode(name, callback) {
+ this.#add(this.liquid.pairedShortcodes, name, callback, {
+ description: "Liquid Paired Shortcode",
+ functionName: "addPairedLiquidShortcode",
+ });
+ }
+
+ addJavaScriptShortcode(name, callback) {
+ this.#add(this.javascript.shortcodes, name, callback, {
+ description: "JavaScript Shortcode",
+ functionName: "addJavaScriptShortcode",
+ });
+
+ // Backwards compat for a time before `addJavaScriptShortcode` existed.
+ this.addJavaScriptFunction(name, callback);
+ }
+
+ addPairedJavaScriptShortcode(name, callback) {
+ this.#add(this.javascript.pairedShortcodes, name, callback, {
+ description: "JavaScript Paired Shortcode",
+ functionName: "addPairedJavaScriptShortcode",
+ });
+
+ // Backwards compat for a time before `addJavaScriptShortcode` existed.
+ this.addJavaScriptFunction(name, callback);
+ }
+
+ // Both Filters and shortcodes feed into this
+ addJavaScriptFunction(name, callback) {
+ this.#add(this.javascript.functions, name, callback, {
+ description: "JavaScript Function",
+ functionName: "addJavaScriptFunction",
+ });
+ }
+
+ /*
+ * Custom Tags
+ */
+
+ // tagCallback: function(liquidEngine) { return { parse: …, render: … }} };
+ addLiquidTag(name, tagFn) {
+ if (typeof tagFn !== "function") {
+ throw new UserConfigError(
+ `EleventyConfig.addLiquidTag expects a callback function to be passed in for ${name}: addLiquidTag(name, function(liquidEngine) { return { parse: …, render: … } })`,
+ );
+ }
+
+ this.#add(this.liquid.tags, name, tagFn, {
+ description: "Liquid Custom Tag",
+ functionName: "addLiquidTag",
+ });
+ }
+
+ addNunjucksTag(name, tagFn) {
+ if (typeof tagFn !== "function") {
+ throw new UserConfigError(
+ `EleventyConfig.addNunjucksTag expects a callback function to be passed in for ${name}: addNunjucksTag(name, function(nunjucksEngine) {})`,
+ );
+ }
+
+ this.#add(this.nunjucks.tags, name, tagFn, {
+ description: "Nunjucks Custom Tag",
+ functionName: "addNunjucksTag",
+ });
+ }
+
+ /*
+ * Plugins
+ */
+
+ // Internal method
+ _enablePluginExecution() {
+ this.#pluginExecution = true;
+ }
+
+ // Internal method
+ _disablePluginExecution() {
+ this.#pluginExecution = false;
+ }
+
+ /* Config is executed in two stages and plugins are the second stage—are we in the plugins stage? */
+ isPluginExecution() {
+ return this.#pluginExecution;
+ }
+
+ /**
+ * @typedef {function|Promise<function>|object} PluginDefinition
+ * @property {Function} [configFunction]
+ * @property {string} [eleventyPackage]
+ * @property {object} [eleventyPluginOptions={}]
+ * @property {boolean} [eleventyPluginOptions.unique]
+ */
+
+ /**
+ * addPlugin: async friendly in 3.0
+ *
+ * @param {PluginDefinition} plugin
+ */
+ addPlugin(plugin, options = {}) {
+ // First addPlugin of a unique plugin wins
+ if (plugin?.eleventyPluginOptions?.unique && this.hasPlugin(plugin)) {
+ debug("Skipping duplicate unique addPlugin for %o", this._getPluginName(plugin));
+ return;
+ }
+
+ if (this.isPluginExecution() || options?.immediate) {
+ // this might return a promise
+ return this._executePlugin(plugin, options);
+ } else {
+ this.plugins.push({
+ plugin,
+ options,
+ pluginNamespace: this.activeNamespace,
+ });
+ }
+ }
+
+ /** @param {string} name */
+ resolvePlugin(name) {
+ let filenameLookup = {
+ "@11ty/eleventy/html-base-plugin": HtmlBasePlugin,
+ "@11ty/eleventy/render-plugin": RenderPlugin,
+ "@11ty/eleventy/inputpath-to-url-plugin": InputPathToUrlPlugin,
+
+ // Async plugins:
+ // requires e.g. `await resolvePlugin("@11ty/eleventy/i18n-plugin")` to avoid preloading i18n dependencies.
+ // see https://github.com/11ty/eleventy-plugin-rss/issues/52
+ "@11ty/eleventy/i18n-plugin": "./Plugins/I18nPlugin.js",
+ };
+
+ if (!filenameLookup[name]) {
+ throw new Error(
+ `Invalid name "${name}" passed to resolvePlugin. Valid options: ${Object.keys(filenameLookup).join(", ")}`,
+ );
+ }
+
+ // Future improvement: add support for any npm package name?
+ if (typeof filenameLookup[name] === "string") {
+ // returns promise
+ return import(filenameLookup[name]).then((plugin) => plugin.default);
+ }
+
+ // return reference
+ return filenameLookup[name];
+ }
+
+ /** @param {string|PluginDefinition} plugin */
+ hasPlugin(plugin) {
+ let pluginName;
+ if (typeof plugin === "string") {
+ pluginName = plugin;
+ } else {
+ pluginName = this._getPluginName(plugin);
+ }
+
+ return this.plugins.some((entry) => this._getPluginName(entry.plugin) === pluginName);
+ }
+
+ // Using Function.name https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name#examples
+ /** @param {PluginDefinition} plugin */
+ _getPluginName(plugin) {
+ if (plugin?.eleventyPackage) {
+ return plugin.eleventyPackage;
+ }
+ if (typeof plugin === "function") {
+ return plugin.name;
+ }
+ if (plugin?.configFunction && typeof plugin.configFunction === "function") {
+ return plugin.configFunction.name;
+ }
+ }
+
+ // Starting in 3.0 the plugin callback might be asynchronous!
+ _executePlugin(plugin, options) {
+ let name = this._getPluginName(plugin);
+ let ret;
+ debug(`Adding %o plugin`, name || "anonymous");
+ let pluginBenchmark = this.benchmarks.aggregate.get("Configuration addPlugin");
+
+ if (typeof plugin === "function") {
+ pluginBenchmark.before();
+ this.benchmarks.config;
+ let configFunction = plugin;
+ ret = configFunction(this, options);
+ pluginBenchmark.after();
+ } else if (plugin?.configFunction) {
+ pluginBenchmark.before();
+
+ if (options && typeof options.init === "function") {
+ // init is not yet async-friendly but it’s also barely used
+ options.init.call(this, plugin.initArguments || {});
+ }
+
+ ret = plugin.configFunction(this, options);
+ pluginBenchmark.after();
+ } else {
+ throw new UserConfigError(
+ "Invalid EleventyConfig.addPlugin signature. Should be a function or a valid Eleventy plugin object.",
+ );
+ }
+ return ret;
+ }
+
+ /** @param {string} name */
+ getNamespacedName(name) {
+ return this.activeNamespace + name;
+ }
+
+ async namespace(pluginNamespace, callback) {
+ let validNamespace = pluginNamespace && typeof pluginNamespace === "string";
+ if (validNamespace) {
+ this.activeNamespace = pluginNamespace || "";
+ }
+
+ await callback(this);
+
+ if (validNamespace) {
+ this.activeNamespace = "";
+ }
+ }
+
+ /**
+ * Adds a path to a file or directory to the list of pass-through copies
+ * which are copied as-is to the output.
+ *
+ * @param {string|object} fileOrDir The path to the file or directory that should
+ * be copied. OR an object where the key is the input glob and the property is the output directory
+ * @param {object} copyOptions options for recursive-copy.
+ * see https://www.npmjs.com/package/recursive-copy#arguments
+ * default options are defined in TemplatePassthrough copyOptionsDefault
+ * @returns {any} a reference to the `EleventyConfig` object.
+ */
+ addPassthroughCopy(fileOrDir, copyOptions = {}) {
+ if (copyOptions.mode) {
+ if (copyOptions.mode !== "html-relative") {
+ throw new Error(
+ "Invalid `mode` option for `addPassthroughCopy`. Received: '" + copyOptions.mode + "'",
+ );
+ }
+ if (isPlainObject(fileOrDir)) {
+ throw new Error(
+ "mode: 'html-relative' does not yet support passthrough copy objects (input -> output mapping). Use a string glob or an Array of string globs.",
+ );
+ }
+
+ this.passthroughCopiesHtmlRelative?.add({
+ match: fileOrDir,
+ ...copyOptions,
+ });
+ } else if (typeof fileOrDir === "string") {
+ this.passthroughCopies[fileOrDir] = { outputPath: true, copyOptions };
+ } else {
+ for (let [inputPath, outputPath] of Object.entries(fileOrDir)) {
+ this.passthroughCopies[inputPath] = { outputPath, copyOptions };
+ }
+ }
+
+ return this;
+ }
+
+ /*
+ * Template Formats
+ */
+ _normalizeTemplateFormats() {
+ throw new Error("The internal _normalizeTemplateFormats() method was removed in Eleventy 3.0");
+ }
+
+ setTemplateFormats(templateFormats) {
+ this.templateFormats = templateFormats;
+ }
+
+ // additive, usually for plugins
+ addTemplateFormats(templateFormats) {
+ this.templateFormatsAdded.push(templateFormats);
+ }
+
+ /*
+ * Library Overrides and Options
+ */
+ setLibrary(engineName, libraryInstance) {
+ if (engineName === "liquid" && Object.keys(this.liquid.options).length) {
+ debug(
+ "WARNING: using `eleventyConfig.setLibrary` will override any configuration set using `.setLiquidOptions` via the config API. You’ll need to pass these options to the library yourself.",
+ );
+ } else if (engineName === "njk" && Object.keys(this.nunjucks.environmentOptions).length) {
+ debug(
+ "WARNING: using `eleventyConfig.setLibrary` will override any configuration set using `.setNunjucksEnvironmentOptions` via the config API. You’ll need to pass these options to the library yourself.",
+ );
+ }
+
+ this.libraryOverrides[engineName.toLowerCase()] = libraryInstance;
+ }
+
+ /* These callbacks run on both libraryOverrides and default library instances */
+ amendLibrary(engineName, callback) {
+ let name = engineName.toLowerCase();
+ if (!this.libraryAmendments[name]) {
+ this.libraryAmendments[name] = [];
+ }
+
+ this.libraryAmendments[name].push(callback);
+ }
+
+ setLiquidOptions(options) {
+ this.liquid.options = options;
+ }
+
+ setLiquidParameterParsing(behavior) {
+ if (behavior !== "legacy" && behavior !== "builtin") {
+ throw new Error(
+ `Invalid argument passed to \`setLiquidParameterParsing\`. Expected one of "legacy" or "builtin".`,
+ );
+ }
+ this.liquid.parameterParsing = behavior;
+ }
+
+ setNunjucksEnvironmentOptions(options) {
+ this.nunjucks.environmentOptions = options;
+ }
+
+ setNunjucksPrecompiledTemplates(templates) {
+ this.nunjucks.precompiledTemplates = templates;
+ }
+
+ setDynamicPermalinks(enabled) {
+ this.dynamicPermalinks = !!enabled;
+ }
+
+ setUseGitIgnore(enabled) {
+ this.useGitIgnore = !!enabled;
+ }
+
+ setDataDeepMerge(deepMerge) {
+ this.#dataDeepMergeModified = true;
+ this.dataDeepMerge = !!deepMerge;
+ }
+
+ // Used by the Upgrade Helper Plugin
+ isDataDeepMergeModified() {
+ return this.#dataDeepMergeModified;
+ }
+
+ addWatchTarget(additionalWatchTargets, options = {}) {
+ // Reset the config when the target path changes
+ if (options.resetConfig) {
+ this.watchTargetsConfigReset.add(additionalWatchTargets);
+ }
+
+ this.additionalWatchTargets.push(additionalWatchTargets);
+ }
+
+ setWatchJavaScriptDependencies(watchEnabled) {
+ this.watchJavaScriptDependencies = !!watchEnabled;
+ }
+
+ setServerOptions(options = {}, override = false) {
+ if (override) {
+ this.serverOptions = options;
+ } else {
+ this.serverOptions = DeepCopy(this.serverOptions, options);
+ }
+ }
+
+ setBrowserSyncConfig() {
+ this._attemptedBrowserSyncUse = true;
+ debug(
+ "The `setBrowserSyncConfig` method was removed in Eleventy 2.0.0. Use `setServerOptions` with the new Eleventy development server or the `@11ty/eleventy-browser-sync` plugin moving forward.",
+ );
+ }
+
+ setChokidarConfig(options = {}) {
+ this.chokidarConfig = options;
+ }
+
+ setWatchThrottleWaitTime(time = 0) {
+ this.watchThrottleWaitTime = time;
+ }
+
+ // 3.0 change: this does a top level merge instead of reset.
+ setFrontMatterParsingOptions(options = {}) {
+ DeepCopy(this.frontMatterParsingOptions, options);
+ }
+
+ /* Internal method for CLI --quiet */
+ _setQuietModeOverride(quietMode) {
+ this.setQuietMode(quietMode);
+ this.#quietModeLocked = true;
+ }
+
+ setQuietMode(quietMode) {
+ if (this.#quietModeLocked) {
+ debug(
+ "Attempt to `setQuietMode(%o)` ignored, --quiet command line argument override in place.",
+ !!quietMode,
+ );
+ // override via CLI takes precedence
+ return;
+ }
+
+ this.quietMode = !!quietMode;
+ }
+
+ addExtension(fileExtension, options = {}) {
+ let extensions;
+
+ // Array support added in 2.0.0-canary.19
+ if (Array.isArray(fileExtension)) {
+ extensions = fileExtension;
+ } else {
+ // single string
+ extensions = [fileExtension];
+ }
+
+ for (let extension of extensions) {
+ if (this.extensionConflictMap[extension]) {
+ throw new Error(
+ `An attempt was made to override the "${extension}" template syntax twice (via the \`addExtension\` configuration API). A maximum of one override is currently supported.`,
+ );
+ }
+ this.extensionConflictMap[extension] = true;
+
+ /** @type {object} */
+ let extensionOptions = Object.assign(
+ {
+ // Might be overridden for aliasing in options.key
+ key: extension,
+ extension: extension,
+ },
+ options,
+ );
+
+ if (extensionOptions.key !== extensionOptions.extension) {
+ extensionOptions.aliasKey = extensionOptions.extension;
+ }
+
+ this.extensionMap.add(extensionOptions);
+ }
+ }
+
+ addDataExtension(extensionList, parser) {
+ let options = {};
+ // second argument is an object with a `parser` callback
+ if (typeof parser !== "function") {
+ if (!("parser" in parser)) {
+ throw new Error(
+ "Expected `parser` property in second argument object to `eleventyConfig.addDataExtension`",
+ );
+ }
+
+ options = parser;
+ parser = options.parser;
+ }
+
+ let extensions = extensionList.split(",").map((s) => s.trim());
+ for (let extension of extensions) {
+ this.dataExtensions.set(extension, {
+ extension,
+ parser,
+ options,
+ });
+ }
+ }
+
+ setUseTemplateCache(bypass) {
+ this.useTemplateCache = !!bypass;
+ }
+
+ setPrecompiledCollections(collections) {
+ this.precompiledCollections = collections;
+ }
+
+ // "passthrough" is the default, no other value is explicitly required in code
+ // but opt-out via "copy" is suggested
+ setServerPassthroughCopyBehavior(behavior) {
+ this.serverPassthroughCopyBehavior = behavior;
+ }
+
+ // Url transforms change page.url and work good with server side content-negotiation (e.g. i18n plugin)
+ addUrlTransform(callback) {
+ this.urlTransforms.push(callback);
+ }
+
+ setDataFileSuffixes(suffixArray) {
+ this.dataFileSuffixesOverride = suffixArray;
+ }
+
+ setDataFileBaseName(baseName) {
+ this.dataFileDirBaseNameOverride = baseName;
+ }
+
+ addTemplate(virtualInputPath, content, data) {
+ // Lookups keys must be normalized
+ virtualInputPath = TemplatePath.stripLeadingDotSlash(
+ TemplatePath.standardizeFilePath(virtualInputPath),
+ );
+ if (this.virtualTemplates[virtualInputPath]) {
+ throw new Error(
+ "Virtual template conflict: you can’t add multiple virtual templates that have the same inputPath: " +
+ virtualInputPath,
+ );
+ }
+
+ this.virtualTemplates[virtualInputPath] = {
+ inputPath: virtualInputPath,
+ data,
+ content,
+ };
+ }
+
+ isVirtualTemplate(virtualInputPath) {
+ return Boolean(this.virtualTemplates[virtualInputPath]);
+ }
+
+ #setDirectory(key, dir) {
+ if (this.isPluginExecution()) {
+ throw new Error(
+ "The `set*Directory` configuration API methods are not yet allowed in plugins.",
+ );
+ }
+ this.directoryAssignments[key] = dir;
+ }
+
+ setInputDirectory(dir) {
+ this.#setDirectory("input", dir);
+ }
+
+ setOutputDirectory(dir) {
+ this.#setDirectory("output", dir);
+ }
+
+ setDataDirectory(dir) {
+ this.#setDirectory("data", dir);
+ }
+
+ setIncludesDirectory(dir) {
+ this.#setDirectory("includes", dir);
+ }
+
+ setLayoutsDirectory(dir) {
+ this.#setDirectory("layouts", dir);
+ }
+
+ // Some data keywords in Eleventy are reserved, throw an error if an application tries to set these.
+ setFreezeReservedData(bool) {
+ this.freezeReservedData = !!bool;
+ }
+
+ addDateParsing(callback) {
+ if (typeof callback === "function") {
+ this.customDateParsingCallbacks.add(callback);
+ } else {
+ throw new Error("addDateParsing expects a function argument.");
+ }
+ }
+
+ // 3.0.0-alpha.18 started merging conflicts here (when possible), issue #3389
+ addGlobalData(name, data) {
+ name = this.getNamespacedName(name);
+ if (this.globalData[name]) {
+ if (isPlainObject(this.globalData[name]) && isPlainObject(data)) {
+ DeepCopy(this.globalData[name], data);
+ } else {
+ debug("Warning: overwriting a previous value set with addGlobalData(%o)", name);
+ this.globalData[name] = data;
+ }
+ } else {
+ this.globalData[name] = data;
+ }
+ return this;
+ }
+
+ addNunjucksGlobal(name, globalType) {
+ name = this.getNamespacedName(name);
+
+ if (this.nunjucks.globals[name]) {
+ debug(
+ chalk.yellow("Warning, overwriting a Nunjucks global with `addNunjucksGlobal(%o)`"),
+ name,
+ );
+ }
+
+ if (typeof globalType === "function") {
+ this.nunjucks.globals[name] = this.#decorateCallback(`"${name}" Nunjucks Global`, globalType);
+ } else {
+ this.nunjucks.globals[name] = globalType;
+ }
+ }
+
+ addTransform(name, callback) {
+ name = this.getNamespacedName(name);
+
+ this.transforms[name] = this.#decorateCallback(`"${name}" Transform`, callback);
+ }
+
+ addPreprocessor(name, fileExtensions, callback) {
+ name = this.getNamespacedName(name);
+
+ this.preprocessors[name] = {
+ filter: fileExtensions,
+ callback: this.#decorateCallback(`"${name}" Preprocessor`, callback),
+ };
+ }
+
+ addLinter(name, callback) {
+ name = this.getNamespacedName(name);
+
+ this.linters[name] = this.#decorateCallback(`"${name}" Linter`, callback);
+ }
+
+ addLayoutAlias(from, to) {
+ this.layoutAliases[from] = to;
+ }
+
+ setLayoutResolution(resolution) {
+ this.layoutResolution = !!resolution;
+ }
+
+ // compat
+ enableLayoutResolution() {
+ this.layoutResolution = true;
+ }
+
+ configureErrorReporting(options = {}) {
+ // allowMissingExtensions: true
+ Object.assign(this.errorReporting, options);
+ }
+
+ configureTemplateHandling(options = {}) {
+ // writeMode: "sync" // "async"
+ Object.assign(this.templateHandling, options);
+ }
+
+ /*
+ * Collections
+ */
+
+ // get config defined collections
+ getCollections() {
+ return this.collections;
+ }
+
+ addCollection(name, callback) {
+ name = this.getNamespacedName(name);
+
+ if (this.collections[name]) {
+ throw new UserConfigError(
+ `config.addCollection(${name}) already exists. Try a different name for your collection.`,
+ );
+ }
+
+ this.collections[name] = callback;
+ }
+
+ augmentFunctionContext(fn, options) {
+ let t = typeof fn;
+ if (t !== "function") {
+ throw new UserConfigError(
+ "Invalid type passed to `augmentFunctionContext`—function was expected and received: " + t,
+ );
+ }
+
+ return augmentFunction(fn, options);
+ }
+
+ setConcurrency(number) {
+ if (typeof number !== "number") {
+ throw new UserConfigError("Argument passed to `setConcurrency` must be a number.");
+ }
+
+ this.#concurrency = number;
+ }
+
+ getConcurrency() {
+ return this.#concurrency;
+ }
+
+ getMergingConfigObject() {
+ let obj = {
+ // filters removed in 1.0 (use addTransform instead)
+ transforms: this.transforms,
+ linters: this.linters,
+ preprocessors: this.preprocessors,
+ globalData: this.globalData,
+ layoutAliases: this.layoutAliases,
+ layoutResolution: this.layoutResolution,
+ passthroughCopiesHtmlRelative: this.passthroughCopiesHtmlRelative,
+ passthroughCopies: this.passthroughCopies,
+
+ // Liquid
+ liquidOptions: this.liquid.options,
+ liquidTags: this.liquid.tags,
+ liquidFilters: this.liquid.filters,
+ liquidShortcodes: this.liquid.shortcodes,
+ liquidPairedShortcodes: this.liquid.pairedShortcodes,
+ liquidParameterParsing: this.liquid.parameterParsing,
+
+ // Nunjucks
+ nunjucksEnvironmentOptions: this.nunjucks.environmentOptions,
+ nunjucksPrecompiledTemplates: this.nunjucks.precompiledTemplates,
+ nunjucksFilters: this.nunjucks.filters,
+ nunjucksAsyncFilters: this.nunjucks.asyncFilters,
+ nunjucksTags: this.nunjucks.tags,
+ nunjucksGlobals: this.nunjucks.globals,
+ nunjucksAsyncShortcodes: this.nunjucks.asyncShortcodes,
+ nunjucksShortcodes: this.nunjucks.shortcodes,
+ nunjucksAsyncPairedShortcodes: this.nunjucks.asyncPairedShortcodes,
+ nunjucksPairedShortcodes: this.nunjucks.pairedShortcodes,
+
+ // 11ty.js
+ javascriptFunctions: this.javascript.functions, // filters and shortcodes, combined
+ javascriptShortcodes: this.javascript.shortcodes,
+ javascriptPairedShortcodes: this.javascript.pairedShortcodes,
+ javascriptFilters: this.javascript.filters,
+
+ // Markdown
+ markdownHighlighter: this.markdownHighlighter,
+
+ libraryOverrides: this.libraryOverrides,
+ dynamicPermalinks: this.dynamicPermalinks,
+ useGitIgnore: this.useGitIgnore,
+ ignores: this.ignores,
+ watchIgnores: this.watchIgnores,
+ dataDeepMerge: this.dataDeepMerge,
+ watchJavaScriptDependencies: this.watchJavaScriptDependencies,
+ additionalWatchTargets: this.additionalWatchTargets,
+ watchTargetsConfigReset: this.watchTargetsConfigReset,
+ serverOptions: this.serverOptions,
+ chokidarConfig: this.chokidarConfig,
+ watchThrottleWaitTime: this.watchThrottleWaitTime,
+ frontMatterParsingOptions: this.frontMatterParsingOptions,
+ dataExtensions: this.dataExtensions,
+ extensionMap: this.extensionMap,
+ quietMode: this.quietMode,
+ events: this.events,
+ benchmarkManager: this.benchmarkManager,
+ plugins: this.plugins,
+ useTemplateCache: this.useTemplateCache,
+ precompiledCollections: this.precompiledCollections,
+ dataFilterSelectors: this.dataFilterSelectors,
+ libraryAmendments: this.libraryAmendments,
+ serverPassthroughCopyBehavior: this.serverPassthroughCopyBehavior,
+ urlTransforms: this.urlTransforms,
+ virtualTemplates: this.virtualTemplates,
+ // `directories` and `directoryAssignments` are merged manually prior to plugin processing
+ freezeReservedData: this.freezeReservedData,
+ customDateParsing: this.customDateParsingCallbacks,
+ errorReporting: this.errorReporting,
+ templateHandling: this.templateHandling,
+ };
+
+ if (Array.isArray(this.dataFileSuffixesOverride)) {
+ // no upstream merging of this array, so we add the override: prefix
+ obj["override:dataFileSuffixes"] = this.dataFileSuffixesOverride;
+ }
+
+ if (this.dataFileDirBaseNameOverride) {
+ obj.dataFileDirBaseNameOverride = this.dataFileDirBaseNameOverride;
+ }
+
+ return obj;
+ }
+
+ // No-op functions for backwards compat
+ addHandlebarsHelper() {}
+ setPugOptions() {}
+ setEjsOptions() {}
+ addHandlebarsShortcode() {}
+ addPairedHandlebarsShortcode() {}
+}
+
+export default UserConfig;