Writing Express View Engines

Express.js

The other day I was working on my JavaScript library HCAS (I’ll write a post about it at some point) when I decided to make it an Express View Engine because, why not?

So I started doing what any good developer would do, I went on Google to find some blog or StackOverflow post explaining how to do it and, surprisingly enough, it’s a bit hard to find resources on this subject, which is weird considering the amount of view engines for Express.

During my search I found this link with the snippet below:

var fs = require('fs'); // this engine requires the fs module

app.engine('ntl', function (filePath, options, callback) { // define the template engine
    fs.readFile(filePath, function (err, content) {
    if (err) return callback(new Error(err));
    // this is an extremely simple template engine
    var rendered = content.toString().replace('#title#', ''+ options.title +'')
        .replace('#message#', ''+ options.message +'');
    return callback(null, rendered);
    });
});

app.set('views', './views'); // specify the views directory
app.set('view engine', 'ntl'); // register the template engine

It was a good start, I was able to make HCAS a view engine in no time but, clearly, it wasn’t enough for a single reason: I can’t put this inside my module’s code without sacrificing the ability to set it as the default view engine by using app.set('view engine', 'hcas');. I’d have to write the code in a way in which I’d always have to instantiate HCAS by writing var hcas = require('hcas').setExpressApp(app); or require('hcas')(app); so I could call Express’ app instance from within my module.

Going back to my Google searches I had no luck finding an answer for this problem, so I started going through Express’ and other View Engine’s source codes to try to figure out how they do it. During my search, I found this piece of code in Express’ View.js (lines 76~79):

if (!opts.engines[this.ext]) {
    // load engine
    opts.engines[this.ext] = require(this.ext.substr(1)).__express;
}

Whoa! Now everything became clear. What this code does is, when you set app.set('view engine', 'yourlib');, it automatically instantiates your module and calls an __express function so Express can call your module when loading a view. So here’s how the code looks like in HCAS now:

var hcas = exports = module.exports = require('./src/hcas.js');
var fs = require('fs');

//builds express viewEngine
exports.__express = function (filePath, options, callback) {
    fs.readFile(filePath, 'ascii', function (err, data) {
        if (err) {
            console.log("Could not open file" + err);
            process.exit(1);
        }

        hcas.parse(data.substring(0, data.length), options, function(result) {
            rendered = result.render();
            return callback(null, rendered);
        });
    });
};

The only sad thing is that it’ll only work if you put your module in the node_modules folder of a Node application, so it’s impossible to test it while developing your library by just creating a simple Node app in your module’s folder and calling app.set('view engine', '.') because Express expects the name of your library to use it as the view’s file extension.
A simple workaround for that is to instantiate the module by using app.engine('hcas', require('.').__express); and, when rendering the view in the route, putting it’s extension there:

router.get('/', function (req, res) {
    res.render('index.hcas', { title: 'Bound title', message: 'Hello there!'});
});

By doing that you won’t have your library as the default View Engine while testing, but at least it’ll be the default one for the extension you specified.

Well, that’s basically it. It took me a while to find the answer for this problem but the solution is actually quite simple. I hope this can be helpful to anyone who’s trying to write an Express View Engine.

Eric Mackrodt on sabyoutubeEric Mackrodt on sabtwitterEric Mackrodt on sablinkedinEric Mackrodt on sabgithubEric Mackrodt on sabemail
Eric Mackrodt
Software Developer, Tech Enthusiast, Music Lover, Movie Watcher, TV Junkie, food eater, air breather...
Back To Top