
Nunjucks templates loader for Webpack
This Webpack loader compiles Nunjucks templates.
html-webpack-plugin
compatible.
Install
npm install --save-dev simple-nunjucks-loader
If you don't use dynamic assets in your code, then you could
save a bit on optional glob
dependency:
npm install --no-optional --save-dev simple-nunjucks-loader
Note on global variables
By default Nunjucks wrap templates to global window.nunjucksPrecompiled
.
Loader didn't expose window.nunjucksPrecompiled
. If your code relied on
this object, it will definitely break. Use imports of required template
or adopt expose-loader
to your build pipeline.
Usage
This loader will precompile
Nunjucks templates. It also includes Nunjunks (slim) runtime for browser.
Add loader to your webpack
config as follows:
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.njk$/,
use: [
{
loader: 'simple-nunjucks-loader',
options: {}
}
]
}
]
}
};
template-example.njk
<p>Hello, {{ username }}!</p>
app.js
import template from './template-example.njk'
document.body.innerHTML = template({
username: 'Mike'
})
Bundling of app.js
above will render paragraph with text "Hello, Mike!" to
the page.
With html-webpack-plugin
For using with html-webpack-plugin
just add it to plugins
array, all options
from it would be available as htmlWebpackPlugin.options
in Nunjucks template.
webpack.config.js
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.njk$/,
use: [
{
loader: 'simple-nunjucks-loader',
options: {}
}
]
}
]
},
plugins: [
new HTMLWebpackPlugin({
template: 'src/page.njk',
templateParameters: {
username: 'Joe'
}
})
]
};
src/page.njk
<p>Hello, {{ username }}!</p>
Refer to html-webpack-plugin
page for all
available options.
With assets
To load static assets (like images, for example), this loader inserts own
static
global function. It works like static
from Django/Jinja2 integration,
but resolves paths via Webpack loaders. It just replace calls
static('foo.jpeg')
with static(importedViaWebpackSymbol)
. static
itself
just returns loaded module or default
export of it.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.njk$/,
use: [
{
loader: 'simple-nunjucks-loader',
options: {
assetsPaths: [
'app_example_a/static',
'app_example_b/static',
]
}
}
]
},
{
test: /\.png$/,
use: [{
loader: 'file-loader'
}]
}
]
}
};
template.njk
<img
src="{{ static('./image.png') }}"
alt=""
/>
The code above will replace {{ static('./image.png') }}
with hash, that
file-loader
returns.
Dynamic assets
Loader has limited support for dynamic assets. It was tested with expressions
like:
{{ static('foo/' + bar) }}
{{ static('foo/' + bar + '.ext') }}
:warning: I advocate against using dynamic assets, because:
- I have to support hacky regular expressions :smile:
- It's hard to find usages of asset, because there is no import of it
- Final bundle could be bigger without proper maintaining
From my experience it's better to have some kind of map, that will match some
variable to import:
{% set examplesMap = {
'example-1': static('foo/bar/dynamic-example-1.md'),
'example-2': static('foo/bar/dynamic-example-2.md')
} %}
{% for item in [1, 2] %}
<p>{{ examplesMap['example-' + item] }}</p>
{% endfor %}
How it works
By default Nunjunks bundle all precompiled templates to
window.nunjucksPrecompiled
, then loads them via custom loader from this
global object. If precompiled template reference some other template file,
it is loaded from disk (in NodeJS environment), or fetched via XMLHttpRequest
from internet.
Both are not webpack-way for projects bundling.
This loader workaround this behaviour by precompiling templates and dependant
templates as separate bundle chunks. It also use custom wrapper for precompiled
code to avoid creating window.nunjucksPrecompiled
.
It also adds each found template as dependency for template that need it,
so bundle get rebuild in watch mode only when required.
Asynchronous support
When loader found async filter in template dependencies, it returns Promise
,
instead of render result.
Options
Loader supports limited number of Nunjuncks options.
It's doesn't support watch
(we use webpack
own dependencies watch),
noCache
, web
settings and express
.
All other options get passed to Nunjunks Environment
during files loading.
Name | Type | Default | Description |
---|
jinjaCompat | {Boolean} | false | Install Jinja syntax support in bundle |
searchPaths | {String} or {Array.<string>} | . | One or more paths to resolve templates paths |
assetsPaths | {String} or {Array.<string>} | . | Paths to resolve static assets. Works like STATICFILES_DIRS . |
globals | Object.<string, string> | {} | Map global function to corresponding module |
extensions | Object.<string, string> | {} | Map extension to corresponding module |
filters | Object.<string, string> | {} | Map filters to corresponding module |
autoescape | {Boolean} | true | See Nunjuncks options for description of options below |
throwOnUndefined | {Boolean} | false | |
trimBlocks | {Boolean} | false | |
lstripBlocks | {Boolean} | false | |
tags | {Object.<string, string>} | Default Jinja tags config | Override tags syntax |
tags
default to:
{
blockStart: '{%',
blockEnd: '%}',
variableStart: '{{',
variableEnd: '}}',
commentStart: '{#',
commentEnd: '#}'
}
jinjaCompat
Installs Jinja syntax. This option install it for whole bundle.
searchPaths
Loader is searching for full template relative to given string(s) from
searchPath
option (or project root, if no paths given).
Path to file couldn't be outside of folders above.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.njk$/,
use: [{
loader: 'simple-nunjucks-loader',
options: {
searchPaths: [
'django_app_a/templates',
'django_app_b/templates'
]
}
}]
}
]
}
};
assetsPaths
List of paths where loader should search for assets.
module.exports = {
module: {
rules: [
{
test: /\.njk$/,
use: [{
loader: 'simple-nunjucks-loader',
options: {
assetsPaths: [
'django_app_a/static',
'django_app_b/static'
]
}
}]
}
]
}
};
globals
Set global function and import path, that should return function to use.
It the same function that env.addGlobal
using.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.njk$/,
use: [{
loader: 'simple-nunjucks-loader',
options: {
globals: {
_: 'lodash',
globalEnv: path.join(__dirname, 'app/global-env.js')
}
}
}]
}
]
}
};
app/global-env.js
module.exports = function(foo, bar) {
return `Do anything with ${foo} and ${bar}`;
};
extensions
Set map of extensions that would be imported before each template render.
Extension should return instance, that would be added via
env.addExtension
.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.njk$/,
use: [{
loader: 'simple-nunjucks-loader',
options: {
extensions: {
CustomExtension: path.join(__dirname, 'lib/extensions/custom-extension.js')
}
}
}]
}
]
}
};
lib/extensions/custom-extension.js
const nunjucks = require('nunjucks/browser/nunjucks-slim');
class CustomExtension {}
module.exports = new CustomExtension();
Loader trying to guess which extensions are really used, and keep only required
imports.
filters
Map of filters, that would be imported before each template render.
Filter should return instance, that would be added via
env.addFilter
.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.njk$/,
use: [{
loader: 'simple-nunjucks-loader',
options: {
filters: {
foo: path.join(__dirname, 'foo.js')
}
}
}]
}
]
}
};
foo.js
module.exports = function(val, param) {
return `${val + param}`;
};
template.njk
{{ foo_var | foo(3) }}
To mark filter as async, filter module should export async
flag:
async-filter.js
module.exports = function(val, param, callback) {
setTimeout(function() {
callback(null, val + param);
}, 1000);
};
module.exports.async = true;