Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add paths to enable theming #2451

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 11 additions & 9 deletions src/Plugins/RenderPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions src/TemplateLayoutPathResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
49 changes: 49 additions & 0 deletions src/UserConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -90,6 +94,8 @@ class UserConfig {
this.dataFilterSelectors = new Set();

this.libraryAmendments = {};

this.paths = {};
}

versionCheck(expected) {
Expand Down Expand Up @@ -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;
131 changes: 131 additions & 0 deletions test/TemplateRenderDynamicPathsTest.js
Original file line number Diff line number Diff line change
@@ -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`);
// });
71 changes: 71 additions & 0 deletions test/TemplateRenderDynamicPathsTestChildTheme.js
Original file line number Diff line number Diff line change
@@ -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`
);
});