11ty / eleventy

A simpler site generator. Transforms a directory of templates (of varying types) into HTML.
https://www.11ty.dev/
MIT License
17.28k stars 495 forks source link

Liquid template can't find include file with relative path #3502

Open dave-kennedy opened 1 month ago

dave-kennedy commented 1 month ago

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

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. 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

dave-kennedy commented 1 month ago

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.

zachleat commented 1 month ago

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?

Ryuno-Ki commented 1 month ago

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.

harttle commented 1 month ago

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", "."]
zachleat commented 1 month ago

Thank you @harttle!

dave-kennedy commented 1 month ago

@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.