diff --git a/src/Plugins/RenderPlugin.js b/src/Plugins/RenderPlugin.js index 78ef74516..7523b53f6 100644 --- a/src/Plugins/RenderPlugin.js +++ b/src/Plugins/RenderPlugin.js @@ -52,15 +52,6 @@ async function compileFile( ); } - if ( - !fs.existsSync(TemplatePath.normalizeOperatingSystemFilePath(inputPath)) - ) { - throw new Error( - "Could not find render plugin file for the `renderFile` shortcode, looking for: " + - inputPath - ); - } - if (!templateConfig) { templateConfig = new TemplateConfig(null, false); } @@ -69,6 +60,17 @@ async function compileFile( } let cfg = templateConfig.getConfig(); + + let processedInputPath = cfg.path(inputPath); + if (!processedInputPath) { + throw new Error( + "Could not find render plugin file for the `renderFile` shortcode, looking for: " + + inputPath + ); + } else { + inputPath = processedInputPath; + } + let tr = new TemplateRender(inputPath, cfg.dir.input, templateConfig); tr.extensionMap = extensionMap; if (templateLang) { diff --git a/src/TemplateLayoutPathResolver.js b/src/TemplateLayoutPathResolver.js index 66d7adf2a..63e167162 100644 --- a/src/TemplateLayoutPathResolver.js +++ b/src/TemplateLayoutPathResolver.js @@ -46,6 +46,18 @@ class TemplateLayoutPathResolver { } init() { + // EJS tests fail in `TemplateLayoutPathResolverTest.js` if config.path is not checked + if (this.config.path) { + let processedInputPath = this.config.path(this.path); + if (processedInputPath) { + if (processedInputPath !== this.path) { + this.fullPath = processedInputPath; + this.filename = this.path; + return; + } + } + } + // we might be able to move this into the constructor? this.aliases = Object.assign({}, this.config.layoutAliases, this.aliases); // debug("Current layout aliases: %o", this.aliases); diff --git a/src/UserConfig.js b/src/UserConfig.js index c07115ae2..16a0be317 100644 --- a/src/UserConfig.js +++ b/src/UserConfig.js @@ -8,6 +8,10 @@ const merge = require("./Util/Merge"); const debug = require("debug")("Eleventy:UserConfig"); const pkg = require("../package.json"); +const { TemplatePath } = require("@11ty/eleventy-utils"); +const fs = require("fs"); +const path = require("path"); + class UserConfigError extends EleventyBaseError {} // API to expose configuration options in config file @@ -90,6 +94,8 @@ class UserConfig { this.dataFilterSelectors = new Set(); this.libraryAmendments = {}; + + this.paths = {}; } versionCheck(expected) { @@ -851,8 +857,51 @@ class UserConfig { precompiledCollections: this.precompiledCollections, dataFilterSelectors: this.dataFilterSelectors, libraryAmendments: this.libraryAmendments, + paths: this.paths, + path: this.path, }; } + + paths = {}; + + path() { + switch (arguments.length) { + case 1: + let file = arguments[0]; + + // if (path.isAbsolute(file) && fs.existsSync(file)) { + if (fs.existsSync(file)) { + return file; + } + + let parts = file.split(":"); + + if (parts.length == 2) { + if (!this.paths[parts[0]]) return false; + + for (let storedPath of this.paths[parts[0]]) { + if (fs.existsSync(storedPath + parts[1])) { + return storedPath + parts[1]; + } + } + } + + return false; + + case 2: + if (!this.paths[arguments[0]]) { + this.paths[arguments[0]] = []; + } + this.paths[arguments[0]].unshift( + TemplatePath.normalizeOperatingSystemFilePath(arguments[1]).replace( + /\/$/, + "" + ) + "/" + ); + } + + return null; + } } module.exports = UserConfig; diff --git a/test/TemplateRenderDynamicPathsTest.js b/test/TemplateRenderDynamicPathsTest.js new file mode 100644 index 000000000..94fa57b2e --- /dev/null +++ b/test/TemplateRenderDynamicPathsTest.js @@ -0,0 +1,131 @@ +const test = require("ava"); +const RenderPlugin = require("../src/Plugins/RenderPlugin"); +const RenderManager = RenderPlugin.RenderManager; +const RenderPluginFile = RenderPlugin.File; +const RenderPluginString = RenderPlugin.String; + +const VuePlugin = require("@11ty/eleventy-plugin-vue"); + +const Eleventy = require("../src/Eleventy"); +const normalizeNewLines = require("./Util/normalizeNewLines"); +const removeNewLines = require("./Util/removeNewLines"); + +async function getTestOutput(input, configCallback = function () {}) { + let elev = new Eleventy(input, "./_site/", { + config: function (eleventyConfig) { + eleventyConfig.addPlugin(RenderPlugin); + configCallback(eleventyConfig); + }, + }); + + elev.setIsVerbose(false); + + // Careful with this! + // elev.disableLogger(); + + await elev.init(); + + let result = await elev.toJSON(); + + if (!result.length) { + throw new Error(`No Eleventy JSON output found for input: ${input}`); + } + return result; +} + +async function getTestOutputForFile(inputFile, configCallback) { + let result = await getTestOutput(inputFile, configCallback); + let html = normalizeNewLines(result[0].content.trim()); + return html; +} + +test("Use 11ty.js file in njk", async (t) => { + let html = await getTestOutputForFile( + "./test/stubs-render-dynamic-paths/11tyjs-file.njk", + function (eleventyConfig) { + eleventyConfig.path( + "includes", + "./test/stubs-render-dynamic-paths/_includes" + ); + } + ); + t.is( + html, + `TESTING +TESTING` + ); +}); + +test("Use 11ty.js file in njk with default layout", async (t) => { + let html = await getTestOutputForFile( + "./test/stubs-render-dynamic-paths/11tyjs-file-default-layout.njk", + function (eleventyConfig) { + eleventyConfig.path( + "includes", + "./test/stubs-render-dynamic-paths/_includes" + ); + } + ); + // console.log(html); + t.is( + html, + `above +TESTING +TESTING + +below` + ); +}); + +test("Use 11ty.js file in njk with themed layout", async (t) => { + let html = await getTestOutputForFile( + "./test/stubs-render-dynamic-paths/11tyjs-file-themed.njk", + function (eleventyConfig) { + eleventyConfig.path( + "includes", + "./test/stubs-render-dynamic-paths/_includes" + ); + eleventyConfig.path( + "views", + "./test/stubs-render-dynamic-paths/themes/parent-theme" + ); + } + ); + // console.log(html); + t.is( + html, + `header of parent theme + +default layout of parent theme +TESTING +TESTING + +footer of parent theme` + ); +}); + +// For some reason, sometimes the path configurations are moved around between instances and async calls. +// If you run this test multiple times, sometimes the one above and sometimes the one below fails. +// I'm not sure, if this has something to do with 11ty internal caching or if this is some weird JS +// async instance pointer issue. +// I moved the child theme test to a separate file to bypass the failure. +// see: `TemplateRenderDynamicPathsTestChildTheme.js` + +// test("Use 11ty.js file in njk with child themed layout", async (t) => { +// let html = await getTestOutputForFile( +// "./test/stubs-render-dynamic-paths/11tyjs-file-child-themed.njk", +// function(eleventyConfig) { +// eleventyConfig.path('views', './test/stubs-render-dynamic-paths/themes/parent-theme'); +// eleventyConfig.path('views', './test/stubs-render-dynamic-paths/themes/child-theme'); +// eleventyConfig.path('includes', './test/stubs-render-dynamic-paths/_includes'); +// } +// ); +// // console.log(html); +// t.is(html, `header of child theme +// +// default layout of child theme +// TESTING +// TESTING +// +// footer of parent theme`); +// }); diff --git a/test/TemplateRenderDynamicPathsTestChildTheme.js b/test/TemplateRenderDynamicPathsTestChildTheme.js new file mode 100644 index 000000000..e5a45e6cb --- /dev/null +++ b/test/TemplateRenderDynamicPathsTestChildTheme.js @@ -0,0 +1,71 @@ +const test = require("ava"); +const RenderPlugin = require("../src/Plugins/RenderPlugin"); +const RenderManager = RenderPlugin.RenderManager; +const RenderPluginFile = RenderPlugin.File; +const RenderPluginString = RenderPlugin.String; + +const VuePlugin = require("@11ty/eleventy-plugin-vue"); + +const Eleventy = require("../src/Eleventy"); +const normalizeNewLines = require("./Util/normalizeNewLines"); +const removeNewLines = require("./Util/removeNewLines"); + +async function getTestOutput(input, configCallback = function () {}) { + let elev = new Eleventy(input, "./_site/", { + config: function (eleventyConfig) { + eleventyConfig.addPlugin(RenderPlugin); + configCallback(eleventyConfig); + }, + }); + + elev.setIsVerbose(false); + + // Careful with this! + // elev.disableLogger(); + + await elev.init(); + + let result = await elev.toJSON(); + + if (!result.length) { + throw new Error(`No Eleventy JSON output found for input: ${input}`); + } + return result; +} + +async function getTestOutputForFile(inputFile, configCallback) { + let result = await getTestOutput(inputFile, configCallback); + let html = normalizeNewLines(result[0].content.trim()); + return html; +} + +test("Use 11ty.js file in njk with child themed layout with partial from parent theme", async (t) => { + let html = await getTestOutputForFile( + "./test/stubs-render-dynamic-paths/11tyjs-file-child-themed.njk", + function (eleventyConfig) { + eleventyConfig.path( + "views", + "./test/stubs-render-dynamic-paths/themes/parent-theme" + ); + eleventyConfig.path( + "views", + "./test/stubs-render-dynamic-paths/themes/child-theme" + ); + eleventyConfig.path( + "includes", + "./test/stubs-render-dynamic-paths/_includes" + ); + } + ); + // console.log(html); + t.is( + html, + `header of child theme + +default layout of child theme +TESTING +TESTING + +footer of parent theme` + ); +}); diff --git a/test/TemplateRenderDynamicPathsTestFromPlugin.js b/test/TemplateRenderDynamicPathsTestFromPlugin.js new file mode 100644 index 000000000..e2ee45362 --- /dev/null +++ b/test/TemplateRenderDynamicPathsTestFromPlugin.js @@ -0,0 +1,71 @@ +const test = require("ava"); +const RenderPlugin = require("../src/Plugins/RenderPlugin"); +const RenderManager = RenderPlugin.RenderManager; +const RenderPluginFile = RenderPlugin.File; +const RenderPluginString = RenderPlugin.String; + +const VuePlugin = require("@11ty/eleventy-plugin-vue"); + +const Eleventy = require("../src/Eleventy"); +const normalizeNewLines = require("./Util/normalizeNewLines"); +const removeNewLines = require("./Util/removeNewLines"); + +const AddTestPathsPlugin = require("./stubs-render-dynamic-paths/plugin/AddTestPathsPlugin.js"); + +async function getTestOutput(input, configCallback = function () {}) { + let elev = new Eleventy(input, "./_site/", { + config: function (eleventyConfig) { + eleventyConfig.addPlugin(RenderPlugin); + + eleventyConfig.addPlugin(AddTestPathsPlugin); + + eleventyConfig.path( + "views", + "./test/stubs-render-dynamic-paths/themes/child-theme" + ); + eleventyConfig.path( + "includes", + "./test/stubs-render-dynamic-paths/_includes" + ); + + configCallback(eleventyConfig); + }, + }); + + elev.setIsVerbose(false); + + // Careful with this! + // elev.disableLogger(); + + await elev.init(); + + let result = await elev.toJSON(); + + if (!result.length) { + throw new Error(`No Eleventy JSON output found for input: ${input}`); + } + return result; +} + +async function getTestOutputForFile(inputFile, configCallback) { + let result = await getTestOutput(inputFile, configCallback); + let html = normalizeNewLines(result[0].content.trim()); + return html; +} + +test("Use 11ty.js file in njk with child themed layout with partial from plugin theme", async (t) => { + let html = await getTestOutputForFile( + "./test/stubs-render-dynamic-paths/11tyjs-file-child-themed.njk" + ); + + t.is( + html, + `header of child theme + +default layout of child theme +TESTING +TESTING + +footer of plugin theme` + ); +}); diff --git a/test/stubs-render-dynamic-paths/11tyjs-file-child-themed.njk b/test/stubs-render-dynamic-paths/11tyjs-file-child-themed.njk new file mode 100644 index 000000000..afc9da811 --- /dev/null +++ b/test/stubs-render-dynamic-paths/11tyjs-file-child-themed.njk @@ -0,0 +1,9 @@ +--- +layout: "views:default.njk" +hi: value +argData: + hi: liquidHi + bye: liquidBye +--- +{% renderFile "./test/stubs-render-plugin/_includes/include.11ty.js", argData %} +{% renderFile "includes:include.11ty.js", argData %} diff --git a/test/stubs-render-dynamic-paths/11tyjs-file-default-layout.njk b/test/stubs-render-dynamic-paths/11tyjs-file-default-layout.njk new file mode 100644 index 000000000..67bbb9c37 --- /dev/null +++ b/test/stubs-render-dynamic-paths/11tyjs-file-default-layout.njk @@ -0,0 +1,9 @@ +--- +layout: default.njk +hi: value +argData: + hi: liquidHi + bye: liquidBye +--- +{% renderFile "./test/stubs-render-plugin/_includes/include.11ty.js", argData %} +{% renderFile "includes:include.11ty.js", argData %} diff --git a/test/stubs-render-dynamic-paths/11tyjs-file-themed.njk b/test/stubs-render-dynamic-paths/11tyjs-file-themed.njk new file mode 100644 index 000000000..afc9da811 --- /dev/null +++ b/test/stubs-render-dynamic-paths/11tyjs-file-themed.njk @@ -0,0 +1,9 @@ +--- +layout: "views:default.njk" +hi: value +argData: + hi: liquidHi + bye: liquidBye +--- +{% renderFile "./test/stubs-render-plugin/_includes/include.11ty.js", argData %} +{% renderFile "includes:include.11ty.js", argData %} diff --git a/test/stubs-render-dynamic-paths/11tyjs-file.njk b/test/stubs-render-dynamic-paths/11tyjs-file.njk new file mode 100644 index 000000000..d56ac769b --- /dev/null +++ b/test/stubs-render-dynamic-paths/11tyjs-file.njk @@ -0,0 +1,8 @@ +--- +hi: value +argData: + hi: liquidHi + bye: liquidBye +--- +{% renderFile "./test/stubs-render-plugin/_includes/include.11ty.js", argData %} +{% renderFile "includes:include.11ty.js", argData %} diff --git a/test/stubs-render-dynamic-paths/_includes/default.njk b/test/stubs-render-dynamic-paths/_includes/default.njk new file mode 100644 index 000000000..4b41c9e8a --- /dev/null +++ b/test/stubs-render-dynamic-paths/_includes/default.njk @@ -0,0 +1,3 @@ +above +{{ content | safe }} +below diff --git a/test/stubs-render-dynamic-paths/_includes/include.11ty.js b/test/stubs-render-dynamic-paths/_includes/include.11ty.js new file mode 100644 index 000000000..2637dc46d --- /dev/null +++ b/test/stubs-render-dynamic-paths/_includes/include.11ty.js @@ -0,0 +1 @@ +module.exports = "TESTING"; diff --git a/test/stubs-render-dynamic-paths/plugin/AddTestPathsPlugin.js b/test/stubs-render-dynamic-paths/plugin/AddTestPathsPlugin.js new file mode 100644 index 000000000..84b48b719 --- /dev/null +++ b/test/stubs-render-dynamic-paths/plugin/AddTestPathsPlugin.js @@ -0,0 +1,12 @@ +module.exports = function (eleventyConfig) { + const key = "views"; + const newPath = + "./test/stubs-render-dynamic-paths/plugin/themes/plugin-theme/"; + + // plugins are processed after config file, but the path needs to be at the end of the array + if (eleventyConfig.paths[key]) { + eleventyConfig.paths[key].push(newPath); + } else { + eleventyConfig.path(key, newPath); + } +}; diff --git a/test/stubs-render-dynamic-paths/plugin/themes/plugin-theme/default.njk b/test/stubs-render-dynamic-paths/plugin/themes/plugin-theme/default.njk new file mode 100644 index 000000000..1e505f8cb --- /dev/null +++ b/test/stubs-render-dynamic-paths/plugin/themes/plugin-theme/default.njk @@ -0,0 +1,4 @@ +{% renderFile "views:partials/head.njk" %} +default layout of plugin theme +{{ content | safe }} +{% renderFile "views:partials/footer.njk" %} diff --git a/test/stubs-render-dynamic-paths/plugin/themes/plugin-theme/partials/footer.njk b/test/stubs-render-dynamic-paths/plugin/themes/plugin-theme/partials/footer.njk new file mode 100644 index 000000000..61a77c818 --- /dev/null +++ b/test/stubs-render-dynamic-paths/plugin/themes/plugin-theme/partials/footer.njk @@ -0,0 +1 @@ +footer of plugin theme diff --git a/test/stubs-render-dynamic-paths/plugin/themes/plugin-theme/partials/head.njk b/test/stubs-render-dynamic-paths/plugin/themes/plugin-theme/partials/head.njk new file mode 100644 index 000000000..e83e332fc --- /dev/null +++ b/test/stubs-render-dynamic-paths/plugin/themes/plugin-theme/partials/head.njk @@ -0,0 +1 @@ +header of plugin theme diff --git a/test/stubs-render-dynamic-paths/themes/child-theme/default.njk b/test/stubs-render-dynamic-paths/themes/child-theme/default.njk new file mode 100644 index 000000000..8d87faea3 --- /dev/null +++ b/test/stubs-render-dynamic-paths/themes/child-theme/default.njk @@ -0,0 +1,4 @@ +{% renderFile "views:partials/head.njk" %} +default layout of child theme +{{ content | safe }} +{% renderFile "views:partials/footer.njk" %} diff --git a/test/stubs-render-dynamic-paths/themes/child-theme/partials/head.njk b/test/stubs-render-dynamic-paths/themes/child-theme/partials/head.njk new file mode 100644 index 000000000..01aaaf3b4 --- /dev/null +++ b/test/stubs-render-dynamic-paths/themes/child-theme/partials/head.njk @@ -0,0 +1 @@ +header of child theme diff --git a/test/stubs-render-dynamic-paths/themes/parent-theme/default.njk b/test/stubs-render-dynamic-paths/themes/parent-theme/default.njk new file mode 100644 index 000000000..32a9266fe --- /dev/null +++ b/test/stubs-render-dynamic-paths/themes/parent-theme/default.njk @@ -0,0 +1,4 @@ +{% renderFile "views:partials/head.njk" %} +default layout of parent theme +{{ content | safe }} +{% renderFile "views:partials/footer.njk" %} diff --git a/test/stubs-render-dynamic-paths/themes/parent-theme/partials/footer.njk b/test/stubs-render-dynamic-paths/themes/parent-theme/partials/footer.njk new file mode 100644 index 000000000..c2e701e4b --- /dev/null +++ b/test/stubs-render-dynamic-paths/themes/parent-theme/partials/footer.njk @@ -0,0 +1 @@ +footer of parent theme diff --git a/test/stubs-render-dynamic-paths/themes/parent-theme/partials/head.njk b/test/stubs-render-dynamic-paths/themes/parent-theme/partials/head.njk new file mode 100644 index 000000000..86bd25526 --- /dev/null +++ b/test/stubs-render-dynamic-paths/themes/parent-theme/partials/head.njk @@ -0,0 +1 @@ +header of parent theme