nunjucks doesn't provide a way to discover a template's direct or transitive dependencies i.e. the child and grandchild templates that are loaded (via extends, import or include) when a parent template is rendered. This is needed to integrate (efficiently) with bundlers such as Parcel, which track changes in dependencies and use them to trigger rebuilds.
(Note: this is orthogonal to nunjucks' caching support via chokidar, though the implementations may overlap.)
Ideally, this would be scoped to a particular Environment#render call, but there doesn't appear to be any space in the render method's parameters to squeeze in additional options, or to return an additional value. Maybe a sister method with a more flexible interface?
// result is an object rather than a string
const result = env.parse(templatePath, { context: ..., trackDependencies: true })
console.log('dependencies:', result.dependencies)
console.log('source:', result.source)
This makes dependency tracking optional, to err on the side of efficiency, but they could be included by default as it's a new API, and the overhead is unlikely to be high e.g.:
const result = env.parse(templatePath, { context: ... })
Another way to do it is via an EventEmitter API (which is already used, lightly, in loaders) e.g.:
env.on('dependency', ({ name, path, parent }) => { ... })
This has several advantages:
- it doesn't clutter or crowd any existing APIs
- it's easier to implement
- it's easy to extend
- if there's a performance concern, it could be toggled on/off with a constructor option e.g.
new Environment(loaders, { trackDependencies: true })
The main disadvantage is that it doesn't directly answer the question "What are this template's dependencies?" i.e. it requires extra work to scope the results to a particular template/render. (This also implies the use of a full EventEmitter implementation rather than the cut-down version that's currently used for loaders, and suggests the addition of some new events e.g. render:start and render:end.)
I'm envisaging each dependency as an object with the following core fields:
{
name: "../macros/util.html.njk",
path: "/home/foo/dev/example/src/html/macros/util.html.njk",
parent: "/home/foo/dev/example/src/html/screens/layout.html.njk",
}
Optional fields could include cached (a boolean indicating that the template was retrieved from its loader's cache) and maybe async (the template was retrieved by an async loader).
Example
layout.html
{% include "../components/header.html" %}
<h1>Body</h1>
{% include "../components/footer.html" %}
header.html
footer.html
<h1>Footer</h1>
{% include "./copyright.html" %}
copyright.html
Copyright ⓒ example.com 2018
render
const result = env.parse("src/html/screens/layout.html")
console.log(result.dependencies)
result
[
{
name: "src/html/screens/layout.html",
path: "/home/foo/example/src/html/screens/layout.html",
parent: null,
},
{
name: "../components/header.html",
path: "/home/foo/example/src/html/components/header.html",
parent: "/home/foo/example/src/html/screens/layout.html",
},
{
name: "../components/footer.html",
path: "/home/foo/example/src/html/components/footer.html",
parent: "/home/foo/example/src/html/screens/layout.html",
},
{
name: "./copyright.html",
path: "/home/foo/example/src/html/components/copyright.html",
parent: "/home/foo/example/src/html/components/footer.html",
},
]
nunjucks doesn't provide a way to discover a template's direct or transitive dependencies i.e. the child and grandchild templates that are loaded (via
extends,importorinclude) when a parent template is rendered. This is needed to integrate (efficiently) with bundlers such as Parcel, which track changes in dependencies and use them to trigger rebuilds.(Note: this is orthogonal to nunjucks' caching support via chokidar, though the implementations may overlap.)
Ideally, this would be scoped to a particular
Environment#rendercall, but there doesn't appear to be any space in therendermethod's parameters to squeeze in additional options, or to return an additional value. Maybe a sister method with a more flexible interface?This makes dependency tracking optional, to err on the side of efficiency, but they could be included by default as it's a new API, and the overhead is unlikely to be high e.g.:
Another way to do it is via an EventEmitter API (which is already used, lightly, in loaders) e.g.:
This has several advantages:
new Environment(loaders, { trackDependencies: true })The main disadvantage is that it doesn't directly answer the question "What are this template's dependencies?" i.e. it requires extra work to scope the results to a particular template/render. (This also implies the use of a full EventEmitter implementation rather than the cut-down version that's currently used for loaders, and suggests the addition of some new events e.g.
render:startandrender:end.)I'm envisaging each dependency as an object with the following core fields:
Optional fields could include
cached(a boolean indicating that the template was retrieved from its loader's cache) and maybeasync(the template was retrieved by an async loader).Example
layout.html
{% include "../components/header.html" %} <h1>Body</h1> {% include "../components/footer.html" %}header.html
footer.html
copyright.html
render
result