Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NodeResolveLoader #1197

Merged
merged 1 commit into from
Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
59 changes: 58 additions & 1 deletion nunjucks/src/node-loaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,64 @@ 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;
}
if ((/^[A-Z]:/).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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ foo }}