Render and inject Pug templates inside React
TL; DR
To inject pug templates into React, compile the said pug template into an HTML string on node server side with compileFile
and pass it down to React. Be careful with a few gotchas, such as relative pathing and potential client side JavaScript conflicts.
Context of the problem
This is a practical problem I encountered at work: let’s say you have a legacy application written in pug (previously known as jade) template engine, a common choice of building web apps back in 2013 ~ 2015. You are trying to migrate this frontend codebase to a modern stack: React, webpack, all that good stuff. How would you approach it?
To start off, we can’t just delete the entire pug stack and rewrite from scratch overnight; we have to migrate the app gradually, one component at a time. This is the reality for most migration projects.
Proposed approaches
There are 2 approaches to handle a pug-to-React frontend migration.
The first approach is to keep pug as the foundation of the web pages. You build React components individually and inject them into these legacy pug-based pages.
Pros: relatively easy to do, as React supports this fragmented rendering mechanism natively. Minimal disruption to how the legacy pages work before.
Cons: you have to deal multiple React roots. You’ll need to go the extra mile to avoid excessive code repetition and will have a hard time if these new React components that live across different roots need to talk to each other.
The second approach is to start anew, use React as the foundation of the page. This means to build React pages from ground up and inject existing legacy pug-based components into React.
Pros: this is a great opportunity to wipe out tech debt and start anew. React components should work flawlessly under a unified React root.
Cons: effort will be higher, as you have to do all the ground work of constructing a webpage again. You also have to spend some time injecting fragments of pug code into React, which I will share my experiences in the next section.
Render pug templates inside React
The first step of rendering pug templates inside React is to compile the said pug template into an HTML string on the node.js server side, usually in your controller where you handle the data assembly.
The pug API reference provides two handy functions: compile
and compileFile
.
If your pug template is simple and concise enough to be contained in a string, feel free to use compile
.
I personally recommend using compileFile
which compiles from a separate .pug
template file. Having your templates outside of your controllers is for good code encapsulation and reuse. If the part of pug template you want to inject into React is inside a parent template, this is a great time to refactor and isolate it.
The pug Github repo provides a minimal example of how this works:
var pug = require('../'); var locals = { users: { tj: { age: 23, email: 'tj@vision-media.ca', isA: 'human' }, tobi: { age: 1, email: 'tobi@is-amazing.com', isA: 'ferret' } } }; var fn = pug.compileFile(__dirname + '/dynamicscript.pug'); console.log(fn(locals));
One gotcha is the file path in the compileFile
function: it takes only the relative path. This could be a problem if your controller code is many folders and layers away from your pug template file. This is a common complaint and one of the better ways to deal with this is to pass the basedir
parameter into the options, as Forbes Lindesay suggested here. This is especially handy if you have to call this function multiple times.
The next step is to pass the compiled HTML string into the React props. Let’s use the dangerouslySetInnerHTML to set the compiled HTML into a JSX div.
Run your server and reload the page. Voila! You have successfully rendered the original pug template and injected it into React. Not that bad, isn’t it?
Conclusion
In my opinion, pug as a template engine has far less complexity than the lifecycle delicacies of React, so I tend to let pug compromise for React when it comes to migration and coexistence. Ever since I figured out the server side compiling process of pug, this has been my go-to approach when it comes to migrate legacy pug frontend code into React.
This rendering mechanism fits the isomorphic nature of React as well, since the HTML string is unchanged between client and server contexts.
You should not need to do anything special for client side JavaScript that was originally running on the pug template either, since all the DOM structure and attributes are almost faithfully preserved with compileFile
. The only delta we introduced is the div in JSX that’s necessary for the dangerouslySetInnerHTML to happen, so be careful if you have client side JavaScript that depends on this exact DOM structure.