Skip to content

Commit

Permalink
Engine initialization.
Browse files Browse the repository at this point in the history
Some view engines (i.e. Nunjucks) rely on the manager configuration
and other state in order to render templates. This change provides
a convenience method to allow engines to initialize any additional
state and to update the engine configuration accordingly.
  • Loading branch information
jagoda committed Aug 15, 2015
1 parent f6c39d5 commit c9bac11
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 11 deletions.
17 changes: 12 additions & 5 deletions README.md
Expand Up @@ -165,9 +165,6 @@ server.route({ method: 'GET', path: '/', handler: rootHandler });
var server = new Hapi.Server();
server.connection({ port: 8000 });

var viewPath = Path.join(__dirname, 'templates');
var environment = Nunjucks.configure(viewPath, { watch: false });

var rootHandler = function (request, reply) {

reply.view('index', {
Expand All @@ -181,16 +178,22 @@ server.views({
html: {
compile: function (src, options) {

var template = Nunjucks.compile(src, environment);
var template = Nunjucks.compile(src, options.environment);

return function (context) {

return template.render(context);
};
},

prepare: function (options, next) {

options.compileOptions.environment = Nunjucks.configure(options.path, { watch : false });
return next();
}
}
},
path: viewPath
path: Path.join(__dirname, 'templates')
});

server.route({ method: 'GET', path: '/', handler: rootHandler });
Expand Down Expand Up @@ -220,6 +223,10 @@ Initializes the server views manager where:
where `callback` has the signature `function(err, compiled)` where `compiled` is a
function with signature `function(context, options, callback)` and `callback` has the
signature `function(err, rendered)`.
- `prepare(config, next)` - initializes additional engine state.
The `config` object is the engine configuration object allowing updates to be made.
This is useful for engines like Nunjucks that rely on additional state for rendering.
`next` has the signature `function(err)`.
- `registerPartial(name, src)` - registers a partial for use during template rendering.
The `name` is the partial path that templates should use to reference the partial and
`src` is the uncompiled template string for the partial.
Expand Down
13 changes: 8 additions & 5 deletions examples/nunjucks/index.js
Expand Up @@ -24,24 +24,27 @@ internals.main = function () {
var server = new Hapi.Server();
server.connection({ port: 8000 });

var viewPath = Path.join(__dirname, 'templates');
var environment = Nunjucks.configure(viewPath, { watch: false });

server.views({
engines: {
html: {
compile: function (src, options) {

var template = Nunjucks.compile(src, environment);
var template = Nunjucks.compile(src, options.environment);

return function (context) {

return template.render(context);
};
},

prepare: function (options, next) {

options.compileOptions.environment = Nunjucks.configure(options.path, { watch: false });
return next();
}
}
},
path: viewPath
path: Path.join(__dirname, 'templates')
});

server.route({ method: 'GET', path: '/', handler: rootHandler });
Expand Down
51 changes: 50 additions & 1 deletion lib/manager.js
Expand Up @@ -156,6 +156,9 @@ exports = module.exports = internals.Manager = function (options) {
engine.cache = {};
}

// When a prepare function is provided, state needs to be initialized before trying to compile and render
engine.ready = !(engine.module.prepare && typeof engine.module.prepare === 'function');

// Load partials and helpers

self._loadPartials(engine);
Expand Down Expand Up @@ -301,11 +304,57 @@ internals.Manager.prototype._prepare = function (template, options, callback) {
return callback(Boom.badImplementation('No view engine found for file: ' + template));
}

template = template + (fileExtension ? '' : engine.suffix);

// Engine is ready to render

if (engine.ready) {
return this._prepareTemplates(template, engine, options, callback);
}

// Engine needs initialization

return this._prepareEngine(engine, function (err) {

if (err) {
return callback(err);
}

return self._prepareTemplates(template, engine, options, callback);
});
};


internals.Manager.prototype._prepareEngine = function (engine, next) {

// _prepareEngine can only be invoked when the prepare function is defined

try {
return engine.module.prepare(engine.config, function (err) {

if (err) {
return next(err);
}

engine.ready = true;
return next();
});
}
catch (err) {
return next(err);
}
};


internals.Manager.prototype._prepareTemplates = function (template, engine, options, callback) {

var self = this;

var compiled = {
settings: Hoek.applyToDefaults(engine.config, options)
};

this._path(template + (fileExtension ? '' : engine.suffix), compiled.settings, false, function (err, templatePath) {
this._path(template, compiled.settings, false, function (err, templatePath) {

if (err) {
return callback(err);
Expand Down
206 changes: 206 additions & 0 deletions test/manager.js
Expand Up @@ -355,6 +355,212 @@ describe('Manager', function () {
});
});

describe('with engine initialization', function () {

it('modifies the engine options', function (done) {

var compileOptions;
var runtimeOptions;

var manager = new Manager({
path: __dirname + '/templates',
engines: {
html: {
compile: function (string, options1) {

compileOptions = options1;

return function (context, options2) {

runtimeOptions = options2;
return string;
};
},

prepare: function (options, next) {

options.compileOptions = { stage: 'compile' };
options.runtimeOptions = { stage: 'render' };
return next();
}
}
}
});

manager.render('valid/test', null, null, function (err, rendered) {

expect(err).to.not.exist();
expect(compileOptions).to.include({ stage: 'compile' });
expect(runtimeOptions).to.include({ stage: 'render' });
done();
});
});

it('errors if initialization fails', function (done) {

var manager = new Manager({
path: __dirname + '/templates',
engines: {
html: {
compile: function (string, options1) {

return function (context, options2) {

return string;
};
},

prepare: function (options, next) {

return next(new Error('Initialization failed'));
}
}
}
});

manager.render('valid/test', null, null, function (err, rendered) {

expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.equal('Initialization failed');
expect(rendered).to.not.exist();
done();
});
});

it('errors if initialization throws', function (done) {

var manager = new Manager({
path: __dirname + '/templates',
engines: {
html: {
compile: function (string, options1) {

return function (context, options2) {

return string;
};
},

prepare: function (options, next) {

throw new Error('Initialization error');
}
}
}
});

manager.render('valid/test', null, null, function (err, rendered) {

expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.equal('Initialization error');
expect(rendered).to.not.exist();
done();
});
});

it('only initializes once before rendering', function (done) {

var initialized = 0;

var manager = new Manager({
path: __dirname + '/templates',
engines: {
html: {
compile: function (string, options1) {

return function (context, options2) {

return string;
};
},

prepare: function (options, next) {

++initialized;
return next();
}
}
}
});

expect(initialized).to.equal(0);
manager.render('valid/test', null, null, function (err, rendered1) {

expect(err).to.not.exist();
expect(rendered1).to.exist();
expect(initialized).to.equal(1);
manager.render('valid/test', null, null, function (err, rendered2) {

expect(err).to.not.exist();
expect(rendered2).to.exist();
expect(initialized).to.equal(1);
done();
});
});
});

it('initializes multiple engines independently', function (done) {

var htmlOptions;
var jadeOptions;

var manager = new Manager({
path: __dirname + '/templates',
engines: {
html: {
compile: function (string, options1) {

htmlOptions = options1;
return function (context, options2) {

return string;
};
},

prepare: function (options, next) {

options.compileOptions = { engine: 'handlebars' };
return next();
}
},

jade: {
compile: function (string, options1) {

jadeOptions = options1;
return function (context, options2) {

return string;
};
},

prepare: function (options, next) {

options.compileOptions = { engine: 'jade' };
return next();
}
}
}
});

manager.render('valid/test.html', null, null, function (err, rendered1) {

expect(err).to.not.exist();
expect(rendered1).to.exist();
expect(htmlOptions).to.include({ engine: 'handlebars' });
expect(jadeOptions).to.not.exist();

manager.render('valid/test.jade', null, null, function (err, rendered2) {

expect(err).to.not.exist();
expect(rendered2).to.exist();
expect(jadeOptions).to.include({ engine: 'jade' });
done();
});
});
});
});

describe('with layout', function (done) {

it('returns response', function (done) {
Expand Down

0 comments on commit c9bac11

Please sign in to comment.