Skip to content

Commit f997a52

Browse files
committedMar 5, 2019
Add NodeResolveLoader
fixes #1175
1 parent 34b0a26 commit f997a52

File tree

10 files changed

+156
-2
lines changed

10 files changed

+156
-2
lines changed
 

‎CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Changelog
22
=========
33

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

‎docs/api.md

+18
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ use the simple [`configure`](#configure) API, nunjucks automatically
193193
creates the appropriate loader for you, depending if you're in node or
194194
the browser. See [`Loader`](#loader) for more information.
195195

196+
Also only in node, [`NodeResolveLoader`](#noderesolveloader) is
197+
provided to allow templates to be included using
198+
[node `require` resolution](https://nodejs.org/api/modules.html#modules_all_together).
199+
This is not enabled by default with [`configure`](#configure), it must be
200+
explicitly passed into the `Environment` constructor.
201+
196202
```js
197203
// the FileSystemLoader is available if in node
198204
var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));
@@ -443,6 +449,18 @@ var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));
443449

444450
{% endapi %}
445451

452+
{% api %}
453+
NodeResolveLoader
454+
new NodeResolveLoader([opts])
455+
456+
As the name suggests, this is also only available in node. It will load
457+
templates from the filesystem using node's
458+
[`require.resolve`](https://nodejs.org/api/modules.html#modules_all_together).
459+
460+
**opts** is an object which takes the same properties as
461+
[`FileSystemLoader`](#filesystemloader).
462+
{% endapi %}
463+
446464
{% api %}
447465
WebLoader
448466
new WebLoader([baseURL], [opts])

‎docs/fr/api.md

+20
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,13 @@ utilisez l'API de configuration simplifiée, nunjucks crée pour vous
182182
automatiquement le chargeur approprié, selon si vous êtes dans node ou dans
183183
le navigateur. Voir [`Chargeur`](#chargeur) pour plus d'informations.
184184

185+
Aussi dans node, le [`NodeResolveLoader`](#noderesolveloader) est disponible
186+
pour charger depuis le système de fichiers selon l'algorithme de résolution
187+
du module node, ce qui est fait par
188+
[`require.resolve`](https://nodejs.org/api/modules.html#modules_all_together).
189+
Cet chargeur n'est pas activé par défaut; il faut passer éxplicitement au
190+
constructeur de `Environment`.
191+
185192
```js
186193
// Le FileSystemLoader est disponible si on est dans node
187194
var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));
@@ -431,6 +438,19 @@ var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));
431438

432439
{% endapi %}
433440

441+
{% api %}
442+
NodeResolveLoader
443+
new NodeResolveLoader([opts])
444+
445+
Comme le nom le suggère, cet chargeur n'est disponible que dans node.
446+
Il chargera les templates depuis le système de fichiers selon l'algorithme de
447+
résolution du module node, ce qui est fait par
448+
[`require.resolve`](https://nodejs.org/api/modules.html#modules_all_together).
449+
450+
**opts** est un object avec les même propriétés que
451+
[`FileSystemLoader`](#filesystemloader).
452+
{% endapi %}
453+
434454
{% api %}
435455
WebLoader
436456
new WebLoader([baseURL], [opts])

‎nunjucks/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ module.exports = {
4949
Template: Template,
5050
Loader: Loader,
5151
FileSystemLoader: loaders.FileSystemLoader,
52+
NodeResolveLoader: loaders.NodeResolveLoader,
5253
PrecompiledLoader: loaders.PrecompiledLoader,
5354
WebLoader: loaders.WebLoader,
5455
compiler: compiler,

‎nunjucks/src/node-loaders.js

+58-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,64 @@ class FileSystemLoader extends Loader {
8686
}
8787
}
8888

89+
class NodeResolveLoader extends Loader {
90+
constructor(opts) {
91+
super();
92+
opts = opts || {};
93+
this.pathsToNames = {};
94+
this.noCache = !!opts.noCache;
95+
96+
if (opts.watch) {
97+
if (!chokidar) {
98+
throw new Error('watch requires chokidar to be installed');
99+
}
100+
this.watcher = chokidar.watch();
101+
102+
this.watcher.on('change', (fullname) => {
103+
this.emit('update', this.pathsToNames[fullname], fullname);
104+
});
105+
this.watcher.on('error', (error) => {
106+
console.log('Watcher error: ' + error);
107+
});
108+
109+
this.on('load', (name, source) => {
110+
this.watcher.add(source.path);
111+
});
112+
}
113+
}
114+
115+
getSource(name) {
116+
// Don't allow file-system traversal
117+
if ((/^\.?\.?(\/|\\)/).test(name)) {
118+
return null;
119+
}
120+
if ((/^[A-Z]:/).test(name)) {
121+
return null;
122+
}
123+
124+
let fullpath;
125+
126+
try {
127+
fullpath = require.resolve(name);
128+
} catch (e) {
129+
return null;
130+
}
131+
132+
this.pathsToNames[fullpath] = name;
133+
134+
const source = {
135+
src: fs.readFileSync(fullpath, 'utf-8'),
136+
path: fullpath,
137+
noCache: this.noCache,
138+
};
139+
140+
this.emit('load', name, source);
141+
return source;
142+
}
143+
}
144+
89145
module.exports = {
90146
FileSystemLoader: FileSystemLoader,
91-
PrecompiledLoader: PrecompiledLoader
147+
PrecompiledLoader: PrecompiledLoader,
148+
NodeResolveLoader: NodeResolveLoader,
92149
};

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"lint": "eslint nunjucks scripts tests",
7474
"prepare": "npm run build",
7575
"test:instrument": "cross-env NODE_ENV=test scripts/bundle.js",
76-
"test:runner": "cross-env NODE_ENV=test scripts/testrunner.js",
76+
"test:runner": "cross-env NODE_ENV=test NODE_PATH=tests/test-node-pkgs scripts/testrunner.js",
7777
"test": "npm run lint && npm run test:instrument && npm run test:runner"
7878
},
7979
"bin": {

‎tests/loader.js

+41
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,22 @@
55
Environment,
66
WebLoader,
77
FileSystemLoader,
8+
NodeResolveLoader,
89
templatesPath;
910

1011
if (typeof require !== 'undefined') {
1112
expect = require('expect.js');
1213
Environment = require('../nunjucks/src/environment').Environment;
1314
WebLoader = require('../nunjucks/src/web-loaders').WebLoader;
1415
FileSystemLoader = require('../nunjucks/src/node-loaders').FileSystemLoader;
16+
NodeResolveLoader = require('../nunjucks/src/node-loaders').NodeResolveLoader;
1517
templatesPath = 'tests/templates';
1618
} else {
1719
expect = window.expect;
1820
Environment = nunjucks.Environment;
1921
WebLoader = nunjucks.WebLoader;
2022
FileSystemLoader = nunjucks.FileSystemLoader;
23+
NodeResolveLoader = nunjucks.NodeResolveLoader;
2124
templatesPath = '../templates';
2225
}
2326

@@ -111,5 +114,43 @@
111114
});
112115
});
113116
}
117+
118+
if (typeof NodeResolveLoader !== 'undefined') {
119+
describe('NodeResolveLoader', function() {
120+
it('should have default opts', function() {
121+
var loader = new NodeResolveLoader();
122+
expect(loader).to.be.a(NodeResolveLoader);
123+
expect(loader.noCache).to.be(false);
124+
});
125+
126+
it('should emit a "load" event', function(done) {
127+
var loader = new NodeResolveLoader();
128+
loader.on('load', function(name, source) {
129+
expect(name).to.equal('dummy-pkg/simple-template.html');
130+
done();
131+
});
132+
133+
loader.getSource('dummy-pkg/simple-template.html');
134+
});
135+
136+
it('should render templates', function() {
137+
var env = new Environment(new NodeResolveLoader());
138+
var tmpl = env.getTemplate('dummy-pkg/simple-template.html');
139+
expect(tmpl.render({foo: 'foo'})).to.be('foo');
140+
});
141+
142+
it('should not allow directory traversal', function() {
143+
var loader = new NodeResolveLoader();
144+
var dummyPkgPath = require.resolve('dummy-pkg/simple-template.html');
145+
expect(loader.getSource(dummyPkgPath)).to.be(null);
146+
});
147+
148+
it('should return null if no match', function() {
149+
var loader = new NodeResolveLoader();
150+
var tmplName = 'dummy-pkg/does-not-exist.html';
151+
expect(loader.getSource(tmplName)).to.be(null);
152+
});
153+
});
154+
}
114155
});
115156
}());

‎tests/test-node-pkgs/dummy-pkg/index.js

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "dummy-pkg",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [],
10+
"author": "Frankie Dintino <fdintino@gmail.com> (http://www.frankiedintino.com/)",
11+
"license": "ISC"
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{ foo }}

0 commit comments

Comments
 (0)
Please sign in to comment.