Skip to content

Commit

Permalink
Adds support for virtual templates to be Eleventy Layouts. #2307 #1612
Browse files Browse the repository at this point in the history
  • Loading branch information
zachleat committed Jul 2, 2024
1 parent a9fe852 commit 510c336
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 20 deletions.
23 changes: 14 additions & 9 deletions src/EleventyFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,15 +359,20 @@ class EleventyFiles {
getPathsWithVirtualTemplates(paths) {
// Support for virtual templates added in 3.0
if (this.config.virtualTemplates && isPlainObject(this.config.virtualTemplates)) {
let virtualTemplates = Object.keys(this.config.virtualTemplates).map((path) => {
let fullVirtualPath = this.dirs.getInputPath(path);
if (!this.extensionMap.getKey(fullVirtualPath)) {
this.eleventyConfig.logger.warn(
`The virtual template at ${fullVirtualPath} is using a template format that’s not valid for your project. Your project is using: "${this.formats}". Read more about formats: https://www.11ty.dev/docs/config/#template-formats`,
);
}
return fullVirtualPath;
});
let virtualTemplates = Object.keys(this.config.virtualTemplates)
.filter((path) => {
// Filter out includes/layouts
return this.dirs.isTemplateFile(path);
})
.map((path) => {
let fullVirtualPath = this.dirs.getInputPath(path);
if (!this.extensionMap.getKey(fullVirtualPath)) {
this.eleventyConfig.logger.warn(
`The virtual template at ${fullVirtualPath} is using a template format that’s not valid for your project. Your project is using: "${this.formats}". Read more about formats: https://www.11ty.dev/docs/config/#template-formats`,
);
}
return fullVirtualPath;
});

paths = paths.concat(virtualTemplates);

Expand Down
31 changes: 22 additions & 9 deletions src/TemplateLayoutPathResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class TemplateLayoutPathResolver {
this.originalDisplayPath =
TemplatePath.join(this.layoutsDir, this.originalPath) +
` (via \`layout: ${this.originalPath}\`)`; // for error messaging

this.path = path;
this.aliases = {};
this.extensionMap = extensionMap;
Expand All @@ -24,6 +25,12 @@ class TemplateLayoutPathResolver {
this.init();
}

getVirtualTemplate(layoutPath) {
let inputDirRelativePath =
this.eleventyConfig.directories.getLayoutPathRelativeToInputDirectory(layoutPath);
return this.config.virtualTemplates[inputDirRelativePath];
}

get dirs() {
return this.eleventyConfig.directories;
}
Expand Down Expand Up @@ -59,6 +66,17 @@ class TemplateLayoutPathResolver {
}
}

exists(layoutPath) {
if (this.getVirtualTemplate(layoutPath)) {
return true;
}
let fullPath = this.eleventyConfig.directories.getLayoutPath(layoutPath);
if (fs.existsSync(fullPath)) {
return true;
}
return false;
}

init() {
// we might be able to move this into the constructor?
this.aliases = Object.assign({}, this.config.layoutAliases, this.aliases);
Expand All @@ -69,16 +87,12 @@ class TemplateLayoutPathResolver {

let useLayoutResolution = this.config.layoutResolution;

this.pathAlreadyHasExtension = TemplatePath.join(this.layoutsDir, this.path);

if (this.path.split(".").length > 0 && fs.existsSync(this.pathAlreadyHasExtension)) {
if (this.path.split(".").length > 0 && this.exists(this.path)) {
this.filename = this.path;
this.fullPath = TemplatePath.addLeadingDotSlash(this.pathAlreadyHasExtension);
this.fullPath = this.eleventyConfig.directories.getLayoutPath(this.path);
} else if (useLayoutResolution) {
this.filename = this.findFileName();
this.fullPath = TemplatePath.addLeadingDotSlash(
TemplatePath.join(this.layoutsDir, this.filename || ""),
);
this.fullPath = this.eleventyConfig.directories.getLayoutPath(this.filename || "");
}
}

Expand Down Expand Up @@ -108,8 +122,7 @@ class TemplateLayoutPathResolver {

findFileName() {
for (let filename of this.extensionMap.getFileList(this.path)) {
// TODO async
if (fs.existsSync(TemplatePath.join(this.layoutsDir, filename))) {
if (this.exists(filename)) {
return filename;
}
}
Expand Down
35 changes: 33 additions & 2 deletions src/Util/ProjectDirectories.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,19 @@ class ProjectDirectories {
return this.#dirs.output || ProjectDirectories.defaults.output;
}

isTemplateFile(filePath) {
let inputPath = this.getInputPath(filePath);
if (this.layouts && inputPath.startsWith(this.layouts)) {
return false;
}

if (inputPath.startsWith(this.includes)) {
return false;
}

return inputPath.startsWith(this.input);
}

// for a hypothetical template file
getInputPath(filePath) {
// TODO change ~/ to project root dir
Expand All @@ -253,9 +266,27 @@ class ProjectDirectories {
}

// Inverse of getInputPath
getInputPathRelativeToInputDirectory(filePath) {
// Removes input dir from path
getInputPathRelativeToInputDirectory(filePathRelativeToInputDir) {
let inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input));
return TemplatePath.stripLeadingSubPath(filePath, inputDir);

// No leading dot slash
return TemplatePath.stripLeadingSubPath(filePathRelativeToInputDir, inputDir);
}

getLayoutPath(filePath) {
return TemplatePath.addLeadingDotSlash(
TemplatePath.join(this.layouts || this.includes, TemplatePath.standardizeFilePath(filePath)),
);
}

// Removes layout dir from path
getLayoutPathRelativeToInputDirectory(filePathRelativeToLayoutDir) {
let layoutPath = this.getLayoutPath(filePathRelativeToLayoutDir);
let inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input));

// No leading dot slash
return TemplatePath.stripLeadingSubPath(layoutPath, inputDir);
}

getProjectPath(filePath) {
Expand Down
17 changes: 17 additions & 0 deletions test/EleventyVirtualTemplatesTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,20 @@ test("RSS virtual templates plugin", async (t) => {
let [ feed ] = results.filter(entry => entry.outputPath.endsWith(".xml"));
t.truthy(feed.content.startsWith(`<?xml version="1.0" encoding="utf-8"?>`));
});

test("Virtual templates as layouts, issue #2307", async (t) => {
let elev = new Eleventy("./test/stubs-virtual-nowrite", "./test/stubs-virtual-nowrite/_site", {
config: function (eleventyConfig) {
eleventyConfig.addTemplate("virtual.md", `# Hello`, {
layout: "virtual.html"
});
eleventyConfig.addTemplate("_includes/virtual.html", `<!-- Layout -->{{ content }}`)
},
});

let results = await elev.toJSON();

t.deepEqual(results.length, 1);
t.deepEqual(results[0].content.trim(), `<!-- Layout --><h1>Hello</h1>`);
t.deepEqual(results[0].rawInput, `# Hello`);
});
25 changes: 25 additions & 0 deletions test/ProjectDirectoriesTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,12 @@ test("Content/template/input paths", t => {
let d = new ProjectDirectories();
t.is(d.getInputPath("test.md"), "./test.md");
t.is(d.getInputPath("./test.md"), "./test.md");
t.is(d.getLayoutPath("./layout.html"), "./_includes/layout.html");

d.setInput("test");
t.is(d.getInputPath("test.md"), "./test/test.md");
t.is(d.getInputPath("./test.md"), "./test/test.md");
t.is(d.getLayoutPath("./layout.html"), "./test/_includes/layout.html");
});

test("Project file paths", t => {
Expand Down Expand Up @@ -322,3 +324,26 @@ test("CLI values should override all others (just output)", t => {
t.is(d.includes, "./test/stubs/myincludes/");
t.is(d.layouts, undefined);
});

test("getLayoutPath (layouts dir)", t => {
let d = new ProjectDirectories();
d.setViaConfigObject({
input: "test/stubs",
layouts: "mylayouts",
includes: "components",
});

t.is(d.getLayoutPath("layout.html"), "./test/stubs/mylayouts/layout.html");
t.is(d.getLayoutPathRelativeToInputDirectory("layout.html"), "mylayouts/layout.html");
});

test("getLayoutPath (includes dir)", t => {
let d = new ProjectDirectories();
d.setViaConfigObject({
input: "test/stubs",
includes: "components",
});

t.is(d.getLayoutPath("layout.html"), "./test/stubs/components/layout.html");
t.is(d.getLayoutPathRelativeToInputDirectory("layout.html"), "components/layout.html");
});

0 comments on commit 510c336

Please sign in to comment.