├── .editorconfig ├── .eslintignore ├── .gitignore ├── .nvmrc ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── layout-component │ ├── server.js │ └── views │ │ ├── about.jsx │ │ ├── home.jsx │ │ └── layout.jsx ├── layout │ ├── server.js │ └── views │ │ ├── about.jsx │ │ ├── home.jsx │ │ └── layout.jsx ├── remount │ ├── client.js │ ├── components │ │ ├── app.jsx │ │ └── html.jsx │ ├── server.js │ └── webpack.js └── simple │ ├── server.js │ └── views │ └── home.jsx ├── index.js ├── package.json └── test ├── fixtures ├── layout-es6.jsx ├── layout.jsx ├── navbar.jsx ├── view-es6.jsx └── view.jsx └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # 4 space indentation 2 | [*.js] 3 | indent_style = space 4 | indent_size = 4 5 | [*.jsx] 6 | indent_style = space 7 | indent_size = 4 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | examples/remount/assets 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | examples/remount/assets 3 | package-lock.json 4 | yarn.lock 5 | .idea/ 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "14" 5 | - "16" 6 | - "18" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 Reza Akhavan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hapi-react-views 2 | 3 | A hapi view engine for React components. 4 | 5 | [![Build Status](https://img.shields.io/travis/jedireza/hapi-react-views.svg)](https://travis-ci.org/jedireza/hapi-react-views) 6 | [![Dependency Status](https://img.shields.io/david/jedireza/hapi-react-views.svg)](https://david-dm.org/jedireza/hapi-react-views) 7 | [![Peer Dependency Status](https://img.shields.io/david/peer/jedireza/hapi-react-views.svg)](https://david-dm.org/jedireza/hapi-react-views?type=peer) 8 | [![Dev Dependency Status](https://img.shields.io/david/dev/jedireza/hapi-react-views.svg)](https://david-dm.org/jedireza/hapi-react-views?type=dev) 9 | 10 | By default rendering is done using `ReactDOMServer.renderToStaticMarkup`. You 11 | can also choose to use `ReactDOMServer.renderToString`, preserving the 12 | `data-react-id` attributes so re-mounting client side is possible. 13 | 14 | 15 | ## Install 16 | 17 | ```bash 18 | $ npm install hapi-react-views 19 | ``` 20 | 21 | Note: Your project should have it's own `react` and `react-dom` dependencies 22 | installed. We depend on these via `peerDependencies`. 23 | 24 | 25 | ## Usage 26 | 27 | Configuring the server manually: 28 | 29 | ```js 30 | const Hapi = require('@hapi/hapi'); 31 | const HapiReactViews = require('hapi-react-views'); 32 | const Vision = require('@hapi/vision'); 33 | 34 | require('@babel/register')({ 35 | presets: ['@babel/preset-react', '@babel/preset-env'] 36 | }); 37 | 38 | const main = async function () { 39 | const server = Hapi.Server(); 40 | 41 | await server.register(Vision); 42 | 43 | server.views({ 44 | engines: { 45 | jsx: HapiReactViews 46 | }, 47 | compileOptions: {}, // optional 48 | relativeTo: __dirname, 49 | path: 'views' 50 | }); 51 | 52 | await server.start(); 53 | 54 | console.log(`Server is listening at ${server.info.uri}`); 55 | }; 56 | 57 | main(); 58 | ``` 59 | 60 | Note: As of `hapi-react-views` v4.x your project must register a transpiler 61 | such as [`babel`][babel]. An alternative to this is to transpile ahead of time 62 | and save the result to file. 63 | 64 | [babel]: https://github.com/babel/babel 65 | 66 | Note: As of `hapi` v9.x, your project must register the [`vision`][vision] 67 | plugin in order for the `server.views()` and `server.render()` methods to be 68 | available. 69 | 70 | [vision]: https://github.com/hapijs/vision 71 | 72 | 73 | ## API 74 | 75 | ### `server.views(options)` 76 | 77 | [Please refer to the `vision` docs on `server.views(options)` for complete 78 | details.][vision-docs] 79 | 80 | [vision-docs]: https://github.com/hapijs/vision/blob/master/API.md#serverviewsoptions 81 | 82 | We'll be focusing on the `compileOptions` property that you can include when 83 | passing `options` to `server.views`. 84 | 85 | The following `compileOptions` will customize how `hapi-react-views` works. 86 | 87 | - `compileOptions` - options object passed to the engine's compile function. 88 | Defaults to `{}`. 89 | - `doctype` - a simple string prepended to the response. Defaults to 90 | `` 91 | - `renderMethod` - the method to invoke on `ReactDOMServer` to generate our 92 | output. Available options are `renderToStaticMarkup` and `renderToString`. 93 | Defaults to `renderToStaticMarkup`. 94 | - `removeCache` - since transpilers tend to take a while to startup, we can 95 | remove templates from the require cache so we don't need to restart the 96 | server to see changes. Defaults to `'production' !== process.env.NODE_ENV`. 97 | - `removeCacheRegExp` - a `RegExp` pattern string, matching modules in 98 | require cache will be removed. Defaults to `undefined`. 99 | - `layout` - the name of the layout file to use. 100 | - `layoutPath` - the directory path of where layouts are stored. 101 | - `layoutRenderMethod` - same as `renderMethod` but used for layouts. 102 | Defaults to `renderToStaticMarkup`. 103 | 104 | You can override all these `compileOptions` at runtime. 105 | 106 | ```js 107 | const context = { name: 'Steve' }; 108 | const renderOpts = { 109 | runtimeOptions: { 110 | doctype: '', 111 | renderMethod: 'renderToString' 112 | } 113 | }; 114 | 115 | const output = await server.render('template', context, renderOpts); 116 | ``` 117 | 118 | [Please refer to `vision`'s docs on 119 | `server.render(template, context, [options], callback)` for complete details.](https://github.com/hapijs/vision/blob/master/API.md#serverrendertemplate-context-options-callback) 120 | 121 | 122 | ## Examples 123 | 124 | Before you can run the examples, you need to clone this repo and install the 125 | dependencies. 126 | 127 | ```bash 128 | $ git clone https://github.com/jedireza/hapi-react-views.git 129 | $ cd hapi-react-views 130 | $ npm install 131 | ``` 132 | 133 | ### Rendering a simple page 134 | 135 | This example renders a component as HTML output. [View the code.][ex-simple] 136 | 137 | [ex-simple]: https://github.com/jedireza/hapi-react-views/tree/master/examples/simple 138 | 139 | ```bash 140 | $ npm run simple-example 141 | ``` 142 | 143 | ### Rendering with layouts 144 | 145 | #### Wrapper style layouts 146 | 147 | This example renders components as HTML adding the idea of using wrapper 148 | layouts. The wrapping is handled by this module, so it may feel like a bit of 149 | magic since there is no direct dependency to the layout in your component 150 | views. [View the code.][ex-layouts] 151 | 152 | [ex-layouts]: https://github.com/jedireza/hapi-react-views/tree/master/examples/layout 153 | 154 | ```bash 155 | $ npm run layout-example 156 | ``` 157 | 158 | #### Component style layouts 159 | 160 | This example renders components as HTML but adds the idea of using component 161 | layouts. The component layout is a direct dependency of your view components 162 | with no magic handling by this module. [View the code.][ex-comp-layouts] 163 | 164 | [ex-comp-layouts]: https://github.com/jedireza/hapi-react-views/tree/master/examples/layout-component 165 | 166 | ```bash 167 | $ npm run layout-component-example 168 | ``` 169 | 170 | ### Remounting on the client (universal/isomorphic) 171 | 172 | This example demonstrates the idea of rendering the full page on the server and 173 | remounting the app view on the client side as a way to to create universal (aka 174 | isomorphic) applications. 175 | 176 | It uses the wrapper layout feature, making it easy for the layout to be 177 | rendered without `data-react-id` attributes and the app view to be rendered 178 | with them. [View the code.][ex-remount] 179 | 180 | [ex-remount]: https://github.com/jedireza/hapi-react-views/tree/master/examples/remount 181 | 182 | ```bash 183 | $ npm run remount-example 184 | ``` 185 | 186 | 187 | ## License 188 | 189 | MIT 190 | 191 | 192 | ## Don't forget 193 | 194 | What you create with `hapi-react-views` is more important than `hapi-react-views`. 195 | -------------------------------------------------------------------------------- /examples/layout-component/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hapi = require('@hapi/hapi'); 4 | const Vision = require('@hapi/vision'); 5 | const HapiReactViews = require('../..'); 6 | 7 | 8 | require('@babel/register')({ 9 | presets: ['@babel/preset-react', '@babel/preset-env'] 10 | }); 11 | 12 | 13 | const main = async function () { 14 | 15 | const server = Hapi.Server({ 16 | port: process.env.PORT 17 | }); 18 | 19 | await server.register(Vision); 20 | 21 | server.views({ 22 | engines: { 23 | jsx: HapiReactViews 24 | }, 25 | relativeTo: __dirname, 26 | path: 'views' 27 | }); 28 | 29 | server.route({ 30 | method: 'GET', 31 | path: '/', 32 | handler: (request, h) => { 33 | 34 | return h.view('home'); 35 | } 36 | }); 37 | 38 | server.route({ 39 | method: 'GET', 40 | path: '/about', 41 | handler: (request, h) => { 42 | 43 | return h.view('about'); 44 | } 45 | }); 46 | 47 | await server.start(); 48 | 49 | console.log(`Server is listening at ${server.info.uri}`); 50 | }; 51 | 52 | main(); 53 | -------------------------------------------------------------------------------- /examples/layout-component/views/about.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const React = require('react'); 4 | const Layout = require('./layout.jsx'); 5 | 6 | 7 | class AboutView extends React.Component { 8 | render () { 9 | 10 | return ( 11 | 12 |

About the plot device.

13 |
14 | ); 15 | } 16 | } 17 | 18 | 19 | module.exports = AboutView; 20 | -------------------------------------------------------------------------------- /examples/layout-component/views/home.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const React = require('react'); 4 | const Layout = require('./layout.jsx'); 5 | 6 | 7 | class HomeView extends React.Component { 8 | render () { 9 | 10 | return ( 11 | 12 |

Welcome to the plot device.

13 |
14 | ); 15 | } 16 | } 17 | 18 | 19 | module.exports = HomeView; 20 | -------------------------------------------------------------------------------- /examples/layout-component/views/layout.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const React = require('react'); 4 | 5 | 6 | class LayoutView extends React.Component { 7 | render () { 8 | 9 | return ( 10 | 11 | 12 | {this.props.title} 13 | 14 | 15 | {this.props.children} 16 |
17 |

18 | Home | About Us 19 |

20 | 21 | 22 | ); 23 | } 24 | } 25 | 26 | 27 | module.exports = LayoutView; 28 | -------------------------------------------------------------------------------- /examples/layout/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hapi = require('@hapi/hapi'); 4 | const Path = require('path'); 5 | const Vision = require('@hapi/vision'); 6 | const HapiReactViews = require('../..'); 7 | 8 | 9 | require('@babel/register')({ 10 | presets: ['@babel/preset-react', '@babel/preset-env'] 11 | }); 12 | 13 | 14 | const main = async function () { 15 | 16 | const server = Hapi.Server({ 17 | port: process.env.PORT 18 | }); 19 | 20 | await server.register(Vision); 21 | 22 | server.views({ 23 | engines: { 24 | jsx: HapiReactViews 25 | }, 26 | relativeTo: __dirname, 27 | path: 'views', 28 | compileOptions: { 29 | layoutPath: Path.join(__dirname, 'views'), 30 | layout: 'layout' 31 | } 32 | }); 33 | 34 | server.route({ 35 | method: 'GET', 36 | path: '/', 37 | handler: (request, h) => { 38 | 39 | return h.view('home', { title: 'Home Page' }); 40 | } 41 | }); 42 | 43 | server.route({ 44 | method: 'GET', 45 | path: '/about', 46 | handler: (request, h) => { 47 | 48 | return h.view('about', { title: 'About Page' }); 49 | } 50 | }); 51 | 52 | await server.start(); 53 | 54 | console.log(`Server is listening at ${server.info.uri}`); 55 | }; 56 | 57 | main(); 58 | -------------------------------------------------------------------------------- /examples/layout/views/about.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const React = require('react'); 4 | 5 | 6 | class AboutView extends React.Component { 7 | render () { 8 | 9 | return ( 10 |

About the plot device.

11 | ); 12 | } 13 | } 14 | 15 | 16 | module.exports = AboutView; 17 | -------------------------------------------------------------------------------- /examples/layout/views/home.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const React = require('react'); 4 | 5 | 6 | class HomeView extends React.Component { 7 | render () { 8 | 9 | return ( 10 |

Welcome to the plot device.

11 | ); 12 | } 13 | } 14 | 15 | 16 | module.exports = HomeView; 17 | -------------------------------------------------------------------------------- /examples/layout/views/layout.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const React = require('react'); 4 | 5 | 6 | class LayoutView extends React.Component { 7 | render () { 8 | 9 | return ( 10 | 11 | 12 | {this.props.title} 13 | 14 | 15 |
17 |
18 |
19 |

20 | Home | About Us 21 |

22 | 23 | 24 | ); 25 | } 26 | } 27 | 28 | 29 | module.exports = LayoutView; 30 | -------------------------------------------------------------------------------- /examples/remount/client.js: -------------------------------------------------------------------------------- 1 | /* global window document */ 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const ReactDOM = require('react-dom'); 6 | const AppComponent = require('./components/app.jsx'); 7 | 8 | 9 | const App = React.createFactory(AppComponent); 10 | const mountNode = document.getElementById('app-mount'); 11 | const serverState = window.state; 12 | 13 | 14 | ReactDOM.hydrate(App(serverState), mountNode); 15 | -------------------------------------------------------------------------------- /examples/remount/components/app.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const React = require('react'); 4 | 5 | 6 | class App extends React.Component { 7 | handleClick () { 8 | alert('Hi ' + this.props.foo); 9 | } 10 | 11 | render () { 12 | 13 | return ( 14 |
15 |

Foo: ({this.props.foo})

16 | 17 |
18 | ); 19 | } 20 | } 21 | 22 | 23 | module.exports = App; 24 | -------------------------------------------------------------------------------- /examples/remount/components/html.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const React = require('react'); 4 | 5 | 6 | class Html extends React.Component { 7 | render () { 8 | 9 | return ( 10 | 11 | 12 | Remount Example 13 | 14 | 15 |
18 |