Using Nunjucks templates with React
A lightweight React wrapper with precompiled Nunjucks
For Visual Framework components 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:
- The
nunjucks-slim.js
runtime. It’s 8KB. - We'll need some extra help implement React's "fragments" to avoid extanious
div
s, so we've addedreact-dom-fragment
.
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.
- Install the npm dependencies
- Import the template
import { VfButton } from "@visual-framework/vf-button/vf-button.react.js";
- Use
<VfButton href="#mylink" variant="big" title="React for the VF 2.0" />
- 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:
- pass any context properties (href, text, variant, etc)
- 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
nunjucks-loader
: an interesting looking project that has some additional Nunjucks features. However it could require additional configuration to thewebpack.config.js
, which would make usage with Create React App very hard.- Similarly we'er not using Webpack to integrate our Nunjucks templates
- User input: currently not supported. We plan to use
dompurify
. - Client-side JavaScript: we know that for things like tabs we won’t be doing JavaScript in the true React way, however we’re mainly interested in content templates (cards, buttons, intros, heroes and other containers/patterns). We accept that for any high-performance DOM-updating element it should be written as a “native" React component.
- Realted discussion, projects in the Visual Framework: