Liquid template can't find include file with relative path
Operating system
Ubuntu 22.04
Eleventy
3.0.0
Describe the bug
(For context, I was trying to implement the new bundle stuff in v3.0, following the pattern established by eleventy-base-blog, but ran into problems when I tried to include the css file in my layout. The only difference is I'm using Liquid templates.)
My project looks like this:
$ tree -a
./
├── content/
│ └── index.md
├── _includes/
│ └── base.liquid
├── public/
│ └── style.css
├── eleventy.config.js
└── package.json
I want to include public/style.css in _includes/base.liquid:
<html>
<head>
<title>Test</title>
<style>{% include 'public/style.css' %}</style>
</head>
<body>
{{ content }}
</body>
</html>
But it throws this error:
$ npx @11ty/eleventy
[11ty] Problem writing Eleventy templates:
[11ty] 1. Having trouble writing to "./_site/index.html" from "./content/index.md" (via EleventyTemplateError)
[11ty] 2. ENOENT: Failed to lookup "public/style.css" in "./_includes/,./content/", file:./_includes/base.liquid, line:4, col:16 (via RenderError)
[11ty] 3. ENOENT: Failed to lookup "public/style.css" in "./_includes/,./content/"
[11ty]
[11ty] Original error stack trace: Error: ENOENT: Failed to lookup "public/style.css" in "./_includes/,./content/"
[11ty] at Loader.lookupError (/home/dave/.npm/_npx/34c007b21a377b7f/node_modules/liquidjs/dist/liquid.node.js:2439:21)
[11ty] at Loader.lookup (/home/dave/.npm/_npx/34c007b21a377b7f/node_modules/liquidjs/dist/liquid.node.js:2407:20)
[11ty] at lookup.next (<anonymous>)
[11ty] at toPromise (/home/dave/.npm/_npx/34c007b21a377b7f/node_modules/liquidjs/dist/liquid.node.js:524:32)
[11ty] at async toPromise (/home/dave/.npm/_npx/34c007b21a377b7f/node_modules/liquidjs/dist/liquid.node.js:532:25)
[11ty] at async toPromise (/home/dave/.npm/_npx/34c007b21a377b7f/node_modules/liquidjs/dist/liquid.node.js:532:25)
[11ty] at async toPromise (/home/dave/.npm/_npx/34c007b21a377b7f/node_modules/liquidjs/dist/liquid.node.js:532:25)
[11ty] at async TemplateLayout.renderPageEntry (file:///home/dave/.npm/_npx/34c007b21a377b7f/node_modules/@11ty/eleventy/src/TemplateLayout.js:225:22)
[11ty] at async #renderPageEntryWithLayoutsAndTransforms (file:///home/dave/.npm/_npx/34c007b21a377b7f/node_modules/@11ty/eleventy/src/Template.js:856:14)
[11ty] at async Template.generateMapEntry (file:///home/dave/.npm/_npx/34c007b21a377b7f/node_modules/@11ty/eleventy/src/Template.js:896:15)
[11ty] Wrote 0 files in 0.10 seconds (v3.0.0)
Here's my config:
export const config = {
dir: {
input: 'content',
includes: '../_includes'
}
};
I tried changing the include path to ../public/style.css but no dice there. If I use a Nunjucks layout instead then everything works great, but I'd rather stick with Liquid.
Reproduction steps
- Go to '...'
- Click on '....'
- Scroll down to '....'
- See an error
Expected behavior
I should be able to include a file outside the layouts directory in a Liquid template.
Reproduction URL
https://github.com/dave-kennedy/eleventy-test
Screenshots
No response
I came up with a workaround. First, change the config:
export default function (eleventyConfig) {
eleventyConfig.setLiquidOptions({
root: ['./content', './_includes', './public']
});
};
export const config = {
dir: {
input: 'content',
includes: '../_includes'
}
};
Then change the include to {% render 'style.css' %}.
./public has to be explicitly added to the list because according to the docs:
relativeReference?: boolean
Allow refer to layouts/partials by relative pathname. To avoid arbitrary filesystem read, paths been referenced also need to be within corresponding root, partials, layouts. Defaults to true.
Defined in src/liquid-options.ts:22
I'm not sure if we still need to define config.dir.includes. And I'm still not sure why it works in Nunjucks without any other changes.
It occurs to me that the included CSS file doesn't need to be in the public directory anyways because it's included in the template. So it would make more sense for it to be in the _includes directory.
I think @harttle might need to weigh in here. The following test case fails via node standalone.js in your repo, which is surprising to me:
import { Liquid } from "liquidjs";
let lib = new Liquid({
root: ["./_includes", "./content"]
});
let tmpl = await lib.parse(`{% include '../public/style.css' %}`, './_includes/base.liquid');
let html = await lib.render(tmpl);
console.log( html );
node:internal/modules/run_main:122
triggerUncaughtException(
^
ENOENT: Failed to lookup "../public/style.css" in "./_includes,./content", file:./_includes/base.liquid, line:1, col:1
>> 1| {% include '../public/style.css' %}
^
RenderError: ENOENT: Failed to lookup "../public/style.css" in "./_includes,./content", file:./_includes/base.liquid, line:1, col:1
at Render.renderTemplates (/Users/zachleat/Temp/eleventy-3502/node_modules/liquidjs/dist/liquid.node.js:1210:53)
at renderTemplates.throw (<anonymous>)
at toPromise (/Users/zachleat/Temp/eleventy-3502/node_modules/liquidjs/dist/liquid.node.js:524:32)
at async file:///Users/zachleat/Temp/eleventy-3502/standalone.js:8:12
From Error: ENOENT: Failed to lookup "../public/style.css" in "./_includes,./content"
at Loader.lookupError (/Users/zachleat/Temp/eleventy-3502/node_modules/liquidjs/dist/liquid.node.js:2439:21)
at Loader.lookup (/Users/zachleat/Temp/eleventy-3502/node_modules/liquidjs/dist/liquid.node.js:2407:20)
at lookup.next (<anonymous>)
at toPromise (/Users/zachleat/Temp/eleventy-3502/node_modules/liquidjs/dist/liquid.node.js:524:32)
at toPromise (/Users/zachleat/Temp/eleventy-3502/node_modules/liquidjs/dist/liquid.node.js:530:25)
at toPromise (/Users/zachleat/Temp/eleventy-3502/node_modules/liquidjs/dist/liquid.node.js:530:25)
at async toPromise (/Users/zachleat/Temp/eleventy-3502/node_modules/liquidjs/dist/liquid.node.js:532:25)
at async file:///Users/zachleat/Temp/eleventy-3502/standalone.js:8:12
as a workaround this works (but, why?):
let lib = new Liquid({
root: ["./_includes", "./content", "."]
});
What does "." mean in this context? It refers to the current root directory, no? I suspect there might be some requirement that includes must be inside of root, maybe?
At least, "." is the default value.
After reading through the code, I would guess, it is because Liquid's loader is responsible for determining which directories to consider.
What does "." mean in this context?
"." means CWD.
The following test case fails via node standalone.js in your repo, which is surprising to me:
@zachleat Thanks for looping me in. @dave-kennedy has provided a good explanation. Accessing outside of root/partials/layouts is not allowed. A quick fix without changing the template files is to update this line (allows everything in . to be accessed):
root: ["./_includes", "./content", "."]
Thank you @harttle!
@zachleat I think it would be good if the base blog used an approach that would work with another template engine. Probably exclude the css from the public folder too.