Repository: 11ty/eleventy
Pull request #2867 (2023-03-17 13:37:43)

Saving/resetting config file should apply new config values everywhere (including layouts)
Config instances were being cached in Eleventy Layout instances.

https://github.com/11ty/eleventy-plugin-bundle/issues/10


Patch:
diff --git a/src/Eleventy.js b/src/Eleventy.js
index 203bc5b9..970f4fdd 100644
--- a/src/Eleventy.js
+++ b/src/Eleventy.js
@@ -15,7 +15,7 @@ const ConsoleLogger = require("./Util/ConsoleLogger");
 const PathPrefixer = require("./Util/PathPrefixer");
 const TemplateConfig = require("./TemplateConfig");
 const FileSystemSearch = require("./FileSystemSearch");
-
+const PathNormalizer = require("./Util/PathNormalizer.js");
 const simplePlural = require("./Util/Pluralize");
 const checkPassthroughCopyBehavior = require("./Util/PassthroughCopyBehaviorCheck");
 const debug = require("debug")("Eleventy");
@@ -55,6 +55,12 @@ class Eleventy {
       this.eleventyConfig.setPathPrefix(this.pathPrefix);
     }
 
+    /**
+     * @member {Object} - Options object passed to the Eleventy constructor
+     * @default {}
+     */
+    this.options = options;
+
     /* Programmatic API config */
     if (options.config && typeof options.config === "function") {
       // TODO use return object here?
@@ -728,7 +734,7 @@ Arguments:
    * @method
    * @param {String} changedFilePath - File that triggered a re-run (added or modified)
    */
-  async _addFileToWatchQueue(changedFilePath) {
+  async _addFileToWatchQueue(changedFilePath, isResetConfig) {
     // Currently this is only for 11ty.js deps but should be extended with usesGraph
     let usedByDependants = [];
     if (this.watchTargets) {
@@ -738,24 +744,27 @@ Arguments:
     }
 
     // Note: this is a sync event!
-    eventBus.emit("eleventy.resourceModified", changedFilePath, usedByDependants);
+    eventBus.emit("eleventy.resourceModified", changedFilePath, usedByDependants, {
+      viaConfigReset: isResetConfig,
+    });
+
     this.watchManager.addToPendingQueue(changedFilePath);
   }
 
-  _shouldResetConfig() {
-    let configFilePaths = this.eleventyConfig.getLocalProjectConfigFiles();
-    let configFilesChanged = this.watchManager.hasQueuedFiles(configFilePaths);
-
-    if (configFilesChanged) {
-      return true;
+  shouldTriggerConfigReset(changedFiles) {
+    let configFilePaths = new Set(this.eleventyConfig.getLocalProjectConfigFiles());
+    for (let filePath of changedFiles) {
+      if (configFilePaths.has(filePath)) {
+        return true;
+      }
     }
 
     for (const configFilePath of configFilePaths) {
       // Any dependencies of the config file changed
-      let configFileDependencies = this.watchTargets.getDependenciesOf(configFilePath);
+      let configFileDependencies = new Set(this.watchTargets.getDependenciesOf(configFilePath));
 
-      for (let dep of configFileDependencies) {
-        if (this.watchManager.hasQueuedFile(dep)) {
+      for (let filePath of changedFiles) {
+        if (configFileDependencies.has(filePath)) {
           return true;
         }
       }
@@ -764,13 +773,26 @@ Arguments:
     return false;
   }
 
+  // Checks the build queue to see if any configuration related files have changed
+  _shouldResetConfig(activeQueue = []) {
+    if (!activeQueue.length) {
+      return false;
+    }
+
+    return this.shouldTriggerConfigReset(
+      activeQueue.map((path) => {
+        return PathNormalizer.normalizeSeperator(TemplatePath.addLeadingDotSlash(path));
+      })
+    );
+  }
+
   /**
    * tbd.
    *
    * @private
    * @method
    */
-  async _watch() {
+  async _watch(isResetConfig = false) {
     if (this.watchManager.isBuildRunning()) {
       return;
     }
@@ -785,7 +807,6 @@ Arguments:
     this.watchTargets.clearRequireCacheFor(queue);
 
     // reset and reload global configuration
-    let isResetConfig = this._shouldResetConfig();
     if (isResetConfig) {
       this.resetConfig();
     }
@@ -1019,12 +1040,13 @@ Arguments:
     let watchRun = async (path) => {
       path = TemplatePath.normalize(path);
       try {
-        this._addFileToWatchQueue(path);
+        let isResetConfig = this._shouldResetConfig([path]);
+        this._addFileToWatchQueue(path, isResetConfig);
         clearTimeout(watchDelay);
 
         await new Promise((resolve, reject) => {
           watchDelay = setTimeout(async () => {
-            this._watch().then(resolve, reject);
+            this._watch(isResetConfig).then(resolve, reject);
           }, this.config.watchThrottleWaitTime);
         });
       } catch (e) {
diff --git a/src/EleventyExtensionMap.js b/src/EleventyExtensionMap.js
index 854f817c..fbc72f67 100644
--- a/src/EleventyExtensionMap.js
+++ b/src/EleventyExtensionMap.js
@@ -48,7 +48,7 @@ class EleventyExtensionMap {
 
   get engineManager() {
     if (!this._engineManager) {
-      this._engineManager = new TemplateEngineManager(this.config);
+      this._engineManager = new TemplateEngineManager(this.eleventyConfig);
     }
 
     return this._engineManager;
diff --git a/src/Engines/TemplateEngine.js b/src/Engines/TemplateEngine.js
index f5efef9e..d2af1fc3 100644
--- a/src/Engines/TemplateEngine.js
+++ b/src/Engines/TemplateEngine.js
@@ -9,7 +9,7 @@ const debug = require("debug")("Eleventy:TemplateEngine");
 class TemplateEngineConfigError extends EleventyBaseError {}
 
 class TemplateEngine {
-  constructor(name, dirs, config) {
+  constructor(name, dirs, eleventyConfig) {
     this.name = name;
 
     if (!dirs) {
@@ -25,21 +25,17 @@ class TemplateEngine {
     this.engineLib = null;
     this.cacheable = false;
 
-    if (!config) {
-      throw new TemplateEngineConfigError("Missing `config` argument.");
+    if (!eleventyConfig) {
+      throw new TemplateEngineConfigError("Missing `eleventyConfig` argument.");
     }
-    this._config = config;
-  }
-
-  set config(cfg) {
-    this._config = cfg;
+    this.eleventyConfig = eleventyConfig;
   }
 
   get config() {
-    if (this._config instanceof TemplateConfig) {
-      return this._config.getConfig();
+    if (this.eleventyConfig instanceof TemplateConfig) {
+      return this.eleventyConfig.getConfig();
     }
-    return this._config;
+    throw new Error("Expecting a TemplateConfig instance.");
   }
 
   get benchmarks() {
@@ -61,7 +57,7 @@ class TemplateEngine {
 
   get extensionMap() {
     if (!this._extensionMap) {
-      this._extensionMap = new EleventyExtensionMap([], this.config);
+      this._extensionMap = new EleventyExtensionMap([], this.eleventyConfig);
     }
     return this._extensionMap;
   }
diff --git a/src/Template.js b/src/Template.js
index a9ce35bb..bc96367d 100755
--- a/src/Template.js
+++ b/src/Template.js
@@ -147,7 +147,7 @@ class Template extends TemplateContent {
     return TemplateLayout.getTemplate(
       layoutKey,
       this.getInputDir(),
-      this.config,
+      this.eleventyConfig,
       this.extensionMap
     );
   }
diff --git a/src/TemplateCache.js b/src/TemplateCache.js
index 8cc404c3..e111c206 100644
--- a/src/TemplateCache.js
+++ b/src/TemplateCache.js
@@ -16,6 +16,11 @@ class TemplateCache {
     this.cacheByInputPath = {};
   }
 
+  // alias
+  removeAll() {
+    this.clear();
+  }
+
   size() {
     return Object.keys(this.cacheByInputPath).length;
   }
@@ -60,14 +65,14 @@ class TemplateCache {
     return this.cache[key];
   }
 
-  remove(filePath) {
-    filePath = TemplatePath.stripLeadingDotSlash(filePath);
-
-    if (!this.cacheByInputPath[filePath]) {
+  remove(layoutFilePath) {
+    layoutFilePath = TemplatePath.stripLeadingDotSlash(layoutFilePath);
+    if (!this.cacheByInputPath[layoutFilePath]) {
+      // not a layout file
       return;
     }
 
-    let layoutTemplate = this.cacheByInputPath[filePath];
+    let layoutTemplate = this.cacheByInputPath[layoutFilePath];
     layoutTemplate.resetCaches();
 
     let keys = layoutTemplate.getCacheKeys();
@@ -75,14 +80,19 @@ class TemplateCache {
       delete this.cache[key];
     }
 
-    delete this.cacheByInputPath[filePath];
+    delete this.cacheByInputPath[layoutFilePath];
   }
 }
 
 let layoutCache = new TemplateCache();
 
-eventBus.on("eleventy.resourceModified", (path) => {
-  layoutCache.remove(path);
+eventBus.on("eleventy.resourceModified", (path, usedBy, metadata = {}) => {
+  // https://github.com/11ty/eleventy-plugin-bundle/issues/10
+  if (metadata.viaConfigReset) {
+    layoutCache.removeAll();
+  } else {
+    layoutCache.remove(path);
+  }
 });
 
 // singleton
diff --git a/src/TemplateContent.js b/src/TemplateContent.js
index 541723f6..14b55f79 100644
--- a/src/TemplateContent.js
+++ b/src/TemplateContent.js
@@ -27,7 +27,7 @@ class TemplateContent {
     if (!config) {
       throw new TemplateContentConfigError("Missing `config` argument to TemplateContent");
     }
-    this.config = config;
+    this.eleventyConfig = config;
 
     this.inputPath = inputPath;
 
@@ -72,7 +72,7 @@ class TemplateContent {
   /* Used by tests */
   get extensionMap() {
     if (!this._extensionMap) {
-      this._extensionMap = new EleventyExtensionMap([], this.config);
+      this._extensionMap = new EleventyExtensionMap([], this.eleventyConfig);
     }
     return this._extensionMap;
   }
@@ -81,26 +81,33 @@ class TemplateContent {
     this._extensionMap = map;
   }
 
-  set config(config) {
+  set eleventyConfig(config) {
     this._config = config;
-  }
 
-  get config() {
     if (this._config instanceof TemplateConfig) {
-      return this._config.getConfig();
+      this._configOptions = this._config.getConfig();
+    } else {
+      throw new TemplateContentConfigError("Tried to get an TemplateConfig but none was found.");
     }
-    return this._config;
-  }
-
-  get bench() {
-    return this.config.benchmarkManager.get("Aggregate");
   }
 
   get eleventyConfig() {
     if (this._config instanceof TemplateConfig) {
       return this._config;
     }
-    throw new TemplateContentConfigError("Tried to get an eleventyConfig but none was found.");
+    throw new TemplateContentConfigError("Tried to get an TemplateConfig but none was found.");
+  }
+
+  get config() {
+    if (this._config instanceof TemplateConfig && !this._configOptions) {
+      this._configOptions = this._config.getConfig();
+    }
+
+    return this._configOptions;
+  }
+
+  get bench() {
+    return this.config.benchmarkManager.get("Aggregate");
   }
 
   get engine() {
@@ -109,7 +116,7 @@ class TemplateContent {
 
   get templateRender() {
     if (!this._templateRender) {
-      this._templateRender = new TemplateRender(this.inputPath, this.inputDir, this.config);
+      this._templateRender = new TemplateRender(this.inputPath, this.inputDir, this.eleventyConfig);
       this._templateRender.extensionMap = this.extensionMap;
     }
 
diff --git a/src/TemplateData.js b/src/TemplateData.js
index e09258ee..c850bf1a 100644
--- a/src/TemplateData.js
+++ b/src/TemplateData.js
@@ -71,7 +71,7 @@ class TemplateData {
 
   get extensionMap() {
     if (!this._extensionMap) {
-      this._extensionMap = new EleventyExtensionMap([], this.config);
+      this._extensionMap = new EleventyExtensionMap([], this.eleventyConfig);
     }
     return this._extensionMap;
   }
diff --git a/src/TemplateEngineManager.js b/src/TemplateEngineManager.js
index bb01e8ba..c44ffa35 100644
--- a/src/TemplateEngineManager.js
+++ b/src/TemplateEngineManager.js
@@ -2,15 +2,19 @@ const EleventyBaseError = require("./EleventyBaseError");
 class TemplateEngineManagerConfigError extends EleventyBaseError {}
 
 class TemplateEngineManager {
-  constructor(config) {
-    if (!config) {
+  constructor(eleventyConfig) {
+    if (!eleventyConfig) {
       throw new TemplateEngineManagerConfigError("Missing `config` argument.");
     }
-    this.config = config;
+    this.eleventyConfig = eleventyConfig;
 
     this.engineCache = {};
   }
 
+  get config() {
+    return this.eleventyConfig.getConfig();
+  }
+
   static isCustomEngineSimpleAlias(entry) {
     let keys = Object.keys(entry);
     if (keys.length > 2) {
@@ -74,7 +78,7 @@ class TemplateEngineManager {
   }
 
   getEngineClassByExtension(extension) {
-    // We include these as raw strings (and not more readable variables) so they’re parsed by the bundler.
+    // We include these as raw strings (and not more readable variables) so they’re parsed by the serverless bundler.
     if (extension === "ejs") {
       return require("./Engines/Ejs");
     } else if (extension === "md") {
@@ -102,9 +106,7 @@ class TemplateEngineManager {
 
   getEngine(name, dirs, extensionMap) {
     if (!this.hasEngine(name)) {
-      throw new Error(
-        `Template Engine ${name} does not exist in getEngine (dirs: ${dirs})`
-      );
+      throw new Error(`Template Engine ${name} does not exist in getEngine (dirs: ${dirs})`);
     }
 
     // TODO these cached engines should be based on extensions not name, then we can remove the error in
@@ -115,7 +117,7 @@ class TemplateEngineManager {
 
     let cls = this.getEngineClassByExtension(name);
 
-    let instance = new cls(name, dirs, this.config);
+    let instance = new cls(name, dirs, this.eleventyConfig);
     instance.extensionMap = extensionMap;
     instance.engineManager = this;
 
@@ -128,7 +130,7 @@ class TemplateEngineManager {
       instance.constructor.name !== "CustomEngine"
     ) {
       const CustomEngine = this.getEngineClassByExtension();
-      const overrideCustomEngine = new CustomEngine(name, dirs, this.config);
+      const overrideCustomEngine = new CustomEngine(name, dirs, 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);
diff --git a/src/TemplateLayout.js b/src/TemplateLayout.js
index 09532238..54c7bc66 100644
--- a/src/TemplateLayout.js
+++ b/src/TemplateLayout.js
@@ -9,15 +9,15 @@ const templateCache = require("./TemplateCache");
 const debugDev = require("debug")("Dev:Eleventy:TemplateLayout");
 
 class TemplateLayout extends TemplateContent {
-  constructor(key, inputDir, extensionMap, config) {
-    if (!config) {
-      throw new Error("Expected `config` in TemplateLayout constructor.");
+  constructor(key, inputDir, extensionMap, eleventyConfig) {
+    if (!eleventyConfig) {
+      throw new Error("Expected `eleventyConfig` in TemplateLayout constructor.");
     }
 
-    let resolver = new TemplateLayoutPathResolver(key, inputDir, extensionMap, config);
+    let resolver = new TemplateLayoutPathResolver(key, inputDir, extensionMap, eleventyConfig);
     let resolvedPath = resolver.getFullPath();
 
-    super(resolvedPath, inputDir, config);
+    super(resolvedPath, inputDir, eleventyConfig);
 
     if (!extensionMap) {
       throw new Error("Expected `extensionMap` in TemplateLayout constructor.");
@@ -46,14 +46,15 @@ class TemplateLayout extends TemplateContent {
     return TemplatePath.join(inputDir, key);
   }
 
-  static getTemplate(key, inputDir, config, extensionMap) {
+  static getTemplate(key, inputDir, eleventyConfig, extensionMap) {
+    let config = eleventyConfig.getConfig();
     if (!config.useTemplateCache) {
-      return new TemplateLayout(key, inputDir, extensionMap, config);
+      return new TemplateLayout(key, inputDir, extensionMap, eleventyConfig);
     }
 
     let fullKey = TemplateLayout.resolveFullKey(key, inputDir);
     if (!templateCache.has(fullKey)) {
-      let layout = new TemplateLayout(key, inputDir, extensionMap, config);
+      let layout = new TemplateLayout(key, inputDir, extensionMap, eleventyConfig);
 
       templateCache.add(layout);
       debugDev("Added %o to TemplateCache", key);
@@ -96,7 +97,7 @@ class TemplateLayout extends TemplateContent {
             let layout = TemplateLayout.getTemplate(
               parentLayoutKey,
               mapEntry.inputDir,
-              this.config,
+              this.eleventyConfig,
               this.extensionMap
             );
 
@@ -193,7 +194,7 @@ class TemplateLayout extends TemplateContent {
         let layoutTemplate = TemplateLayout.getTemplate(
           key,
           inputDir,
-          this.config,
+          this.eleventyConfig,
           this.extensionMap
         );
 
diff --git a/src/TemplateLayoutPathResolver.js b/src/TemplateLayoutPathResolver.js
index 07957cf3..ef039917 100644
--- a/src/TemplateLayoutPathResolver.js
+++ b/src/TemplateLayoutPathResolver.js
@@ -4,11 +4,11 @@ const { TemplatePath } = require("@11ty/eleventy-utils");
 // const debug = require("debug")("Eleventy:TemplateLayoutPathResolver");
 
 class TemplateLayoutPathResolver {
-  constructor(path, inputDir, extensionMap, config) {
-    if (!config) {
-      throw new Error("Expected `config` in TemplateLayoutPathResolver constructor");
+  constructor(path, inputDir, extensionMap, eleventyConfig) {
+    if (!eleventyConfig) {
+      throw new Error("Expected `eleventyConfig` in TemplateLayoutPathResolver constructor");
     }
-    this._config = config;
+    this.eleventyConfig = eleventyConfig;
     this.inputDir = inputDir;
     this.originalPath = path;
     this.path = path;
@@ -42,7 +42,11 @@ class TemplateLayoutPathResolver {
   }
 
   get config() {
-    return this._config;
+    if (this.eleventyConfig) {
+      return this.eleventyConfig.getConfig();
+    } else {
+      throw new Error("Missing this.eleventyConfig");
+    }
   }
 
   init() {
diff --git a/src/TemplatePassthroughManager.js b/src/TemplatePassthroughManager.js
index 00231522..08cc3d8b 100644
--- a/src/TemplatePassthroughManager.js
+++ b/src/TemplatePassthroughManager.js
@@ -36,7 +36,7 @@ class TemplatePassthroughManager {
 
   get extensionMap() {
     if (!this._extensionMap) {
-      this._extensionMap = new EleventyExtensionMap([], this.config);
+      this._extensionMap = new EleventyExtensionMap([], this.eleventyConfig);
     }
     return this._extensionMap;
   }
diff --git a/src/TemplateRender.js b/src/TemplateRender.js
index 0af6ad0d..9d5c7df0 100644
--- a/src/TemplateRender.js
+++ b/src/TemplateRender.js
@@ -49,14 +49,14 @@ class TemplateRender {
 
   get extensionMap() {
     if (!this._extensionMap) {
-      this._extensionMap = new EleventyExtensionMap([], this.config);
+      this._extensionMap = new EleventyExtensionMap([], this.eleventyConfig);
     }
     return this._extensionMap;
   }
 
   getEngineByName(name) {
     let engine = this.extensionMap.engineManager.getEngine(name, this.getDirs(), this.extensionMap);
-    engine.config = this.config;
+    engine.eleventyConfig = this.eleventyConfig;
 
     return engine;
   }


Test patch:
diff --git a/test/TemplateEngineManagerTest.js b/test/TemplateEngineManagerTest.js
index ab359221..19d9448d 100644
--- a/test/TemplateEngineManagerTest.js
+++ b/test/TemplateEngineManagerTest.js
@@ -4,23 +4,21 @@ const TemplateConfig = require("../src/TemplateConfig");
 
 test("Unsupported engine", async (t) => {
   t.throws(() => {
-    let config = new TemplateConfig().getConfig();
-    let tem = new TemplateEngineManager(config);
+    let eleventyConfig = new TemplateConfig();
+    let tem = new TemplateEngineManager(eleventyConfig);
     tem.getEngine("doesnotexist");
   });
 });
 
 test("Supported engine", async (t) => {
-  let config = new TemplateConfig().getConfig();
-  let tem = new TemplateEngineManager(config);
+  let eleventyConfig = new TemplateConfig();
+  let tem = new TemplateEngineManager(eleventyConfig);
   t.truthy(tem.hasEngine("ejs"));
 });
 
 test("Supported custom engine", async (t) => {
-  let config = new TemplateConfig().getConfig();
-  let tem = new TemplateEngineManager(config);
-  tem.config = Object.assign({}, config);
-  tem.config.extensionMap.add({
+  let eleventyConfig = new TemplateConfig();
+  eleventyConfig.userConfig.extensionMap.add({
     extension: "txt",
     key: "txt",
     compile: function (str, inputPath) {
@@ -30,6 +28,7 @@ test("Supported custom engine", async (t) => {
       };
     },
   });
+  let tem = new TemplateEngineManager(eleventyConfig);
 
   t.truthy(tem.hasEngine("txt"));
   let engine = tem.getEngine("txt");
@@ -55,7 +54,7 @@ test("Custom engine with custom init", async (t) => {
   });
 
   let config = eleventyConfig.getConfig();
-  let tem = new TemplateEngineManager(config);
+  let tem = new TemplateEngineManager(eleventyConfig);
 
   t.truthy(tem.hasEngine("custom1"));
   let engine = tem.getEngine("custom1");
@@ -73,8 +72,8 @@ test("Custom engine with custom init", async (t) => {
 });
 
 test("Handlebars Helpers", async (t) => {
-  let config = new TemplateConfig().getConfig();
-  let tem = new TemplateEngineManager(config);
+  let eleventyConfig = new TemplateConfig();
+  let tem = new TemplateEngineManager(eleventyConfig);
   let engine = tem.getEngine("hbs");
   engine.addHelpers({
     uppercase: function (name) {
@@ -87,7 +86,7 @@ test("Handlebars Helpers", async (t) => {
 });
 
 test("getEngineLib", async (t) => {
-  let config = new TemplateConfig().getConfig();
-  let tem = new TemplateEngineManager(config);
+  let eleventyConfig = new TemplateConfig();
+  let tem = new TemplateEngineManager(eleventyConfig);
   t.truthy(tem.getEngine("md").getEngineLib());
 });
diff --git a/test/TemplateLayoutPathResolverTest.js b/test/TemplateLayoutPathResolverTest.js
index 286a6ba6..cff7982c 100644
--- a/test/TemplateLayoutPathResolverTest.js
+++ b/test/TemplateLayoutPathResolverTest.js
@@ -15,7 +15,7 @@ function getResolverInstance(path, inputDir, { eleventyConfig, map } = {}) {
     );
   }
 
-  return new TemplateLayoutPathResolver(path, inputDir, map, eleventyConfig.getConfig());
+  return new TemplateLayoutPathResolver(path, inputDir, map, eleventyConfig);
 }
 
 test("Layout", (t) => {
diff --git a/test/TemplateLayoutTest.js b/test/TemplateLayoutTest.js
index 56fa0d0c..80d876c6 100644
--- a/test/TemplateLayoutTest.js
+++ b/test/TemplateLayoutTest.js
@@ -12,7 +12,7 @@ function getTemplateLayoutInstance(key, inputDir, map) {
       eleventyConfig
     );
   }
-  let layout = new TemplateLayout(key, inputDir, map, eleventyConfig.getConfig());
+  let layout = new TemplateLayout(key, inputDir, map, eleventyConfig);
   return layout;
 }
 
diff --git a/test/TemplateRenderJavaScriptTest.js b/test/TemplateRenderJavaScriptTest.js
index 95939886..8d14800b 100644
--- a/test/TemplateRenderJavaScriptTest.js
+++ b/test/TemplateRenderJavaScriptTest.js
@@ -3,8 +3,11 @@ const TemplateRender = require("../src/TemplateRender");
 const TemplateConfig = require("../src/TemplateConfig");
 const EleventyExtensionMap = require("../src/EleventyExtensionMap");
 
-function getNewTemplateRender(name, inputDir) {
+function getNewTemplateRender(name, inputDir, extendedConfig) {
   let eleventyConfig = new TemplateConfig();
+  if (extendedConfig) {
+    eleventyConfig.appendToRootConfig(extendedConfig);
+  }
   let tr = new TemplateRender(name, inputDir, eleventyConfig);
   tr.extensionMap = new EleventyExtensionMap([], eleventyConfig);
   return tr;
@@ -12,58 +15,40 @@ function getNewTemplateRender(name, inputDir) {
 
 test("JS", (t) => {
   t.is(getNewTemplateRender("11ty.js").getEngineName(), "11ty.js");
-  t.is(
-    getNewTemplateRender("./test/stubs/filename.11ty.js").getEngineName(),
-    "11ty.js"
-  );
+  t.is(getNewTemplateRender("./test/stubs/filename.11ty.js").getEngineName(), "11ty.js");
   t.is(getNewTemplateRender("11ty.cjs").getEngineName(), "11ty.js");
-  t.is(
-    getNewTemplateRender("./test/stubs/filename.11ty.cjs").getEngineName(),
-    "11ty.js"
-  );
+  t.is(getNewTemplateRender("./test/stubs/filename.11ty.cjs").getEngineName(), "11ty.js");
 });
 
 test("JS Render a string (no data)", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/string.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/string.11ty.js").getCompiledTemplate();
   t.is(await fn({ name: "Bill" }), "<p>Zach</p>");
 });
 
 test("JS Render a promise (no data)", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/promise.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/promise.11ty.js").getCompiledTemplate();
   t.is(await fn({ name: "Bill" }), "<p>Zach</p>");
 });
 
 test("JS Render a buffer (no data)", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/buffer.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/buffer.11ty.js").getCompiledTemplate();
   t.is(await fn({ name: "Bill" }), "<p>tést</p>");
 });
 
 test("JS Render a function", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/function.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/function.11ty.js").getCompiledTemplate();
   t.is(await fn({ name: "Zach" }), "<p>Zach</p>");
   t.is(await fn({ name: "Bill" }), "<p>Bill</p>");
 });
 
 test("JS Render a function (arrow syntax)", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/function-arrow.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/function-arrow.11ty.js").getCompiledTemplate();
   t.is(await fn({ name: "Zach" }), "<p>Zach</p>");
   t.is(await fn({ name: "Bill" }), "<p>Bill</p>");
 });
 
 test("JS Render a function, returns a Buffer", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/function-buffer.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/function-buffer.11ty.js").getCompiledTemplate();
   t.is(await fn({ name: "tést" }), "<p>tést</p>");
   t.is(await fn({ name: "Zach" }), "<p>Zach</p>");
   t.is(await fn({ name: "Bill" }), "<p>Bill</p>");
@@ -104,56 +89,38 @@ test("JS Render a function (Collections)", async (t) => {
 });
 
 test("JS Render an async function", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/function-async.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/function-async.11ty.js").getCompiledTemplate();
   t.is(await fn({ name: "Zach" }), "<p>Zach</p>");
   t.is(await fn({ name: "Bill" }), "<p>Bill</p>");
 });
 
 test("JS Render with a Class", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/class.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/class.11ty.js").getCompiledTemplate();
   t.is(await fn({ name: "Zach" }), "<p>ZachBillTed</p>");
   t.is(await fn({ name: "Bill" }), "<p>BillBillTed</p>");
 });
 
 test("JS Render with a Class, returns a buffer", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/class-buffer.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/class-buffer.11ty.js").getCompiledTemplate();
   t.is(await fn({ name: "Zách" }), "<p>ZáchBillTed</p>");
   t.is(await fn({ name: "Zach" }), "<p>ZachBillTed</p>");
   t.is(await fn({ name: "Bill" }), "<p>BillBillTed</p>");
 });
 
 test("JS Render with a Class, async render", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/class-async.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/class-async.11ty.js").getCompiledTemplate();
   t.is(await fn({ name: "Zach" }), "<p>Zach</p>");
   t.is(await fn({ name: "Bill" }), "<p>Bill</p>");
 });
 
 test("JS Render using Vue", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/vue.11ty.js"
-  ).getCompiledTemplate();
-  t.is(
-    await fn({ name: "Zach" }),
-    "<p>Hello Zach, this is a Vue template.</p>"
-  );
-  t.is(
-    await fn({ name: "Bill" }),
-    "<p>Hello Bill, this is a Vue template.</p>"
-  );
+  let fn = await getNewTemplateRender("./test/stubs/vue.11ty.js").getCompiledTemplate();
+  t.is(await fn({ name: "Zach" }), "<p>Hello Zach, this is a Vue template.</p>");
+  t.is(await fn({ name: "Bill" }), "<p>Hello Bill, this is a Vue template.</p>");
 });
 
 test("JS Render using Vue (with a layout)", async (t) => {
-  let fn = await getNewTemplateRender(
-    "./test/stubs/vue-layout.11ty.js"
-  ).getCompiledTemplate();
+  let fn = await getNewTemplateRender("./test/stubs/vue-layout.11ty.js").getCompiledTemplate();
   t.is(
     await fn({ name: "Zach" }),
     `<!doctype html>
@@ -165,8 +132,7 @@ test("JS Render using Vue (with a layout)", async (t) => {
 test("JS Render with a function", async (t) => {
   t.plan(8);
 
-  let tr = getNewTemplateRender("./test/stubs/function-filter.11ty.js");
-  tr.config = {
+  let tr = getNewTemplateRender("./test/stubs/function-filter.11ty.js", undefined, {
     javascriptFunctions: {
       upper: function (val) {
         t.is(this.page.url, "/hi/");
@@ -176,7 +142,7 @@ test("JS Render with a function", async (t) => {
         return new String(val).toUpperCase();
       },
     },
-  };
+  });
 
   let fn = await tr.getCompiledTemplate();
   t.is(await fn({ name: "Zach", page: { url: "/hi/" } }), "<p>ZACHT9000</p>");
@@ -186,8 +152,7 @@ test("JS Render with a function", async (t) => {
 test("JS Render with a function and async filter", async (t) => {
   t.plan(4);
 
-  let tr = getNewTemplateRender("./test/stubs/function-async-filter.11ty.js");
-  tr.config = {
+  let tr = getNewTemplateRender("./test/stubs/function-async-filter.11ty.js", undefined, {
     javascriptFunctions: {
       upper: function (val) {
         return new Promise((resolve) => {
@@ -196,7 +161,7 @@ test("JS Render with a function and async filter", async (t) => {
         });
       },
     },
-  };
+  });
 
   let fn = await tr.getCompiledTemplate();
   t.is(await fn({ name: "Zach", page: { url: "/hi/" } }), "<p>ZACH</p>");
@@ -205,39 +170,31 @@ test("JS Render with a function and async filter", async (t) => {
 
 test("JS Render with a function prototype", async (t) => {
   t.plan(4);
-  let tr = getNewTemplateRender("./test/stubs/function-prototype.11ty.js");
-  tr.config = {
+  let tr = getNewTemplateRender("./test/stubs/function-prototype.11ty.js", undefined, {
     javascriptFunctions: {
       upper: function (val) {
         t.is(this.page.url, "/hi/");
         return new String(val).toUpperCase();
       },
     },
-  };
+  });
 
   let fn = await tr.getCompiledTemplate();
-  t.is(
-    await fn({ name: "Zach", page: { url: "/hi/" } }),
-    "<p>ZACHBillT9001</p>"
-  );
-  t.is(
-    await fn({ name: "Bill", page: { url: "/hi/" } }),
-    "<p>BILLBillT9001</p>"
-  );
+  t.is(await fn({ name: "Zach", page: { url: "/hi/" } }), "<p>ZACHBillT9001</p>");
+  t.is(await fn({ name: "Bill", page: { url: "/hi/" } }), "<p>BILLBillT9001</p>");
 });
 
 test("JS Class Render with a function", async (t) => {
   t.plan(4);
 
-  let tr = getNewTemplateRender("./test/stubs/class-filter.11ty.js");
-  tr.config = {
+  let tr = getNewTemplateRender("./test/stubs/class-filter.11ty.js", undefined, {
     javascriptFunctions: {
       upper: function (val) {
         t.is(this.page.url, "/hi/");
         return new String(val).toUpperCase();
       },
     },
-  };
+  });
 
   let fn = await tr.getCompiledTemplate();
   t.is(await fn({ name: "Zach", page: { url: "/hi/" } }), "<p>ZACHBillTed</p>");
@@ -247,15 +204,14 @@ test("JS Class Render with a function", async (t) => {
 test("JS Class Async Render with a function", async (t) => {
   t.plan(4);
 
-  let tr = getNewTemplateRender("./test/stubs/class-async-filter.11ty.js");
-  tr.config = {
+  let tr = getNewTemplateRender("./test/stubs/class-async-filter.11ty.js", undefined, {
     javascriptFunctions: {
       upper: function (val) {
         t.is(this.page.url, "/hi/");
         return new String(val).toUpperCase();
       },
     },
-  };
+  });
 
   let fn = await tr.getCompiledTemplate();
   // Overrides all names to Ted
@@ -264,48 +220,40 @@ test("JS Class Async Render with a function", async (t) => {
 });
 
 test("JS Class Async Render with a function (sync function, throws error)", async (t) => {
-  let tr = getNewTemplateRender("./test/stubs/function-throws.11ty.js");
-  tr.config = {
+  let tr = getNewTemplateRender("./test/stubs/function-throws.11ty.js", undefined, {
     javascriptFunctions: {
       upper: function (val) {
-        throw new Error(
-          "JS Class Async Render with a function (sync function, throws error)"
-        );
+        throw new Error("JS Class Async Render with a function (sync function, throws error)");
       },
     },
-  };
+  });
 
   let error = await t.throwsAsync(async () => {
     let fn = await tr.getCompiledTemplate();
     await fn({ name: "Zach" });
   });
   t.true(
-    error.message.indexOf(
-      "JS Class Async Render with a function (sync function, throws error)"
-    ) > -1
+    error.message.indexOf("JS Class Async Render with a function (sync function, throws error)") >
+      -1
   );
 });
 
 test("JS Class Async Render with a function (async function, throws error)", async (t) => {
-  let tr = getNewTemplateRender("./test/stubs/function-throws-async.11ty.js");
-  tr.config = {
+  let tr = getNewTemplateRender("./test/stubs/function-throws-async.11ty.js", undefined, {
     javascriptFunctions: {
       upper: async function (val) {
-        throw new Error(
-          "JS Class Async Render with a function (async function, throws error)"
-        );
+        throw new Error("JS Class Async Render with a function (async function, throws error)");
       },
     },
-  };
+  });
 
   let error = await t.throwsAsync(async () => {
     let fn = await tr.getCompiledTemplate();
     await fn({ name: "Zach" });
   });
   t.true(
-    error.message.indexOf(
-      "JS Class Async Render with a function (async function, throws error)"
-    ) > -1
+    error.message.indexOf("JS Class Async Render with a function (async function, throws error)") >
+      -1
   );
 });
 
diff --git a/test/TemplateTest-JavaScript.js b/test/TemplateTest-JavaScript.js
index 9e25022d..212559d6 100644
--- a/test/TemplateTest-JavaScript.js
+++ b/test/TemplateTest-JavaScript.js
@@ -1,14 +1,11 @@
 const test = require("ava");
 const semver = require("semver");
+const TemplateConfig = require("../src/TemplateConfig");
 const getNewTemplate = require("./_getNewTemplateForTests");
 const getRenderedTmpls = require("./_getRenderedTemplates");
 
 test("JavaScript template type (function)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/function.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/function.11ty.js", "./test/stubs/", "./dist");
 
   let data = await tmpl.getData();
   t.is(await tmpl.getOutputPath(data), "./dist/function/index.html");
@@ -23,11 +20,7 @@ test("JavaScript template type (function)", async (t) => {
 });
 
 test("JavaScript template type (class with data getter)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/class-data.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/class-data.11ty.js", "./test/stubs/", "./dist");
 
   let data = await tmpl.getData();
   t.is(await tmpl.getOutputPath(data), "./dist/class-data/index.html");
@@ -38,11 +31,7 @@ test("JavaScript template type (class with data getter)", async (t) => {
 });
 
 test("JavaScript template type (class with data method)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/class-data-fn.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/class-data-fn.11ty.js", "./test/stubs/", "./dist");
 
   let data = await tmpl.getData();
   t.is(await tmpl.getOutputPath(data), "./dist/class-data-fn/index.html");
@@ -54,11 +43,7 @@ test("JavaScript template type (class with data method)", async (t) => {
 
 if (semver.gte(process.version, "12.4.0")) {
   test("JavaScript template type (class fields)", async (t) => {
-    let tmpl = getNewTemplate(
-      "./test/stubs/classfields-data.11ty.js",
-      "./test/stubs/",
-      "./dist"
-    );
+    let tmpl = getNewTemplate("./test/stubs/classfields-data.11ty.js", "./test/stubs/", "./dist");
 
     let data = await tmpl.getData();
     t.is(await tmpl.getOutputPath(data), "./dist/classfields-data/index.html");
@@ -77,10 +62,7 @@ test("JavaScript template type (class with shorthand data method)", async (t) =>
   );
 
   let data = await tmpl.getData();
-  t.is(
-    await tmpl.getOutputPath(data),
-    "./dist/class-data-fn-shorthand/index.html"
-  );
+  t.is(await tmpl.getOutputPath(data), "./dist/class-data-fn-shorthand/index.html");
 
   let pages = await getRenderedTmpls(tmpl, data);
   t.is(pages[0].templateContent.trim(), "<p>Ted</p>");
@@ -88,11 +70,7 @@ test("JavaScript template type (class with shorthand data method)", async (t) =>
 });
 
 test("JavaScript template type (class with async data method)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/class-async-data-fn.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/class-async-data-fn.11ty.js", "./test/stubs/", "./dist");
 
   let data = await tmpl.getData();
   t.is(await tmpl.getOutputPath(data), "./dist/class-async-data-fn/index.html");
@@ -103,18 +81,23 @@ test("JavaScript template type (class with async data method)", async (t) => {
 });
 
 test("JavaScript template type (class with data getter and a javascriptFunction)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/class-data-filter.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
-  tmpl.templateRender.config = {
+  let eleventyConfig = new TemplateConfig();
+  eleventyConfig.appendToRootConfig({
     javascriptFunctions: {
       upper: function (val) {
         return new String(val).toUpperCase();
       },
     },
-  };
+  });
+
+  let tmpl = getNewTemplate(
+    "./test/stubs/class-data-filter.11ty.js",
+    "./test/stubs/",
+    "./dist",
+    null,
+    null,
+    eleventyConfig
+  );
 
   let data = await tmpl.getData();
   t.is(await tmpl.getOutputPath(data), "./dist/class-data-filter/index.html");
@@ -124,35 +107,33 @@ test("JavaScript template type (class with data getter and a javascriptFunction)
 });
 
 test("JavaScript template type (class with data method and a javascriptFunction)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/class-data-fn-filter.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
-  tmpl.templateRender.config = {
+  let eleventyConfig = new TemplateConfig();
+  eleventyConfig.appendToRootConfig({
     javascriptFunctions: {
       upper: function (val) {
         return new String(val).toUpperCase();
       },
     },
-  };
+  });
 
-  let data = await tmpl.getData();
-  t.is(
-    await tmpl.getOutputPath(data),
-    "./dist/class-data-fn-filter/index.html"
+  let tmpl = getNewTemplate(
+    "./test/stubs/class-data-fn-filter.11ty.js",
+    "./test/stubs/",
+    "./dist",
+    null,
+    null,
+    eleventyConfig
   );
+
+  let data = await tmpl.getData();
+  t.is(await tmpl.getOutputPath(data), "./dist/class-data-fn-filter/index.html");
   let pages = await getRenderedTmpls(tmpl, data);
   t.is(pages[0].content.trim(), "<p>TED</p>");
   t.is(pages[0].templateContent.trim(), "<p>TED</p>");
 });
 
 test("JavaScript template type (class with data permalink)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/class-data-permalink.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/class-data-permalink.11ty.js", "./test/stubs/", "./dist");
   let data = await tmpl.getData();
   t.is(await tmpl.getOutputPath(data), "./dist/my-permalink/index.html");
 });
@@ -205,18 +186,11 @@ test("JavaScript template type (class with data permalink function using a filte
   );
 
   let data = await tmpl.getData();
-  t.is(
-    await tmpl.getOutputPath(data),
-    "./dist/my-permalink/my-super-cool-title/index.html"
-  );
+  t.is(await tmpl.getOutputPath(data), "./dist/my-permalink/my-super-cool-title/index.html");
 });
 
 test("JavaScript template type (should use the same class instance for data and render)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/oneinstance.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/oneinstance.11ty.js", "./test/stubs/", "./dist");
 
   let data = await tmpl.getData();
   let pages = await getRenderedTmpls(tmpl, data);
@@ -228,11 +202,7 @@ test("JavaScript template type (should use the same class instance for data and
 });
 
 test("JavaScript template type (multiple exports)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/multipleexports.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/multipleexports.11ty.js", "./test/stubs/", "./dist");
 
   let data = await tmpl.getData();
   let pages = await getRenderedTmpls(tmpl, data);
@@ -256,11 +226,7 @@ test("JavaScript template type (multiple exports, promises)", async (t) => {
 });
 
 test("JavaScript template type (object)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/object.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/object.11ty.js", "./test/stubs/", "./dist");
 
   let data = await tmpl.getData();
   t.is(data.name, "Ted");
@@ -271,11 +237,7 @@ test("JavaScript template type (object)", async (t) => {
 });
 
 test("JavaScript template type (object, no render method)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/object-norender.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/object-norender.11ty.js", "./test/stubs/", "./dist");
 
   let data = await tmpl.getData();
   t.is(data.name, "Ted");
@@ -286,11 +248,7 @@ test("JavaScript template type (object, no render method)", async (t) => {
 });
 
 test("JavaScript template type (class, no render method)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/class-norender.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/class-norender.11ty.js", "./test/stubs/", "./dist");
 
   let data = await tmpl.getData();
   t.is(data.name, "Ted");
@@ -300,11 +258,7 @@ test("JavaScript template type (class, no render method)", async (t) => {
   t.is(pages[0].content.trim(), "");
 });
 test("JavaScript template type (data returns a string)", async (t) => {
-  let tmpl = getNewTemplate(
-    "./test/stubs/exports-flatdata.11ty.js",
-    "./test/stubs/",
-    "./dist"
-  );
+  let tmpl = getNewTemplate("./test/stubs/exports-flatdata.11ty.js", "./test/stubs/", "./dist");
 
   await t.throwsAsync(async () => {
     await tmpl.getData();