koa2-jsx
is middleware for Koa2
which provides support for rendering JSX in a server application via react-dom/server
. It also uses Redux
to create a store updatable with actions for a better control of what data needs to be rendered, for example, it is possible to create a reducer with a title
slice, and an action to set that property from ctx
, and then print { title }
in the JSX template.
In addition to the core functionality, the package gives a minimum wireframe View
container and actions to set page title and viewport, add external and inline scripts and styles, icons and a link to the manifest file.
Finally, there's an extra middleware function which can be used after koa2-jsx
and wireframe actions were installed to include links to Bootstrap 4
scripts (including jQuery) and CSS.
The module will return a single middleware function which accepts 3 arguments: reducer
, View
and actions
. They are describe below in the Example section.
The middleware function will perform the following for each request:
{ setTitle(title) }
becomes ctx.setTitle
;ctx.Content
if found using react-dom/server
as a stream with doctype html sent, using the View.import Koa from 'koa2' import koa2Jsx from 'koa2-jsx' import { combineReducers } from 'redux' import { connect } from 'react-redux' const app = new Koa() const View = ({ title, children }) => { return ( <html lang="en"> <head> <title>{title}</title> </head> <body> {children} </body> </html> ) } const jsx = koa2Jsx({ reducer: combineReducers({ title(state = null, { type, title }) { if (type != 'SET_TITLE') return state return title } }) actions: { setTitle(title) { return { type: 'SET_TITLE', title } } } View: connect(state => state)(View), }) app.use(jsx, async (ctx, next) => { ctx.setTitle('Welcome to the koa2-jsx world') ctx.Content = <div><h1> Hello @ there </h1></div> await next() })
When setting up middleware, ensure that the koa2-jsx
middleware function comes ahead of pages so that the Redux store and render logic are initialised.
If ctx.Content
is set in downstream application middleware, <!doctype html>
is written and a readable stream from React Dom's renderToStaticNodeStream(<WebPage />)
is be piped into ctx.body
.
koa2Jsx({
reducer: function,
View: Container,
actions: object,
static?: boolean = true,
render?: function,
}): function
This will set up the middleware function and return it. Add it as a usual Koa2
middleware (shown below).
The example shows how to create a reducer, actions and View for a minimum HTML template.
/* yarn example/ */ import Koa from 'koa2' import koa2Jsx from 'koa2-jsx' import actions from './actions' import reducer from './reducer' import View from './Containers/View' const app = new Koa() const jsx = koa2Jsx({ reducer, View, actions, static: true, // ^ set to false for universal applications pretty: false, // ^ set to true for prettified HTML output }) app.use(jsx, async (ctx, next) => { ctx.setTitle('Welcome to the koa2-jsx world') ctx.Content = <div><h1>Hello @ there</h1></div> await next() })
The reducer is either a simple function or a combination of reducers created with combineReducers
from the redux
package. The reducer is used during the initialisation of the middleware to create a store with createStore(reducer)
. The store is used in rendering as a context for the View container. This way, it's possible to pass data to the template by invoking methods on the Koa's context (see actions).
import { combineReducers } from 'redux' const title = (state = null, { type, title }) => { if (type != 'SET_TITLE') return state return title } export default combineReducers({ title, })
The view can be a connected react-redux
component when actions and a reducer are used, or a pure React
component when they're omitted. It follows the same principles as when developing for a browser-side react-redux
application, so that it accepts the state of the reducer as the first argument, with children
property (set to ctx.Content
).
import { connect } from 'react-redux' const View = ({ title, children }) => { return ( <html lang="en"> <head> <title>{title}</title> </head> <body> {children} </body> </html> ) } export default connect(state => state)(View)
Actions map action creators to Koa's context, so that it is possible to dispatch actions from ctx
to control the state and thus data which goes into the template.
const actions = { setTitle: title => ({ type: 'SET_TITLE', title }), // ^ exported as ctx.setTitle(title) } export default actions
Whether to use static rendering, i.e., without React's metadata required for hydration on the client-side. Set to false
when building universal applications.
Results with static:
<div class="container"><div class="row"><div class="col">test</div></div></div>
and without static:
<div class="container" data-reactroot=""><div class="row"><div class="col">test</div></div></div>
Prettify HTML output. This will use string rendering to get HTML before formatting, therefore it's slower to display a page.
<div class="container" data-reactroot=""> <div class="row"> <div class="col"> <p>Test</p> </div> </div> </div>
render: function(ctx: Koa.Context, WebSite: React.Component)
It is possible to pass a custom render function. You should implement your own render for more control when needed. It accepts a Koa's context and a WebSite
arguments. The WebSite
is a View
container wrapped in a state provider.
Examples below show how you can implement (a) markup renderer:
import { renderToStaticMarkup } from 'react-dom/server' import { prettyPrint } from 'html' const render = (ctx, WebSite) => { ctx.type = 'html' ctx.status = 200 ctx.res.write('<!doctype html>\n') const markup = renderToStaticMarkup(WebSite) const s = prettyPrint(markup) ctx.body = s }
(b) stream renderer:
import { renderToStaticNodeStream } from 'react-dom/server' const streamRender = (ctx, WebSite) => { ctx.type = 'html' ctx.status = 200 ctx.res.write('<!doctype html>\n') const stream = renderToStaticNodeStream(WebSite) ctx.body = stream }
The wireframe provides a reducer
, actions
and View
to be used when creating web pages. It accounts for most common use cases, such as assigning viewport and icons. To include it in your application, use:
import koa2Jsx, { wireframe } from 'koa2-jsx' const jsx = koa2Jsx(wireframe) /* or using object destructuring */ const jsx = koa2Jsx({ ...wireframe, pretty: true, })
The following template is used, which allows to set viewport, title, add links, external scripts and script and style blocks.
<html lang="en"> <head> <meta charSet="utf-8" /> {viewport && <meta name="viewport" content={viewport} /> } <title>{title}</title> <!-- css, icons, manifest --> {links.map((props, i) => <link key={i} {...props} /> )} <!-- CSS --> {styles.map((style, i) => <style key={i} dangerouslySetInnerHTML={{ __html: style }} /> )} </head> <body> {children} {scripts.map((props, i) => <script key={i} {...props} /> )} {js.map((script, i) => <script key={i} dangerouslySetInnerHTML={{ __html: script }} /> )} </body> </html>
To update the data to present in the template, the actions API is as follows.
Set title of the page.
Set the viewport.
ctx.setViewport('width=device-width, initial-scale=1, shrink-to-fit=no')
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
Add a link to the manifest file.
ctx.addManifest('/manifest.json')
<link rel="manifest" href="/manifest.json" />
addIcon(href | [[href, type, sizes, ref=icon]])
Add an icon or icons links.
ctx.addIcon('/icons/favicon.ico') ctx.addIcon([ [ '/icons/favicon-32x32.png', 'image/png', '32x32', ], [ '/icons/apple-icon-180x180.png', 'image/png', '180x180', 'apple-touch-icon', ], ])
<link href="/icons/favicon.ico" rel="icon" /> <link href="/icons/favicon-32x32.png" type="image/png" sizes="32x32" rel="icon" /> <link href="/icons/apple-icon-180x180.png" type="image/png" sizes="180x180" rel="apple-touch-icon" />
addScript(src | [[src, integrity, crossOrigin]])
Add a single, or multiple script tags. If integrity and origin need to be used, an array must be passed.
ctx.addScript('/js/bundle.js') ctx.addScript([ [ 'https://code.jquery.com/jquery-3.2.1.slim.min.js', ...(process.env.NODE_ENV == 'production' ? [ 'sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN', 'anonymous', ] : []), ], ])
<script src="/js/bundle.js"></script> <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
addCss(href | [[href, integrity, crossOrigin]])
Add a single, or multiple style links. If integrity and origin need to be specified, an array must be passed.
ctx.addCss('https://fonts.googleapis.com/css?family=Roboto:700&effect=anaglyph|3d-float') ctx.addCss([ [ 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css', ...(process.env.NODE_ENV == 'production' ? [ 'sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm', 'anonymous', ] : []), ], ])
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css?family=Roboto:700&effect=anaglyph|3d-float" rel="stylesheet" />
Add a style block to the HTML.
ctx.addStyle('h1 { font-family: \'Roboto\', sans-serif; }')
<style> h1 { font-family: 'Roboto', sans-serif; } </style>
Add a block of JS code.
ctx.addJs('$(".alert").alert()')
<script> $(".alert").alert() </script>
To include the full Bootstrap 4
support to an HTML page, use the following snippet:
import koa2Jsx, { bootstrap, wireframe } from 'koa2-jsx' const jsx = koa2Jsx(wireframe) // ... app.use(jsx) app.use(bootstrap)
<!-- viewport is assigned --> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <!-- css embedded --> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" origin="anonymous" rel="stylesheet" /> <!-- script tags included --> <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" origin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" origin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" origin="anonymous"></script>
To start using koa2-jsx
, you really need to be able to write JSX
syntax. During development, use @babel/register
, and for production compile your project. You can of course create classes with create-react-class
(see reading) and then not require babel transpilation but the point of this package is to write JSX
which is unfortunately is not native to Node.JS.
@babel/register
is a good way to develop with koa and koa2-jsx
, however when using in production, it is recommended to build the app.
require('@babel/register') require('.')
To build JSX code, you can use the following .babelrc
snippet:
{ "plugins": [ "react-require", "@babel/plugin-syntax-object-rest-spread", "@babel/plugin-transform-modules-commonjs" ], "presets": [ "@babel/preset-react" ] }
The koa2-jsx
middlware comes with the Koa2-based framework idio
which apart from other pre-installed middleware such as sessions and Mongo support out of the box, also provides automatic routes initialisation from a given folder and hot route reload.
With koa2-jsx
, idio
allows to use JSX with Koa2 for templates. It's very powerful and can be used in isomorphic applications.
create-react-class
to create instances of components, that is not using jsx
syntax.(c) Art Deco Code 2018
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4