- Notifications
You must be signed in to change notification settings - Fork0
A Koa2 middleware to render JSX templates with Redux support
License
artdecocode/koa2-jsx
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
yarn add koa2jsx
koa2-jsx ismiddleware forKoa2 which provides support for rendering JSX in a server application viareact-dom/server. It also usesRedux 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 atitle slice, and an action to set that property fromctx, and then print{ title } in the JSX template.
In addition to the core functionality, the package gives a minimum wireframeView 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 afterkoa2-jsx and wireframe actions were installed to include links toBootstrap 4 scripts (including jQuery) and CSS.
The module will return a single middleware function which accepts 3 arguments:reducer,View andactions. They are describe below in theExample section.
The middleware function will perform the following for each request:
- Initialise the Redux store by creating areducer;
- assignactions to the context, such as
{ setTitle(title) }becomesctx.setTitle; - wait for all other middleware and pages to resolve;and
- render
ctx.Contentif found usingreact-dom/serveras a stream with doctype html sent, using theView.
importKoafrom'koa2'importkoa2Jsxfrom'koa2-jsx'import{combineReducers}from'redux'import{connect}from'react-redux'constapp=newKoa()constView=({ title, children})=>{return(<htmllang="en"><head><title>{title}</title></head><body>{children}</body></html>)}constjsx=koa2Jsx({reducer:combineReducers({title(state=null,{ type, title}){if(type!='SET_TITLE')returnstatereturntitle}})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>awaitnext()})
When setting up middleware, ensure that thekoa2-jsx middleware function comesahead of pages so that the Redux store and render logic are initialised.
Ifctx.Content is set in downstream application middleware,<!doctype html>is written and a readable stream from React Dom'srenderToStaticNodeStream(<WebPage />) is be piped intoctx.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 usualKoa2 middleware (shown below).
The example shows how to create a reducer, actions and View for a minimum HTMLtemplate.
/* yarn example/ */importKoafrom'koa2'importkoa2Jsxfrom'koa2-jsx'importactionsfrom'./actions'importreducerfrom'./reducer'importViewfrom'./Containers/View'constapp=newKoa()constjsx=koa2Jsx({ reducer, View, actions,static:true,// ^ set to false for universal applicationspretty: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>awaitnext()})
The reducer is either a simple function or a combination of reducers created withcombineReducers from theredux package. The reducer is used during the initialisation of the middleware to create astore withcreateStore(reducer). Thestore is used in rendering as a context for theView 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'consttitle=(state=null,{ type, title})=>{if(type!='SET_TITLE')returnstatereturntitle}exportdefaultcombineReducers({ title,})
The view can be a connectedreact-redux component when actions and a reducer are used, or a pureReact component when they're omitted. It follows the same principles as when developing for a browser-sidereact-redux application, so that it accepts the state of the reducer as the first argument, withchildren property (set toctx.Content).
import{connect}from'react-redux'constView=({ title, children})=>{return(<htmllang="en"><head><title>{title}</title></head><body>{children}</body></html>)}exportdefaultconnect(state=>state)(View)
Actions map action creators to Koa's context, so that it is possible to dispatchactions fromctx to control the state and thus data which goes into thetemplate.
constactions={setTitle:title=>({type:'SET_TITLE', title}),// ^ exported as ctx.setTitle(title)}exportdefaultactions
Whether to use static rendering, i.e., without React's metadata required for hydration on the client-side. Set tofalse when building universal applications.
Results with static:
<divclass="container"><divclass="row"><divclass="col">test</div></div></div>
and without static:
<divclass="container"data-reactroot=""><divclass="row"><divclass="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.
<divclass="container"data-reactroot=""><divclass="row"><divclass="col"><p>Test</p></div></div></div>
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 aWebSite arguments. TheWebSite is aView 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'constrender=(ctx,WebSite)=>{ctx.type='html'ctx.status=200ctx.res.write('<!doctype html>\n')constmarkup=renderToStaticMarkup(WebSite)consts=prettyPrint(markup)ctx.body=s}
(b) stream renderer:
import{renderToStaticNodeStream}from'react-dom/server'conststreamRender=(ctx,WebSite)=>{ctx.type='html'ctx.status=200ctx.res.write('<!doctype html>\n')conststream=renderToStaticNodeStream(WebSite)ctx.body=stream}
The wireframe provides areducer,actions andView 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:
importkoa2Jsx,{wireframe}from'koa2-jsx'constjsx=koa2Jsx(wireframe)/* or using object destructuring */constjsx=koa2Jsx({ ...wireframe,pretty:true,})
The following template is used, which allows to set viewport, title, add links, external scripts and script and style blocks.
<htmllang="en"><head><metacharSet="utf-8"/> {viewport &&<metaname="viewport"content={viewport}/> }<title>{title}</title><!-- css, icons, manifest --> {links.map((props, i) =><linkkey={i}{...props}/> )}<!-- CSS --> {styles.map((style, i) =><stylekey={i}dangerouslySetInnerHTML={{__html:style}}/> )}</head><body> {children} {scripts.map((props, i) =><scriptkey={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.
ctx.setTitle('koa2-jsx')
<title>koa2-jsx</title>
Set the viewport.
ctx.setViewport('width=device-width, initial-scale=1, shrink-to-fit=no')
<metaname="viewport"content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
Add a link to the manifest file.
ctx.addManifest('/manifest.json')
<linkrel="manifest"href="/manifest.json"/>
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',],])
<linkhref="/icons/favicon.ico"rel="icon"/><linkhref="/icons/favicon-32x32.png"type="image/png"sizes="32x32"rel="icon"/><linkhref="/icons/apple-icon-180x180.png"type="image/png"sizes="180x180"rel="apple-touch-icon"/>
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',] :[]),],])
<scriptsrc="/js/bundle.js"></script><scriptsrc="https://code.jquery.com/jquery-3.2.1.slim.min.js"integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"crossorigin="anonymous"></script>
Add a single, or multiple style links. If integrity and origin need to bespecified, 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',] :[]),],])
<linkhref="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"crossorigin="anonymous"rel="stylesheet"/><linkhref="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 fullBootstrap 4 support to an HTML page, use the following snippet:
importkoa2Jsx,{bootstrap,wireframe}from'koa2-jsx'constjsx=koa2Jsx(wireframe)// ...app.use(jsx)app.use(bootstrap)
<!-- viewport is assigned --><metaname="viewport"content="width=device-width, initial-scale=1, shrink-to-fit=no"/><!-- css embedded --><linkhref="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"origin="anonymous"rel="stylesheet"/><!-- script tags included --><scriptsrc="https://code.jquery.com/jquery-3.2.1.slim.min.js"integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"origin="anonymous"></script><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"origin="anonymous"></script><scriptsrc="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"origin="anonymous"></script>
To start usingkoa2-jsx, you really need to be able to writeJSX syntax.During development, use@babel/register, and for production compile yourproject. You can of course create classes withcreate-react-class(seereading) and then not require babel transpilation but thepoint of this package is to writeJSX which is unfortunately is not native toNode.JS.
@babel/register is a good way to develop with koa andkoa2-jsx, howeverwhen 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" ]}Using withidio
Thekoa2-jsx middlware comes with the Koa2-based frameworkidio whichapart from other pre-installed middleware such as sessions and Mongo supportout of the box, also provides automatic routes initialisation from a givenfolder and hot route reload.
Withkoa2-jsx,idio allows touse JSX with Koa2 for templates. It'svery powerful and can be used in isomorphic applications.
- Redux Server Rendering explains how to render pages with a storeserver-side, and send initial data to the browser.
- React without ES6 talks about how to use
create-react-classto createinstances of components, that is not usingjsxsyntax.
(c)Art Deco Code 2018
About
A Koa2 middleware to render JSX templates with Redux support
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors2
Uh oh!
There was an error while loading.Please reload this page.