UNPKG

@hyperapp/render

Version:

Render Hyperapp to an HTML string with SSR and Node.js streaming support

199 lines (151 loc) 7.46 kB
# <img height="24" src="https://cdn.rawgit.com/hyperapp/render/master/logo.svg"> Hyperapp Render [![npm version](https://img.shields.io/npm/v/@hyperapp/render.svg)](https://www.npmjs.com/package/@hyperapp/render) [![npm downloads](https://img.shields.io/npm/dw/@hyperapp/render.svg)](https://www.npmjs.com/package/@hyperapp/render) [![library size](https://img.shields.io/bundlephobia/minzip/@hyperapp/render.svg)](https://bundlephobia.com/result?p=@hyperapp/render) [![slack chat](https://hyperappjs.herokuapp.com/badge.svg)](https://hyperappjs.herokuapp.com 'Join us') A [Hyperapp](https://github.com/hyperapp/hyperapp) higher-order `app` that allows you to render views to an HTML string. - **User experience** — Generate HTML on the server and send the markup down on the initial request for faster page loads. Built-in [mounting](https://github.com/hyperapp/hyperapp/tree/1.2.0#mounting) feature in Hyperapp is allowing you to have a very performant first-load experience. - **Accessibility** — Allow search engines to crawl your pages for [SEO](https://en.wikipedia.org/wiki/Search_engine_optimization) purposes. - **Testability** — [Check HTML validity](https://en.wikipedia.org/wiki/Validator) and use [snapshot testing](https://facebook.github.io/jest/docs/en/snapshot-testing.html) to improve quality of your software. ## Getting Started Our first example is an interactive app from which you can generate an HTML markup. Go ahead and [try it online](https://codepen.io/frenzzy/pen/zpmRQY/left/?editors=0010). ```jsx import { h, app } from 'hyperapp' import { withRender } from '@hyperapp/render' const state = { text: 'Hello' } const actions = { setText: text => ({ text }) } const view = (state, actions) => ( <main> <h1>{state.text.trim() === '' ? '👋' : state.text}</h1> <input value={state.text} oninput={e => actions.setText(e.target.value)} /> </main> ) const main = withRender(app)(state, actions, view) main.toString() // => <main><h1>Hello</h1><input value="Hello"/></main> main.setText('World') // <= any sync or async action call main.toString() // => <main><h1>World</h1><input value="World"/></main> ``` Looking for a boilerplate? Try [Hyperapp Starter](https://github.com/frenzzy/hyperapp-starter) with pre-configured server-side rendering and many more. ## Installation Using [npm](https://www.npmjs.com/package/@hyperapp/render): ```bash npm install @hyperapp/render --save ``` Or using a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network) like [unpkg.com](https://unpkg.com/@hyperapp/render/hyperapp-render.min.js) or [jsDelivr](https://cdn.jsdelivr.net/npm/@hyperapp/render/hyperapp-render.min.js) with the following script tag: ```html <script src="https://unpkg.com/@hyperapp/render/hyperapp-render.min.js"></script> ``` You can find the library in `window.hyperappRender`. We support all ES5-compliant browsers, including Internet Explorer 9 and above, but depending on your target browsers you may need to include [polyfills](<https://en.wikipedia.org/wiki/Polyfill_(programming)>) for [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set), [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and [`Object.assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) before any other code. ## Usage The library provides a few functions which you can use depending on your needs or personal preferences. ```jsx import { withRender, renderToString, renderToStream } from '@hyperapp/render' const main = withRender(app)(state, actions, view, container) main.toString() // => <string> renderToString(<Component />) // => <string> renderToString(view, state, actions) // => <string> main.toStream() // => <stream.Readable> => <string> renderToStream(<Component />) // => <stream.Readable> => <string> renderToStream(view, state, actions) // => <stream.Readable> => <string> ``` **Note:** functions `toStream` and `renderToStream` are available in [Node.js](https://nodejs.org/en/) environment only (v6 or newer). ## Overview The library exposes three functions. The first of these is `withRender` high-order function, which adds the `toString` action to be able to render your application to an HTML string at any given time. This can be useful for server-side rendering or creating HTML snippets based on current application state. ```jsx import { h, app } from 'hyperapp' import { withRender } from '@hyperapp/render' const state = { name: 'World' } const actions = { setName: name => ({ name }) } const view = (state, actions) => <h1>Hello {state.name}</h1> const main = withRender(app)(state, actions, view) main.toString() // => <h1>Hello World</h1> main.setName('Hyperapp') // <= any sync or async action call main.toString() // => <h1>Hello Hyperapp</h1> ``` The second `renderToString` function generates HTML markup from any of your views without app initialization. That could be useful to generate HTML markup from static views. ```jsx import { renderToString } from '@hyperapp/render' const Component = ({ name }) => <h1>Hello {name}</h1> renderToString(<Component name="World" />) // => <h1>Hello World</h1> ``` The last `renderToStream` function and `toStream` equivalent return a [Readable stream](https://nodejs.org/api/stream.html#stream_readable_streams) that outputs an HTML string. The HTML output by this stream is exactly equal to what `toString` or `renderToString` would return. They are designed for more performant server-side rendering and here are examples how they could be used with [Express](http://expressjs.com/) or [Koa](http://koajs.com/): ```jsx app.get('/', (req, res) => { res.write('<!doctype html><html><head>') res.write('<title>Page</title>') res.write('</head><body><div id="app">') const main = withRender(app)(state, actions, view) const stream = main.toStream() stream.pipe(res, { end: false }) stream.on('end', () => { res.write('</div></body></html>') res.end() }) }) ``` ```jsx app.get('/', (req, res) => { res.write('<!doctype html>') const stream = renderToStream( <html> <head><title>Page</title></head> <body> <div id="app">{view(state, actions)}</div> </body> </html> ) stream.pipe(res) }) ``` ## Caveats The library automatically escapes text content and attribute values of [virtual DOM nodes](https://github.com/hyperapp/hyperapp/tree/1.2.0#view) to protect your application against [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting) attacks. However, it is not safe to allow "user input" for node names or attribute keys because the library does not reject injection attack on markup due to performance reasons. See: ```jsx const Node = 'div onclick="alert()"' renderToString(<Node title="XSS">Hi</Node>) // => <div onclick="alert()" title="XSS">Hi</div> const attributes = { 'onclick="alert()" title': 'XSS' } renderToString(<div {...attributes}>Hi</div>) // => <div onclick="alert()" title="XSS">Hi</div> const userInput = '<script>alert()</script>' renderToString(<div title="XSS" innerHTML={userInput}>Hi</div>) // => <div title="XSS"><script>alert()</script></div> ``` ## License Hyperapp Render is MIT licensed. See [LICENSE](https://github.com/hyperapp/render/blob/master/LICENSE.md).