rollup-plugin-handlebars-plus

Rollup plugin to compile Handlebars templates including support for partials and more.

Usage no npm install needed!

<script type="module">
  import rollupPluginHandlebarsPlus from 'https://cdn.skypack.dev/rollup-plugin-handlebars-plus';
</script>

README

rollup-plugin-handlebars-plus

Rollup plugin to precompile and resolve Handlebars templates.

Features:

  • Import Handlebars templates as ES6 modules
  • Support for Handlebars helpers and partials
  • Precompiles templates so your application only needs the Handlebars runtime
  • Handlebars runtime included
  • Optional rendering to jQuery collections vs. raw strings

Installation

npm install rollup-plugin-handlebars-plus or npm install rollup-plugin-handlebars-plus --save

To use the plugin's copy of the Handlebars runtime, you'll also need to do:

npm install rollup-plugin-node-resolve rollup-plugin-commonjs or npm install rollup-plugin-node-resolve rollup-plugin-commonjs --save

See here for more information.

Usage

var rollup = require('rollup');
var handlebars = require('rollup-plugin-handlebars-plus');
var rootImport = require('rollup-plugin-root-import');

var partialRoots = [`${__dirname}/src/client/js/views/`, `${__dirname}/src/common/views/`];

rollup({
  entry: 'main.js',
  plugins: [
    // Required by use of `partialRoot` below.
    rootImport({
      root: partialRoots,
    }),
    handlebars({
      handlebars: {
        // The module ID of the Handlebars runtime, exporting `Handlebars` as `default`.
        // As a shortcut, you can pass this as the value of `handlebars` above.
        // See the "Handlebars" section below.
        id: 'handlebars', // Default: the path of Handlebars' CJS definition within this module

        // Custom handlebars compiler if the built in version is not proper. If you pass this,
        // you must also pass `id` (above), to ensure that the compiler and runtime versions match.
        module: require('handlebars'),

        // Options to pass to Handlebars' `parse` and `precompile` methods.
        options: {
          // Whether to generate sourcemaps for the templates
          sourceMap: true, // Default: true
        },
      },

      // The ID(s) of modules to import before every template, see the "Helpers" section below.
      // Can be a string too.
      helpers: ['/utils/HandlebarsHelpers.js'], // Default: none

      // Whether to register the defined helpers at template declaration in a way that would allow
      // the initialization call to be elided if the template is never used. Useful in a library
      // context where the templates might all get tree-shaken away, leaving no need for the
      // helpers. Does nothing if helpers is empty.
      helpersPureInitialize: true, // Default: false

      // In case you want to compile files with other extensions.
      templateExtension: '.html', // Default: '.hbs'

      // A function that can determine whether or not a template is a partial.
      isPartial: (name) => name.startsWith('_'), // Default: as at left

      // The absolute paths of the root directory(ies) from which to try to resolve the partials.
      // You must also register these with `rollup-plugin-root-import`.
      partialRoot: partialRoots, // Default: none

      // The module ID of jQuery, see the "jQuery" section below.
      jquery: 'jquery', // Default: none
    }),
  ],
});

lets you do this:

{{! src/client/js/views/_messageBody.html }}
<p>{{message}}</p>
{{! src/client/js/views/message.html }}
<div>{{> _messageBody }}</div>
// main.js
import $ from 'jquery';
import MessageTemplate from 'message.html';

$('body').append(MessageTemplate({ message: 'Hello world!' }));

Helpers

You can load Handlebars helpers using the helpers option, whose value is the ID(s) of modules to import before every template. They should export as default a function that accepts the Handlebars runtime as argument, letting them register helpers. Each such export will only be invoked once.

var rollup = require('rollup');
var handlebars = require('rollup-plugin-handlebars-plus');

rollup({
  entry: 'main.js',
  plugins: [
    handlebars({
      helpers: ['/utils/HandlebarsHelpers.js'],
    }),
  ],
});
// /utils/HandlebarsHelpers.js
export default function (Handlebars) {
  Handlebars.registerHelper('encodeURIComponent', function (text) {
    return new Handlebars.SafeString(encodeURIComponent(text));
  });
}
{{! dashboardLink.hbs }}
<a href="https://app.mixmax.com/dashboard/live?user={{encodeURIComponent email}}">Dashboard</a>
// main.js
import DashboardLinkTemplate from './dashboardLink.hbs';

console.log(DashboardLinkTemplate({ email: 'jeff@mixmax.com' }));

Handlebars

This plugin produces precompiled templates, which then need to be rendered by the Handlebars runtime. You can either bundle the runtime yourself and provide its module ID to this plugin using the handlebars.id option, or you can let the plugin bundle its own copy of the runtime.

The advantage of the latter is that compatibility is guaranteed between the compiler and the runtime (see #6 here).

The tradeoff is that the plugin's copy of the runtime is a CJS module, so to load such you'll also need to install rollup-plugin-node-resolve and rollup-plugin-commonjs:

var rollup = require('rollup');
var nodeResolve = require('rollup-plugin-node-resolve');
var commonjs = require('rollup-plugin-commonjs');
var handlebars = require('rollup-plugin-handlebars-plus');

rollup({
  entry: 'main.js',
  plugins: [
    nodeResolve(),
    commonjs({
      include: 'node_modules/**',
    }),
    handlebars(),
  ],
});

In case you need the default runtime ID, it's available as handlebars.runtimeId. This might be useful if you want to import the runtime for use by templates precompiled by something other than this plugin. In that case, you'll have to make sure that the other compiler's version is compatible with this runtime.

jQuery

At Mixmax we often find it convenient to render templates to jQuery collections rather than to raw strings. This lets us immediately manipulate the template as a DOM element, either by passing it to an API that expects such like Backbone.View#setElement:

import Backbone from 'backbone';
import Template from './index.html';

var MyView = Backbone.View.extend({
  render() {
    this.setElement(Template());
  },
});

or by customizing the template using jQuery's APIs:

import $ from 'jquery';
import TooltipTemplate from './popdown.html';

var tooltip = TooltipTemplate();

tooltip.css({
  left: 50,
  top: 100,
});

$('body').append(tooltip);

What makes this possible is providing the module ID of jQuery (that you've bundled separately) to this plugin, using the jquery option:

var rollup = require('rollup');
var handlebars = require('rollup-plugin-handlebars-plus');

rollup({
  entry: 'main.js',
  plugins: [
    handlebars({
      jquery: 'jquery',
    }),
  ],
});

Curious about how to ID jQuery when it's a global i.e. you're not bundling it? Here's a Gist for that.

In case you want to render to a string even when using this option, all precompiled template functions have the signature (data, options, asString) so you can do:

import Template from './index.html';

console.log(Template({}, {}, true));

Contributing

We welcome pull requests! Please lint your code using the JSHint configuration in this project.

Credits

Created by Eli Skeggs and Jeff Wear.

Prior art: https://github.com/jibhaine/rollup-plugin-handlebars.

How does this differ from rollup-plugin-handlebars?

At the time of this project's development, rollup-plugin-handlebars did not support partials. This project was created to fix that and to add support for a few other features that Mixmax needed to be compatible with our use of Handlebars template pre-Rollup. We started to add partial support to rollup-plugin-handlebars, then got blocked and made a solution (this plugin) that was specific to our needs, then worked to make this more generic again (we think). We kept this as a separate project since the code had become by that point very different.