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

Emit 'load' events on Loader and Environment instances #1196

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/emitted/passed/


* **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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 {
Copy link

@chocolateboy chocolateboy Mar 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not worth changing now, but other EventEmitters are available, e.g. little-emitter, which is smaller and doesn't require the addition or modification of a superclass:

const EventEmitter = require('little-emitter'); 

class Environment {
    constructor() {
        EventEmitter(this); // mix in `emit`, `on` etc.
    }
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with the default webpack events shim just because it's so widely used, it was easy, it's well documented, and it only added 1.5K gzipped to the minified builds.

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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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');
});
});
}
});
}());