Search code examples
javascripthandlebars.jskoa

How to render the main layout and partials with koa-views + handlebars?


This is my views folder structure:

- views
  - layouts
      layout.hbs
  - partials
      part.hbs
  home.hbs

I'm rendering the template width:

app.use(views(__dirname + '/views', {
  extension: 'hbs',
  map: { hbs: 'handlebars' }
}));

router.get('/', async (ctx) => {
  await ctx.render('home', {
    Name: 'Iris',
    Type: 'Web',
    Path: '/'
  });
});

What I want is to define the main layout file and the partials folder, just like if it was in express-handlebars. Really there's no way to achieve this with koa-views and pure handlebars?

I have to use koa-hbs or koa-handlebars? But they using soon deprecated features (and Handlebars v2.0.0, v3.0.0):

koa deprecated Support for generators will been removed in v3.
See the documentation for examples of how to convert old middleware
https://github.com/koajs/koa/tree/v2.x#old-signature-middleware-v1x app.js:45:5

EDIT:

Seems like koa-hbs and koa-handlebars plugins not compatible with koa v2. So there's no way to use koa v2, partials and layouts rendered with handlebars at the moment? :( Without those (define layouts, partials) handlebars are useless. So still stucked with express...


Solution

  • koa-hbs is really just using handlebars .registerPartial under the hood.

    As basic as possible:

    var handlebars = require('handlebars'),
        fs = require('fs')
    
    handlebars.registerPartial(
        'defaultLayout',
        fs.readFileSync(__dirname + '/views/layouts/default.html', 'utf8')
    )
    
    // then continue with loading the application...
    

    But you probably want the convenience of it just loading your entire partials folder on startup though.

    1. Create a Promise, and have a function execute that reads through your partials folder, and registers each partial via handlebars.registerPartial. It should resolve the Promise when they are all registered.
    2. Create a middleware async that waits for that Promise to be resolved, so your app doesn't render any views before those partials are registered

    Here's an example that I use:

    var fs = require('fs'),
        handlebars = require('handlebars'),
        glob = require('glob'), // for convenience, npm install glob
        path = require('path')
    
    function readAsPromise (path) {
        return new Promise(function (resolve, reject) {
            fs.readFile(path, 'utf8', function (err, data) {
                resolve({path: path, data: data})
            })
        })
    }
    
    function registerPartial (partial) {
        var partialName = path.basename(partial.path, '.hbs')
    
        handlebars.registerPartial(partialName, partial.data)
    }
    
    var loadPartials = new Promise(function (resolve, reject) {
    
        glob('./views/partials/*.hbs', function (err, files) {
            Promise.all(files.map(readAsPromise)).then(function (partials) {
                partials.forEach(registerPartial)
                resolve()
            })
        })
    
    })
    

    Now the question is which version of Koa are you using

    // koa 1
    
    app.use(function* (next) {
        yield loadPartials
        yield next
    })
    
    // latest koa
    
    app.use(async (ctx, next) => {
      await loadPartials
    })
    

    Now just use partials in handlebars as normal. This works fine with koa-views which will require the same instance of handlebars