Skip to content

Commit

Permalink
Emit 'load' events on Loader and Environment instances
Browse files Browse the repository at this point in the history
fixes #1153
  • Loading branch information
fdintino committed Mar 4, 2019
1 parent b828158 commit 45325bf
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 44 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,9 @@
Changelog
=========

* Emit 'load' events on `Environment` instances, to allow runtime dependency
tracking. Fixes [#1153](https://github.com/mozilla/nunjucks/issues/1153).

3.1.7 (Jan 12 2019)
------------------

Expand Down
17 changes: 17 additions & 0 deletions docs/api.md
Expand Up @@ -353,6 +353,23 @@ SafeString (to be documented) if one was passed in, so the output will
copy the safeness of the input, but this property is helpful in rare
circumstances.
{% endapi %}

{% api %}
'load' event
env.on('load', function(name, source, loader))

The 'load' event gets emitted whenever a Loader retrieves the source of a
template. It can be listened to in order to determine template dependencies
at runtime. The arguments emitted to the callback are:

* **name** *(String)* The template name, as passed to the loader
* **source** *(Object)* The object that gets returned from Loader.getSource
* **src** *(String)* The template source
* **path** *(String)* The full path to the template
* **noCache** *(Bool)* If `true`, the template wasn't cached.
* **loader** The Loader instance that triggered the event.
{% endapi %}

{% raw %}

## Template
Expand Down
16 changes: 16 additions & 0 deletions docs/fr/api.md
Expand Up @@ -344,6 +344,22 @@ de rares circonstances.
{% endapi %}
{% raw %}

{% api %}
'load' événement
env.on('load', function(name, source, loader))

Quand une instance 'Loader' charge un template, elle émet l'événement
'load'. On peut utiliser cette événement pour détérminer les dépendances
à l'exécution. Ses arguments sont :

* **name** *(String)* Le nom du template
* **source** *(Object)* Le resultat de l'appel de `Loader.getSource`
* **src** *(String)* Le code source du template
* **path** *(String)* Le chemin d'accèss du fichier ou l'URL
* **noCache** *(Bool)* Si la valeur n'est pas mise en cache.
* **loader** L'instance du Loader qui a émetté l'événement
{% endapi %}

## Template

Un `Template` est un objet qui gère la compilation des chaînes de template
Expand Down
2 changes: 1 addition & 1 deletion nunjucks/src/compiler.js
Expand Up @@ -5,7 +5,7 @@ const transformer = require('./transformer');
const nodes = require('./nodes');
const {TemplateError} = require('./lib');
const {Frame} = require('./runtime');
const Obj = require('./object');
const {Obj} = require('./object');

// These are all the same for now, but shouldn't be passed straight
// through
Expand Down
24 changes: 17 additions & 7 deletions nunjucks/src/environment.js
Expand Up @@ -8,7 +8,7 @@ const filters = require('./filters');
const {FileSystemLoader, WebLoader, PrecompiledLoader} = require('./loaders');
const tests = require('./tests');
const globals = require('./globals');
const Obj = require('./object');
const {Obj, EmitterObj} = require('./object');
const globalRuntime = require('./runtime');
const {handleError, Frame} = globalRuntime;
const expressApp = require('./express-app');
Expand Down Expand Up @@ -37,7 +37,7 @@ const noopTmplSrc = {
}
};

class Environment extends Obj {
class Environment extends EmitterObj {
init(loaders, opts) {
// The dev flag determines the trace that'll be shown on errors.
// If set to true, returns the full trace from the error point,
Expand Down Expand Up @@ -82,7 +82,7 @@ class Environment extends Obj {
);
}

this.initCache();
this._initLoaders();

this.globals = globals();
this.filters = {};
Expand All @@ -95,18 +95,28 @@ class Environment extends Obj {
lib._entries(tests).forEach(([name, test]) => this.addTest(name, test));
}

initCache() {
// Caching and cache busting
_initLoaders() {
this.loaders.forEach((loader) => {
// Caching and cache busting
loader.cache = {};
if (typeof loader.on === 'function') {
loader.on('update', (template) => {
loader.cache[template] = null;
loader.on('update', (name, fullname) => {
loader.cache[name] = null;
this.emit('update', name, fullname, loader);
});
loader.on('load', (name, source) => {
this.emit('load', name, source, loader);
});
}
});
}

invalidateCache() {
this.loaders.forEach((loader) => {
loader.cache = {};
});
}

addExtension(name, extension) {
extension.__name = name;
this.extensions[name] = extension;
Expand Down
18 changes: 2 additions & 16 deletions nunjucks/src/loader.js
@@ -1,23 +1,9 @@
'use strict';

const path = require('path');
const Obj = require('./object');

module.exports = class Loader extends Obj {
on(name, func) {
this.listeners = this.listeners || {};
this.listeners[name] = this.listeners[name] || [];
this.listeners[name].push(func);
}

emit(name, ...args) {
if (this.listeners && this.listeners[name]) {
this.listeners[name].forEach((listener) => {
listener(...args);
});
}
}
const {EmitterObj} = require('./object');

module.exports = class Loader extends EmitterObj {
resolve(from, to) {
return path.resolve(path.dirname(from), to);
}
Expand Down
6 changes: 4 additions & 2 deletions nunjucks/src/node-loaders.js
Expand Up @@ -45,7 +45,7 @@ class FileSystemLoader extends Loader {
watcher.on('all', (event, fullname) => {
fullname = path.resolve(fullname);
if (event === 'change' && fullname in this.pathsToNames) {
this.emit('update', this.pathsToNames[fullname]);
this.emit('update', this.pathsToNames[fullname], fullname);
}
});
watcher.on('error', (error) => {
Expand Down Expand Up @@ -76,11 +76,13 @@ class FileSystemLoader extends Loader {

this.pathsToNames[fullpath] = name;

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

Expand Down
2 changes: 1 addition & 1 deletion nunjucks/src/nodes.js
@@ -1,6 +1,6 @@
'use strict';

const Obj = require('./object');
const {Obj} = require('./object');

function traverseAndCheck(obj, type, results) {
if (obj instanceof type) {
Expand Down
25 changes: 24 additions & 1 deletion nunjucks/src/object.js
@@ -1,6 +1,7 @@
'use strict';

// A simple class system, more documentation to come
const EventEmitter = require('events');
const lib = require('./lib');

function parentWrap(parent, prop) {
Expand Down Expand Up @@ -59,4 +60,26 @@ class Obj {
}
}

module.exports = Obj;
class EmitterObj extends EventEmitter {
constructor(...args) {
super();
// Unfortunately necessary for backwards compatibility
this.init(...args);
}

init() {}

get typename() {
return this.constructor.name;
}

static extend(name, props) {
if (typeof name === 'object') {
props = name;
name = 'anonymous';
}
return extendClass(this, name, props);
}
}

module.exports = { Obj, EmitterObj };
2 changes: 1 addition & 1 deletion nunjucks/src/parser.js
Expand Up @@ -2,7 +2,7 @@

var lexer = require('./lexer');
var nodes = require('./nodes');
var Obj = require('./object');
var Obj = require('./object').Obj;
var lib = require('./lib');

class Parser extends Obj {
Expand Down
1 change: 1 addition & 0 deletions nunjucks/src/web-loaders.js
Expand Up @@ -45,6 +45,7 @@ class WebLoader extends Loader {
path: name,
noCache: !useCache
};
this.emit('load', name, result);
if (cb) {
cb(null, result);
}
Expand Down
9 changes: 9 additions & 0 deletions tests/api.js
Expand Up @@ -82,5 +82,14 @@
path: path.resolve(templatesPath, 'string.njk')
})).to.be('FooTest3BazFizzle');
});

it('should emit "load" event on Environment instance', function(done) {
var env = new Environment(new Loader(templatesPath));
env.on('load', function(name, source) {
expect(name).to.equal('item.njk');
done();
});
env.render('item.njk', {});
});
});
}());
59 changes: 44 additions & 15 deletions tests/loader.js
Expand Up @@ -22,21 +22,6 @@
}

describe('loader', function() {
it('should have default opts for WebLoader', function() {
var webLoader = new WebLoader(templatesPath);
expect(webLoader).to.be.a(WebLoader);
expect(webLoader.useCache).to.be(false);
expect(webLoader.async).to.be(false);
});

if (typeof FileSystemLoader !== 'undefined') {
it('should have default opts for FileSystemLoader', function() {
var fileSystemLoader = new FileSystemLoader(templatesPath);
expect(fileSystemLoader).to.be.a(FileSystemLoader);
expect(fileSystemLoader.noCache).to.be(false);
});
}

it('should allow a simple loader to be created', function() {
// From Docs: http://mozilla.github.io/nunjucks/api.html#writing-a-loader
// We should be able to create a loader that only exposes getSource
Expand Down Expand Up @@ -82,5 +67,49 @@
done();
});
});

describe('WebLoader', function() {
it('should have default opts for WebLoader', function() {
var webLoader = new WebLoader(templatesPath);
expect(webLoader).to.be.a(WebLoader);
expect(webLoader.useCache).to.be(false);
expect(webLoader.async).to.be(false);
});

it('should emit a "load" event', function(done) {
var loader = new WebLoader(templatesPath);

if (typeof window === 'undefined') {
this.skip();
}

loader.on('load', function(name, source) {
expect(name).to.equal('simple-base.njk');
done();
});

loader.getSource('simple-base.njk');
});
});

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

it('should emit a "load" event', function(done) {
var loader = new FileSystemLoader(templatesPath);
loader.on('load', function(name, source) {
expect(name).to.equal('simple-base.njk');
done();
});

loader.getSource('simple-base.njk');
});
});
}
});
}());

0 comments on commit 45325bf

Please sign in to comment.