Nunjucks: Using relative paths inside an include?

Created on 3 May 2019  Â·  9Comments  Â·  Source: mozilla/nunjucks

Hi,

Is there a way to get the include to understand relative paths that are relative to the file that the include statement is in?

Background

I have the following two files:

├── privacy
│   ├── index.html
│   ├── index.md.html

Inside of index.html I have the following include:

{% block content %} 
{% include "./privacy/index.md.html" %}
{% endblock %} </body>

This works however this does not:

{% block content %} 
{% include "./index.md.html" %}
{% endblock %} </body>

From what I can tell this is because the privacy folder is located in src/test/html/privacy. I have configured nunjucks to resolve files from this directory:

nunjucks.configure(['src/test/html']);

Most helpful comment

This isn't possible by design (because it follows the spec set by Jinja2). Template includes and extends are relative to the template root dirs, not the file from which they're included.

All 9 comments

This isn't possible by design (because it follows the spec set by Jinja2). Template includes and extends are relative to the template root dirs, not the file from which they're included.

Don't think template root dirs is a good design. 👎
And nunjucks does not support render(absolute-path).

Guess I'm getting back to swig ...

I suppose technically you _can_ make jinja2 use relative paths if you write a custom loader. At the moment I don't think nunjucks passes the requesting template as a parameter to the loader. I'd need to add that to make it possible to write a relative-path loader class.

Yes it is possible with custom loader:

class MyLoader {
  constructor() {
    this.async = true
  }

  isRelative (filename) {
    console.log ('isRelative', filename)
    return true
  }

  resolve(parentName, filename) {
    console.log('resolve', parentName, filename)
    return filename
  }

  getSource (filePath, callback) {
    console.log('getSource', filePath)
    try {
      const src = 'test'
      const file = {
        src,
        path: filePath
      }
      callback(null, file)
    } catch (error) {
      callback(error)
    }
  }
}

Similar to what @magicdawn mentioned, template root dirs are not a good design. In my case, and I'm sure many others, my dev working dir is src but when I push the code to prod the root folder often changes to either dist or build.

@antonsamper in that case, src shouldn't be part of your template import path. You would conditionally make your template root /absolute/path/to/src or /absolute/path/to/dist depending on the environment. The template paths in {% include %} are relative to the template root paths of the Environment. The reason I re-opened the ticket is because the API as currently written isn't quite flexible enough to allow users to write their own relative file loader.

any progress on this ?

I'm having the following use case: my nunjucks templates are published as part of my npm package. The user runs the package from their CLI and some results are being rendered. Here are my observations:

  • Not using any template root (nunjucks.configure({})) works locally when the working directory is my project root. I was also able to load relative templates.
  • This approach totally failed after publishing my project, because the working directory from user perspective is not the package root, obviously.
  • What helped, was setting the template root to an absolute path: nunjucks.configure([join(__dirname, "..")], {});

Now everything works as expected, and I can also include templates using relative paths: this template is being included here.

I'm not sure if this behavior is a bug or a feature. But it is nice to use nunjucks outside of express apps as a general-purpose templating language. For me, it has been an improvement over EJS, handlebars and other libraries.

Here is my implemented loader, so I can make relative imports for example

src/components/card.html

{% macro card(name) %}
<div class="bg-red-500 p-12">{{ name }}</div>
{% endmacro %}

src/html/call-to-action/call-to-action-01.html

{% from "../../components/card.html" import card %}

{{ card('Andrew Luca') }}
class RelativeLoader implements ILoader {
    constructor(private paths: string[]) {}
    getSource(name) {
        const fullPath = require.resolve(name, {
            paths: this.paths,
        });

        return {
            src: readFileSync(fullPath, 'utf-8'),
            path: fullPath,
            noCache: true,
        };
    }
}
const filePath = 'src/html/call-to-action/call-to-action-01.html'
const fileDir = 'src/html/call-to-action'
const content = readFileSync(filePath, 'utf8');
const env = new Environment(new RelativeLoader([fileDir]));
return env.renderString(content, {});
Was this page helpful?
0 / 5 - 0 ratings

Related issues

sandiprb picture sandiprb  Â·  5Comments

ricordisamoa picture ricordisamoa  Â·  5Comments

Galfour picture Galfour  Â·  3Comments

ziir picture ziir  Â·  3Comments

mintyPT picture mintyPT  Â·  4Comments