Awesome
react-template-render
npm install react-template-render
Use React as your server-side HTML templating library the same way you'd use Pug, Nunjucks, Handlebars, etc.
Not intended to be used for anything more clever that 100% server-side rendering.
const makeRender = require('react-template-render')
const root = require('path').join(__dirname, 'views')
// `render` is an object with two functions: { string(), stream() }
const render = makeRender(root, { parent: 'parent' })
// As string
// Expects files ./views/template.jsx and ./views/parent.jsx to exist
render.string('template', { foo: 42 })
// As stream
render.stream('template', { foo: 42 })
Check out the example/
folder for a minimal demo using Koa: cd example && npm install && npm start
.
Usage
The library exports a single function that takes some options and returns a renderer. The first argument is required as it specifies the directory that holds your .jsx templates.
These are the default options.
const makeRender = require('react-template-render')
const root = require('path').join(__dirname, 'views')
const render = makeRender(root, {
parent: null,
prefix: '<!doctype html>',
keyPropWarnings: true,
})
parent
is a template path (thus relative to the root) of a parent template that will receive your rendered template as a child (<parent>child</parent>
).keyPropWarnings
determines whether to print React's https://fb.me/react-warning-keys warnings in development. Setting this to false silences those warnings since they don't apply to server-side rendering.
These options can be overridden ad-hoc via the third argument to .string()
/.stream()
:
const html = render('profile', { name: 'katie' }, { parent: 'katies-layout' })
JSX compilation in Node
Simplest way I know how is with babel-register.
Put this at the top of your server's entry-point:
require('babel-register')({
presets: ['react'],
extensions: ['.jsx'],
})
npm i babel-register babel-preset-react
For info on precompiling, check out: https://github.com/babel/example-node-server#getting-ready-for-production-use
Note that if you precompile, babel will change ".jsx" extensions to ".js" which will break your require()
s if you use
require('homepage.jsx')
instead of require('homepage')
. My solution is to just leave off the extension entirely.
This way, your code will work in development since Babel resolves ".jsx" files, yet it will still work after compilation
since require
natively works with ".js" files.
Extras
This library provides some extra features:
- Implements template inheritance. Set the
{ parent: 'parent-template' }
option (or override it per-template) to choose a parent component. Your template component will be passed to it as its child:<Parent><Child /></Parent>
. The parent just has to render itschildren
prop:Parent = ({children}) => <div>{children}</div>
. - Prepends its html output with the
<!doctype html>
directive since you're unable to create that node yourself in jsx.
Example parent.jsx:
const React = require('react')
module.exports = ({ children, title }) => (
<html>
<head>
<title>{title ? title + ' - Example.com' : 'The Best Example - Example.com'}</title>
</head>
<body>{children}</body>
</html>
)
Example child.jsx:
const React = require('react')
module.exports = ({ greeting }) => <div>{greeting}, world!</div>
All togther:
const makeRender = require('react-template-render')
const root = require('path').join(__dirname, 'views')
const render = makeRender(root, { parent: 'parent' })
const html = render('child', { greeting: 'hello', title: "child's title" })
<!doctype html>
<html>
<head>
<title>child's title - Example.com</title>
</head>
<body>hello, world!</body>
</html>
Benefits of jsx on the server
- JSX tooling is pretty good. Likely better than the templating library you're already using.
- You can just
require()
code you need directly from your templates. - It's trivial to break apart a template into bite-sized components.
- It's streamable.
- You can validate the data passed into your templates with React's PropTypes.
Drawbacks
- Needs to be compiled.
- I miss some of Pug's ergonomics like being able to pleasantly write an inline
script.
/ filter when it's the simplest solution.
Koa example
A fully working Koa example can be found in the example/
folder.
cd example && npm install && npm start
But for the sake of readme skimmability, here's the gist of what the Koa middleware would look like:
const makeRenderer = require('react-template-render')
const middleware = (root, opts) => {
return async (ctx, next) => {
// For when you want to call stream() or string() yourself
//
// e.g. Maybe you want to cache some html output to disk:
//
// const html = ctx.renderer.string('template')
// await writeFile(html, { encoding: 'utf8' })
//
ctx.renderer = makeRenderer(root, opts)
// Convenience function for streaming to the response
ctx.render = (template, locals, overrides) => {
ctx.type = 'html'
ctx.body = ctx.renderer.stream(template, locals, overrides)
}
return next()
}
}
const app = new Koa()
const root = require('path').join(__dirname, 'views')
app.use(middleware(root, { parent: 'layout' }))
app.get('/users/:id', async ctx => {
const { id } = ctx.params
const user = await db.getUser(id)
ctx.assert(user, 404)
ctx.render('show-user', {
title: `Profile of ${user.uname}`,
user,
})
})
Tips and notes
- Remember that
React
must be in scope of your jsx templates. i.e. just putconst React = require('react')
as the first line of every .jsx file. - Remember to set
NODE_ENV=production
in production which will significantly speed up React rendering. - This library uses
require(template)
to load templates which means that templates are loaded synchronously and cached for the process lifetime until the server is reset. Just usenodemon --ext jsx server.js
in development to trigger server reboot. - This library uses the debug module to print out useful input. If you're having
trouble getting your template paths to load, boot your server with
DEBUG=react-template-render
to see the parent/child paths that this library is attempting to load. - Name your components with assignment, e.g.
const Component = () => <div></div>; module.exports = Component
. This way, React can tell you the name of the component in its debug/warning output. If you instead wrote this:module.exports = () => <div></div>
orconst Component = module.exports = () => <div></div>
, then React will not be able to discern the name and the component will show up unhelpfully as "Unknown".