Using Nunjucks templates with React

  Filed in: component systems

Entering the React wrapper. Thanks to Honza Soukup for the CC licnesed photo.

A lightweight React wrapper with precompiled Nunjucks

For Visual Framework component's we’re using the Nunjucks JavaScript templating engine. It's a JS-runtime and based off jinja2.

Here’s a simplified vf-button.njk template:

<a 
href="{{ button_href }}" 
class="vf-button
{%- if variant %} vf-button--{{ variant }}{% endif %}
">
{{- text | safe -}}
</a>

If you've used jinja2, Handlebars or Twig templating, you'll probably find Nunjucks familiar.

But what to do when we want to use our nice Nunjucks template in React? It’s all just JS, right?

Requirements

  • Use our *.precompiled.js templates so we don’t have to maintain a second set of templates for React. Or with minimal changes.
  • Compatible with a non-ejected Create React App. CRA limits you by not allowing customisation to the webpack config and has some other restricitions for consistency.
  • Minimal local project changes and code.
  • A “JS callback" to support client-side JS for basic UI elements like tabs. Much as you would for integrating jQuery.
  • Conceivably reusable in Angular.
  • Bonus points for using React memo components.

Step 1: Precompiling nunjukcs

While you don’t have to precompile your Nunjucks templates to use them in other systems, doing so will make them portable, faster and require a smaller JS runtime (8KB vs 20KB)

This is is fairly standard business for Nunjucks and we’ve already been doing it as part of the Visual Framework npm distributable.

For more you can follow the Nunjucks guide on precompiling.

Step 2: Common assets

To use our Nunjucks templates in react we’ll need two dependencies:

To deliver and somre reusable central logic, we made a small npm package: vf-extensions-react.

Step 3: Template wrappers and callback

We're aiming for a simple React integration and usage process.

  1. Install the npm dependencies
  2. Import the template import { VfButton } from "@visual-framework/vf-button/vf-button.react.js";
  3. Use <VfButton href="#mylink" variant="big" title="React for the VF 2.0" />
  4. Do any needed JS callback for tabs, or similar

To facilitate this, each Visual Framework component will need a small template wrapper in the monorepo; here's components/vf-button/vf-button.react.js:

// vf-button for React
// See vf-extensions-react for usage guidance
// We use vanilla JS templates for react for compatibility with create react app
// ---
import React from "react";
import Fragment from "react-dom-fragment";
import VfButtonTemplate from "./vf-button.precompiled.js";  // import templates before the nunjucks env
import { vfNunjucksEnv } from "@visual-framework/vf-extensions-react/vf-extensions-react.js";

// any JS actions needed on component insertion
class VfButtonCallback extends React.Component {
componentDidMount() {
  // console.log("any JS actions needed");
}

render() {
  return React.createElement(React.Fragment, null);
}
}

const VfButton = React.memo(({
text, button_href, theme
}) => {
return React.createElement(React.Fragment, null,
  React.createElement(Fragment, {
    dangerouslySetInnerHTML: {
      // our HTML is handled by nunjucks, this should not receive user input
      __html: vfNunjucksEnv.render("vf-button", {
        text: text, button_href: button_href, theme: theme
      })
    }
  }),
  React.createElement(VfButtonCallback, null)
);
}
);

export { VfButton };

Most of this code is from a template and we only need:

  1. pass any context properties (href, text, variant, etc)
  2. add any JS callbacks

Step 4: demo

Install the requirements:

yarn add @visual-framework/vf-extensions-react @visual-framework/vf-button

And here’s a usage example in a React template:

import { VfButton } from "@visual-framework/vf-button/vf-button.react.js"
<VfButton href="#mylink" variant="big" title="React for the VF 2.0" />


You can see all this in action in the Visual Framework’s example React app:

Notes, caveats