Skip to content

Commit

Permalink
Add NodeResolveLoader
Browse files Browse the repository at this point in the history
fixes #1175
  • Loading branch information
fdintino committed Mar 5, 2019
1 parent c99154e commit eed3db4
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,10 @@
Changelog
=========

* Adds [`NodeResolveLoader`](http://mozilla.github.io/nunjucks/api.html#noderesolveloader),
a Loader that loads templates using node's
[`require.resolve`](https://nodejs.org/api/modules.html#modules_all_together).
Fixes [#1175](https://github.com/mozilla/nunjucks/issues/1175).
* Emit 'load' events on `Environment` instances, to allow runtime dependency
tracking. Fixes [#1153](https://github.com/mozilla/nunjucks/issues/1153).

Expand Down
18 changes: 18 additions & 0 deletions docs/api.md
Expand Up @@ -193,6 +193,12 @@ use the simple [`configure`](#configure) API, nunjucks automatically
creates the appropriate loader for you, depending if you're in node or
the browser. See [`Loader`](#loader) for more information.

Also only in node, [`NodeResolveLoader`](#noderesolveloader) is
provided to allow templates to be included using
[node `require` resolution](https://nodejs.org/api/modules.html#modules_all_together).
This is not enabled by default with [`configure`](#configure), it must be
explicitly passed into the `Environment` constructor.

```js
// the FileSystemLoader is available if in node
var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));
Expand Down Expand Up @@ -443,6 +449,18 @@ var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));

{% endapi %}

{% api %}
NodeResolveLoader
new NodeResolveLoader([opts])

As the name suggests, this is also only available in node. It will load
templates from the filesystem using node's
[`require.resolve`](https://nodejs.org/api/modules.html#modules_all_together).

**opts** is an object which takes the same properties as
[`FileSystemLoader`](#filesystemloader).
{% endapi %}

{% api %}
WebLoader
new WebLoader([baseURL], [opts])
Expand Down
20 changes: 20 additions & 0 deletions docs/fr/api.md
Expand Up @@ -182,6 +182,13 @@ utilisez l'API de configuration simplifiée, nunjucks crée pour vous
automatiquement le chargeur approprié, selon si vous êtes dans node ou dans
le navigateur. Voir [`Chargeur`](#chargeur) pour plus d'informations.

Aussi dans node, le [`NodeResolveLoader`](#noderesolveloader) est disponible
pour charger depuis le système de fichiers selon l'algorithme de résolution
du module node, ce qui est fait par
[`require.resolve`](https://nodejs.org/api/modules.html#modules_all_together).
Cet chargeur n'est pas activé par défaut; il faut passer éxplicitement au
constructeur de `Environment`.

```js
// Le FileSystemLoader est disponible si on est dans node
var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));
Expand Down Expand Up @@ -431,6 +438,19 @@ var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));

{% endapi %}

{% api %}
NodeResolveLoader
new NodeResolveLoader([opts])

Comme le nom le suggère, cet chargeur n'est disponible que dans node.
Il chargera les templates depuis le système de fichiers selon l'algorithme de
résolution du module node, ce qui est fait par
[`require.resolve`](https://nodejs.org/api/modules.html#modules_all_together).

**opts** est un object avec les même propriétés que
[`FileSystemLoader`](#filesystemloader).
{% endapi %}

{% api %}
WebLoader
new WebLoader([baseURL], [opts])
Expand Down
1 change: 1 addition & 0 deletions nunjucks/index.js
Expand Up @@ -49,6 +49,7 @@ module.exports = {
Template: Template,
Loader: Loader,
FileSystemLoader: loaders.FileSystemLoader,
NodeResolveLoader: loaders.NodeResolveLoader,
PrecompiledLoader: loaders.PrecompiledLoader,
WebLoader: loaders.WebLoader,
compiler: compiler,
Expand Down
56 changes: 55 additions & 1 deletion nunjucks/src/node-loaders.js
Expand Up @@ -86,7 +86,61 @@ class FileSystemLoader extends Loader {
}
}

class NodeResolveLoader extends Loader {
constructor(opts) {
super();
opts = opts || {};
this.pathsToNames = {};
this.noCache = !!opts.noCache;

if (opts.watch) {
if (!chokidar) {
throw new Error('watch requires chokidar to be installed');
}
this.watcher = chokidar.watch();

this.watcher.on('change', (fullname) => {
this.emit('update', this.pathsToNames[fullname], fullname);
});
this.watcher.on('error', (error) => {
console.log('Watcher error: ' + error);
});

this.on('load', (name, source) => {
this.watcher.add(source.path);
});
}
}

getSource(name) {
// Don't allow file-system traversal
if ((/^\.?\.?\//).test(name)) {
return null;
}

let fullpath;

try {
fullpath = require.resolve(name);
} catch (e) {
return null;
}

this.pathsToNames[fullpath] = name;

const source = {
src: fs.readFileSync(fullpath, 'utf-8'),
path: fullpath,
noCache: this.noCache,
};

this.emit('load', name, source);
return source;
}
}

module.exports = {
FileSystemLoader: FileSystemLoader,
PrecompiledLoader: PrecompiledLoader
PrecompiledLoader: PrecompiledLoader,
NodeResolveLoader: NodeResolveLoader,
};
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -73,7 +73,7 @@
"lint": "eslint nunjucks scripts tests",
"prepare": "npm run build",
"test:instrument": "cross-env NODE_ENV=test scripts/bundle.js",
"test:runner": "cross-env NODE_ENV=test scripts/testrunner.js",
"test:runner": "cross-env NODE_ENV=test NODE_PATH=tests/test-node-pkgs scripts/testrunner.js",
"test": "npm run lint && npm run test:instrument && npm run test:runner"
},
"bin": {
Expand Down
41 changes: 41 additions & 0 deletions tests/loader.js
Expand Up @@ -5,19 +5,22 @@
Environment,
WebLoader,
FileSystemLoader,
NodeResolveLoader,
templatesPath;

if (typeof require !== 'undefined') {
expect = require('expect.js');
Environment = require('../nunjucks/src/environment').Environment;
WebLoader = require('../nunjucks/src/web-loaders').WebLoader;
FileSystemLoader = require('../nunjucks/src/node-loaders').FileSystemLoader;
NodeResolveLoader = require('../nunjucks/src/node-loaders').NodeResolveLoader;
templatesPath = 'tests/templates';
} else {
expect = window.expect;
Environment = nunjucks.Environment;
WebLoader = nunjucks.WebLoader;
FileSystemLoader = nunjucks.FileSystemLoader;
NodeResolveLoader = nunjucks.NodeResolveLoader;
templatesPath = '../templates';
}

Expand Down Expand Up @@ -111,5 +114,43 @@
});
});
}

if (typeof NodeResolveLoader !== 'undefined') {
describe('NodeResolveLoader', function() {
it('should have default opts', function() {
var loader = new NodeResolveLoader();
expect(loader).to.be.a(NodeResolveLoader);
expect(loader.noCache).to.be(false);
});

it('should emit a "load" event', function(done) {
var loader = new NodeResolveLoader();
loader.on('load', function(name, source) {
expect(name).to.equal('dummy-pkg/simple-template.html');
done();
});

loader.getSource('dummy-pkg/simple-template.html');
});

it('should render templates', function() {
var env = new Environment(new NodeResolveLoader());
var tmpl = env.getTemplate('dummy-pkg/simple-template.html');
expect(tmpl.render({foo: 'foo'})).to.be('foo');
});

it('should not allow directory traversal', function() {
var loader = new NodeResolveLoader();
var dummyPkgPath = require.resolve('dummy-pkg/simple-template.html');
expect(loader.getSource(dummyPkgPath)).to.be(null);
});

it('should return null if no match', function() {
var loader = new NodeResolveLoader();
var tmplName = 'dummy-pkg/does-not-exist.html';
expect(loader.getSource(tmplName)).to.be(null);
});
});
}
});
}());
Empty file.
12 changes: 12 additions & 0 deletions tests/test-node-pkgs/dummy-pkg/package.json
@@ -0,0 +1,12 @@
{
"name": "dummy-pkg",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Frankie Dintino <fdintino@gmail.com> (http://www.frankiedintino.com/)",
"license": "ISC"
}
1 change: 1 addition & 0 deletions tests/test-node-pkgs/dummy-pkg/simple-template.html
@@ -0,0 +1 @@
{{ foo }}

0 comments on commit eed3db4

Please sign in to comment.