Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/artdecocode/koa2-jsx

A Koa2 middleware to render JSX templates with Redux support
https://github.com/artdecocode/koa2-jsx

jsx koa2 koa2-middleware react

Last synced: 19 days ago
JSON representation

A Koa2 middleware to render JSX templates with Redux support

Awesome Lists containing this project

README

        

# koa2-jsx

[![npm version](https://badge.fury.io/js/koa2-jsx.svg)](https://badge.fury.io/js/koa2-jsx)

```bash
yarn add koa2jsx
```

`koa2-jsx` is _middleware_ for [`Koa2`][3] 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.

## API

The module will return a single middleware function which accepts 3 arguments: `reducer`, `View` and `actions`. They are describe below in the [_Example section_](#example).

The middleware function will perform the following for each request:

1. Initialise the Redux store by creating a **reducer**;
1. assign **actions** to the context, such as `{ setTitle(title) }` becomes `ctx.setTitle`;
1. wait for all other middleware and pages to resolve; _and_
1. render `ctx.Content` if found using `react-dom/server` as a stream with doctype html sent, using the **View**.

```jsx
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 (


{title}


{children}


)
}

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 =

Hello @ there


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, ``
is written and a readable stream from React Dom's
`renderToStaticNodeStream()` 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).

### Example

The example shows how to create a reducer, actions and View for a minimum HTML
template.

```js
/* 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 =

Hello @ there


await next()
})
```

### reducer

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).

```js
import { combineReducers } from 'redux'

const title = (state = null, { type, title }) => {
if (type != 'SET_TITLE') return state
return title
}

export default combineReducers({
title,
})
```

### View

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`).

```js
import { connect } from 'react-redux'

const View = ({ title, children }) => {
return (


{title}


{children}


)
}

export default connect(state => state)(View)
```

### actions

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.

```js
const actions = {
setTitle: title => ({ type: 'SET_TITLE', title }),
// ^ exported as ctx.setTitle(title)
}

export default actions
```

### `static: bool = true`

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:

```html

test

```

and without static:

```html

test

```

### `pretty: bool = false`

Prettify HTML output. This will use string rendering to get HTML before formatting, therefore it's slower to display a page.

```html




Test





```

### `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:

```js
import { renderToStaticMarkup } from 'react-dom/server'
import { prettyPrint } from 'html'

const render = (ctx, WebSite) => {
ctx.type = 'html'
ctx.status = 200
ctx.res.write('\n')
const markup = renderToStaticMarkup(WebSite)
const s = prettyPrint(markup)
ctx.body = s
}
```

(b) stream renderer:

```js
import { renderToStaticNodeStream } from 'react-dom/server'

const streamRender = (ctx, WebSite) => {
ctx.type = 'html'
ctx.status = 200
ctx.res.write('\n')
const stream = renderToStaticNodeStream(WebSite)
ctx.body = stream
}
```

## Wireframe

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:

```js
import koa2Jsx, { wireframe } from 'koa2-jsx'

const jsx = koa2Jsx(wireframe)

/* or using object destructuring */

const jsx = koa2Jsx({
...wireframe,
pretty: true,
})
```

### Template

The following template is used, which allows to set viewport, title, add links, external scripts and script and style blocks.

```html



{viewport &&

}

{title}


{links.map((props, i) =>

)}

{styles.map((style, i) =>

)}


{children}

{scripts.map((props, i) =>

)}
{js.map((script, i) =>

)}

```

## Actions

To update the data to present in the template, the actions API is as follows.

### `setTitle(title)`

Set title of the page.

```js
ctx.setTitle('koa2-jsx')
```

```html
koa2-jsx
```

### `setViewport(viewport)`

Set the viewport.

```js
ctx.setViewport('width=device-width, initial-scale=1, shrink-to-fit=no')
```

```html

```

### `addManifest(href)`

Add a link to the manifest file.

```js
ctx.addManifest('/manifest.json')
```

```html

```

### `addIcon(href | [[href, type, sizes, ref=icon]])`

Add an icon or icons links.

```js
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',
],
])
```

```html

```

### `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.

```js
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',
] : []),
],
])
```

```html

```

### `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.

```js
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',
] : []),
],
])
```

```html

```

### `addStyle(style)`

Add a style block to the HTML.

```js
ctx.addStyle('h1 { font-family: \'Roboto\', sans-serif; }')
```

```html

h1 { font-family: 'Roboto', sans-serif; }

```

### `addJs(js)`

Add a block of JS code.

```js
ctx.addJs('$(".alert").alert()')
```

```html

$(".alert").alert()

```

## Bootstrap

To include the full `Bootstrap 4` support to an HTML page, use the following snippet:

```js
import koa2Jsx, { bootstrap, wireframe } from 'koa2-jsx'

const jsx = koa2Jsx(wireframe)

// ...
app.use(jsx)
app.use(bootstrap)
```

```html

```

## Babel

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](#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_.

### Development

`@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.

```js
require('@babel/register')
require('.')
```

### .babelrc

To build JSX code, you can use the following `.babelrc` snippet:

```json
{
"plugins": [
"react-require",
"@babel/plugin-syntax-object-rest-spread",
"@babel/plugin-transform-modules-commonjs"
],
"presets": [
"@babel/preset-react"
]
}
```

## Using with [idio][2]

The `koa2-jsx` middlware comes with the Koa2-based framework [`idio`][2] 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][2] for templates. It's
very powerful and can be used in isomorphic applications.

## Reading

- [Redux Server Rendering][4] explains how to render pages with a store
server-side, and send initial data to the browser.
- [React without ES6][5] talks about how to use `create-react-class` to create
instances of components, that is not using `jsx` syntax.

---

(c) [Art Deco Code][1] 2018

[1]: https://adc.sh
[2]: https://idio.cc
[3]: http://koajs.com
[4]: https://redux.js.org/recipes/server-rendering
[5]: https://reactjs.org/docs/react-without-es6.html