Search code examples
koanunjucks

Nunjucks setup for koa v2


I have a Koa v2 app with koa-views@next as a renderer and nunjucks templating engine. Here is my working setup, which don't have any problem, I just confused with the double declaration of the views folder:

const Koa = require('koa');
const nunjucks = require('nunjucks');
const path = require('path');
const router = require('koa-router')();
const views = require('koa-views');

const app = new Koa();

const index = require('./routes/index');

app.use(views(path.join(__dirname, 'views'), {
  extension: 'njk',
  map: { njk: 'nunjucks' },
}));
nunjucks.configure(path.join(__dirname, 'views'), {
  autoescape: true,
});

router.use('/', index.routes(), index.allowedMethods());
app
  .use(router.routes())
  .use(router.allowedMethods());

app.listen(3000);

But if I don't do this, the rendering doesn't work. If I uncommenting the nunjucks.configure block, I'm getting the following error:

Template render error: (unknown path)
Error: template not found: layout.njk

Is there any problem with my setup?


Solution

  • I come up a solution to use nunjucks without any other renderer library in koa v2:

    /*
    USAGE:
    
    import njk from './nunjucks';
    
    // Templating - Must be used before any router
    app.use(njk(path.join(__dirname, 'views'), {
      extname: '.njk',
      noCache: process.env.NODE_ENV !== 'production',
      throwOnUndefined: true,
      filters: {
        json: function (str) {
          return JSON.stringify(str, null, 2);
        },
        upperCase: str => str.toUpperCase(),
      },
      globals: {
        version: 'v3.0.0',
      },
    }));
    
    */
    
    // Inspired by:
    // https://github.com/ohomer/koa-nunjucks-render/blob/master/index.js
    // https://github.com/beliefgp/koa-nunjucks-next/blob/master/index.js
    
    const Promise = require('bluebird');
    const nunjucks = require('nunjucks');
    
    function njk(path, opts) {
      const env = nunjucks.configure(path, opts);
    
      const extname = opts.extname || '';
    
      const filters = opts.filters || {};
      //console.time('benchmark');
      const f = Object.keys(filters).length;
      let i = 0;
      while (i < f) {
        env.addFilter(Object.keys(filters)[i], Object.values(filters)[i]);
        i += 1;
      }
      //console.timeEnd('benchmark');
    
      const globals = opts.globals || {};
      const g = Object.keys(globals).length;
      let j = 0;
      while (j < g) {
        env.addFilter(Object.keys(globals)[j], Object.values(globals)[j]);
        j += 1;
      }
    
      return (ctx, next) => {
        ctx.render = (view, context = {}) => {
          context = Object.assign({}, ctx.state, context);
          return new Promise((resolve, reject) => {
            env.render(`${view}${extname}`, context, (err, res) => {
              if (err) {
                return reject(err);
              }
              ctx.body = res;
              return resolve();
            });
          });
        };
        return next();
      };
    }
    
    module.exports = njk;
    

    Gist