├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── PATENTS ├── README.md ├── examples ├── dynamic │ ├── .babelrc │ ├── .gitignore │ ├── app.js │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── readme.md │ └── views │ │ ├── Content.js │ │ ├── Html.js │ │ └── main.js └── simple │ ├── app.js │ ├── package.json │ ├── public │ └── stylesheets │ │ └── style.css │ ├── routes │ ├── index.js │ └── user.js │ └── views │ ├── index.jsx │ └── layout.jsx ├── index.js ├── package.json ├── prettier.config.js ├── test ├── es5-component.jsx ├── es6-component.jsx ├── es6-flow-component.jsx └── index.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | parserOptions: { 4 | ecmaVersion: 6, 5 | ecmaFeatures: { 6 | jsx: true, 7 | }, 8 | }, 9 | plugins: ['prettier'], 10 | rules: { 11 | 'prettier/prettier': 'error', 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: yarn 3 | node_js: 4 | - 6 5 | - 8 6 | script: 7 | - yarn run test-on-travis 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.11.0 (2018-11-18) 2 | 3 | * Update to Babel v7 4 | 5 | 6 | # v0.10.5 (2018-01-15) 7 | 8 | * Switch from babel-preset-es2015 to babel-preset-env 9 | 10 | 11 | # v0.10.4 (2017-10-13) 12 | 13 | * Fixed bug preventing the use of arrays for the Express views setting 14 | 15 | 16 | # v0.10.3 (2017-10-13) 17 | 18 | * Added support for React 16 19 | * Upgrade package dependencies 20 | 21 | 22 | # v0.10.2 (2016-05-27) 23 | 24 | * Fix bug preventing view cache from being cleared in some cases 25 | 26 | 27 | # v0.10.1 (2016-04-14) 28 | 29 | * Add React v15 to peerDependencies 30 | 31 | 32 | # v0.10.0 (2016-02-08) 33 | 34 | * Update to Babel v6, using `react` and `es2015` presets by default 35 | * Clear view module cache on render errors so the server doesn't need to be restarted 36 | 37 | 38 | # v0.9.0 (2015-10-08) 39 | 40 | * Update to React v0.14, drop support for React v0.12-0.13 41 | * Update other package dependencies. 42 | 43 | # v0.8.2 (2015-07-27) 44 | 45 | * Add new `transformViews` option to make it possible to disable Babel 46 | 47 | 48 | # v0.8.1 (2015-05-28) 49 | 50 | * Fix bug where views weren't getting compiled on Windows 51 | 52 | 53 | # v0.8 (2015-05-10) 54 | 55 | * Switch to Babel for compile pipeline 56 | 57 | # v0.7.2 (2015-04-06) 58 | 59 | * Update dependency to work with React v0.12 *and* v0.13 60 | 61 | 62 | # v0.7.1 (2015-02-04) 63 | 64 | * Fix bug when calling createEngine without arguments 65 | 66 | 67 | # v0.7.0 (2015-01-07) 68 | 69 | * Add support for `stripTypes` option 70 | * Use `object-assign` instead of `lodash.merge` 71 | * Update js-beautify 72 | 73 | 74 | # v0.6.0 (2014-11-05) 75 | 76 | * Update to React v0.12. 77 | 78 | 79 | # v0.5.1 (2014-10-20) 80 | 81 | * Support common pattern of ES6 module transpilers where the component would actually be exported to `default`, not directly. 82 | 83 | 84 | # v0.5 (2014-08-14) 85 | 86 | * Move React to `peerDependencies` 87 | * Stop beautifying code without permission. There is now an option to do this. 88 | 89 | 90 | # v0.4 (2014-07-26) 91 | 92 | * Update React dependency to v0.11.1+ 93 | 94 | 95 | # v0.3 (2014-07-04) 96 | 97 | * The module cache is cleared of files matching the specified extension at the end of each render. This allows views to be modified without restarting the server. 98 | 99 | 100 | # v0.2 (2014-04-05) 101 | 102 | * `__express` and `renderFile` are gone. You must now call `createEngine(options)` which will create an engine with given options. 103 | 104 | 105 | # v0.1 (2014-03-31) 106 | 107 | * Initial release! 108 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Pull Requests 4 | 5 | We actively welcome your pull requests. 6 | 7 | 1. Fork the repo and create your branch from `master`. 8 | 2. If you've added code that should be tested, add tests 9 | 3. If you've changed APIs, update the documentation. 10 | 4. Ensure the test suite passes. 11 | 5. Make sure your code lints. 12 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 13 | 14 | ## Contributor License Agreement ("CLA") 15 | In order to accept your pull request, we need you to submit a CLA. You only need 16 | to do this once to work on any of Facebook's open source projects. 17 | 18 | Complete your CLA here: 19 | 20 | ## Issues 21 | We use GitHub issues to track public bugs. Please ensure your description is 22 | clear and has sufficient instructions to be able to reproduce the issue. 23 | 24 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe 25 | disclosure of security bugs. In those cases, please go through the process 26 | outlined on that page and do not file a public issue. 27 | 28 | ## License 29 | By contributing to MyProject, you agree that your contributions will be licensed 30 | under its BSD license. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For express-react-views software 4 | 5 | Copyright (c) 2014, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the express-react-views software distributed by Facebook, Inc. 4 | 5 | Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software 6 | ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary 8 | Claims, to make, have made, use, sell, offer to sell, import, and otherwise 9 | transfer the Software. For avoidance of doubt, no license is granted under 10 | Facebook's rights in any patent claims that are infringed by (i) modifications 11 | to the Software made by you or any third party or (ii) the Software in 12 | combination with any software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Facebook or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Facebook or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating 21 | to the Software. Notwithstanding the foregoing, if Facebook or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Facebook that is 29 | necessarily infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # express-react-views 2 | 3 | This is an [Express][express] view engine which renders [React][react] components on server. It renders static markup and *does not* support mounting those views on the client. 4 | 5 | This is intended to be used as a replacement for existing server-side view solutions, like [jade][jade], [ejs][ejs], or [handlebars][hbs]. 6 | 7 |
8 | 9 | ## Deprecated 10 | 11 | This package is no longer maintained. There are better options, though not all work with Express. I recommend [Next.js](https://nextjs.org/) or [Remix](https://remix.run/). 12 | 13 |
14 | 15 | 16 | ## Usage 17 | 18 | ```sh 19 | npm install express-react-views react react-dom 20 | ``` 21 | 22 | **Note:** You must explicitly install `react` as a dependency. Starting in v0.5, `react` is a peer dependency here. This is to avoid issues that may come when using incompatible versions. 23 | 24 | ### Add it to your app. 25 | 26 | ```js 27 | // app.js 28 | 29 | var app = express(); 30 | 31 | app.set('views', __dirname + '/views'); 32 | app.set('view engine', 'jsx'); 33 | app.engine('jsx', require('express-react-views').createEngine()); 34 | ``` 35 | 36 | ### Options 37 | 38 | Beginning with v0.2, you can now pass options in when creating your engine. 39 | 40 | option | values | default 41 | -------|--------|-------- 42 | `doctype` | any string that can be used as [a doctype](http://en.wikipedia.org/wiki/Document_type_declaration), this will be prepended to your document | `""` 43 | `beautify` | `true`: beautify markup before outputting (note, this can affect rendering due to additional whitespace) | `false` 44 | `transformViews` | `true`: use `babel` to apply JSX, ESNext transforms to views.
**Note:** if already using `babel-register` in your project, you should set this to `false` | `true` 45 | `babel` | any object containing valid Babel options
**Note:** does not merge with defaults | `{presets: ['@babel/preset-react', [ '@babel/preset-env', {'targets': {'node': 'current'}}]]}` 46 | 47 | The defaults are sane, but just in case you want to change something, here's how it would look: 48 | 49 | ```js 50 | var options = { beautify: true }; 51 | app.engine('jsx', require('express-react-views').createEngine(options)); 52 | ``` 53 | 54 | 55 | ### Views 56 | 57 | Under the hood, [Babel][babel] is used to compile your views to code compatible with your current node version, using the [react][babel-preset-react] and [env][babel-preset-env] presets by default. Only the files in your `views` directory (i.e. `app.set('views', __dirname + '/views')`) will be compiled. 58 | 59 | Your views should be node modules that export a React component. Let's assume you have this file in `views/index.jsx`: 60 | 61 | ```js 62 | var React = require('react'); 63 | 64 | function HelloMessage(props) { 65 | return
Hello {props.name}
; 66 | } 67 | 68 | module.exports = HelloMessage; 69 | ``` 70 | 71 | ### Routes 72 | 73 | Your routes would look identical to the default routes Express gives you out of the box. 74 | 75 | ```js 76 | // app.js 77 | 78 | app.get('/', require('./routes').index); 79 | ``` 80 | 81 | ```js 82 | // routes/index.js 83 | 84 | exports.index = function(req, res){ 85 | res.render('index', { name: 'John' }); 86 | }; 87 | ``` 88 | 89 | **That's it!** Layouts follow really naturally from the idea of composition. 90 | 91 | ### Layouts 92 | 93 | Simply pass the relevant props to a layout component. 94 | 95 | `views/layouts/default.jsx`: 96 | ```js 97 | var React = require('react'); 98 | 99 | function DefaultLayout(props) { 100 | return ( 101 | 102 | {props.title} 103 | {props.children} 104 | 105 | ); 106 | } 107 | 108 | module.exports = DefaultLayout; 109 | ``` 110 | 111 | `views/index.jsx`: 112 | ```js 113 | var React = require('react'); 114 | var DefaultLayout = require('./layouts/default'); 115 | 116 | function HelloMessage(props) { 117 | return ( 118 | 119 |
Hello {props.name}
120 |
121 | ); 122 | } 123 | 124 | module.exports = HelloMessage; 125 | ``` 126 | 127 | 128 | ## Questions 129 | 130 | ### What about partials & includes? 131 | 132 | These ideas don't really apply. But since they are familiar ideas to people coming from more traditional "templating" solutions, let's address it. Most of these can be solved by packaging up another component that encapsulates that piece of functionality. 133 | 134 | ### What about view helpers? 135 | 136 | I know you're used to registering helpers with your view helper (`hbs.registerHelper('something', ...))`) and operating on strings. But you don't need to do that here. 137 | 138 | * Many helpers can be turned into components. Then you can just require and use them in your view. 139 | * You have access to everything else in JS. If you want to do some date formatting, you can `require('moment')` and use directly in your view. You can bundle up other helpers as you please. 140 | 141 | ### Where does my data come from? 142 | 143 | All "locals" are exposed to your view in `this.props`. These should work identically to other view engines, with the exception of how they are exposed. Using `this.props` follows the pattern of passing data into a React component, which is why we do it that way. Remember, as with other engines, rendering is synchronous. If you have database access or other async operations, they should be done in your routes. 144 | 145 | 146 | ## Caveats 147 | 148 | * I'm saying it again to avoid confusion: this does not do anything with React in the browser. This is *only* a solution for server-side rendering. 149 | * This currently uses `require` to access your views. This means that contents are cached for the lifetime of the server process. You need to restart your server when making changes to your views. **In development, we clear your view files from the cache so you can simply refresh your browser to see changes.** 150 | * React & JSX have their own rendering caveats. For example, inline `', 17 | ], 18 | text: '', 19 | }; 20 | res.render('Html', {data: initialState}); 21 | }); 22 | 23 | var port = process.env.PORT || 3000; 24 | app.listen(port, function() { 25 | console.log('Dynamic react example listening on port ' + port); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/dynamic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamic-views", 3 | "version": "0.1.0", 4 | "description": "Example of creating a dynamic app using express-react-views", 5 | "author": "Chris Johnson ", 6 | "private": true, 7 | "scripts": { 8 | "start": 9 | "browserify -t babelify views/main.js --standalone main -o public/main.js && node app.js" 10 | }, 11 | "dependencies": { 12 | "@babel/preset-env": "^7.0.0", 13 | "@babel/preset-react": "^7.0.0", 14 | "babelify": "^10.0.0", 15 | "browserify": "^14.3.0", 16 | "express": "^4.15.3", 17 | "express-react-views": "file:../../", 18 | "react": "^16.0.0", 19 | "react-dom": "^16.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/dynamic/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reactjs/express-react-views/c20cec2e4a389300f758778b8a5f58f8b256f4b1/examples/dynamic/public/favicon.ico -------------------------------------------------------------------------------- /examples/dynamic/readme.md: -------------------------------------------------------------------------------- 1 | Dynamic react views example 2 | =========================== 3 | 4 | This example is the todo list borrowed from the 5 | [react.js main page](http://facebook.github.io/react/). 6 | We render the application server-side using express-react-views. 7 | An initial set of items has been added 8 | to illustrate populating data from the server. 9 | 10 | 11 | run it 12 | ------ 13 | 14 | npm install 15 | npm start 16 | 17 | 18 | How it works 19 | ------------ 20 | 21 | 1. Separate the page into two templates, 22 | a [static container component](views/Html.js) 23 | and a [dynamic inner component](views/Content.js). 24 | 25 | 2. Use express-react-views to render and serve the container. 26 | Server-side data can be sent via view options. 27 | 28 | 3. Make your views available client-side as javascript. 29 | Here I created a [main](views/main.js) function for bootstrapping 30 | and packaged it up using [browserify](http://browserify.org/). 31 | 32 | 4. Initialize the client-side app into the dynamic component 33 | using the same data from the server-side. 34 | This example passes the initial data to the client 35 | as the argument of the main function. 36 | Be mindful of potential XSS vulnerabilities. 37 | -------------------------------------------------------------------------------- /examples/dynamic/views/Content.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | class TodoList extends React.Component { 4 | render() { 5 | var i = 0; 6 | var createItem = function(itemText) { 7 | return
  • {itemText}
  • ; 8 | }; 9 | return ; 10 | } 11 | } 12 | 13 | class TodoApp extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | this.onChange = this.onChange.bind(this); 17 | this.handleSubmit = this.handleSubmit.bind(this); 18 | this.state = props; 19 | } 20 | onChange(e) { 21 | this.setState({text: e.target.value}); 22 | } 23 | handleSubmit(e) { 24 | e.preventDefault(); 25 | var nextItems = this.state.items.concat([this.state.text]); 26 | var nextText = ''; 27 | this.setState({items: nextItems, text: nextText}); 28 | } 29 | render() { 30 | return ( 31 |
    32 |

    TODO List

    33 | 34 |
    35 |
    36 | 43 |   44 |
    45 | 48 |
    49 |
    50 | ); 51 | } 52 | } 53 | 54 | module.exports = TodoApp; 55 | -------------------------------------------------------------------------------- /examples/dynamic/views/Html.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDOMServer = require('react-dom/server'); 3 | var Content = require('./Content'); 4 | 5 | class Html extends React.Component { 6 | render() { 7 | var data = this.props.data; 8 | 9 | // render the content as a dynamic react component 10 | var contentHtml = ReactDOMServer.renderToString(); 11 | 12 | /** 13 | * re-render the content as json, 14 | * for client-side app initialization 15 | * 16 | * NOTE on XSS prevention: 17 | * 18 | * This text will be placed into a script tag. 19 | * It cannot be escaped, 20 | * because it is intended to be raw javascript. 21 | * Were the data object to contain the string, "", 22 | * the script tag would terminate prematurely. 23 | * And two bad things would happen. 24 | * 25 | * 1. The client-side react application would not work. 26 | * 2. A second script tag could then run arbitrary javascript. 27 | * 28 | * The former sucks a little but the latter sucks a lot. 29 | * It would pwn you, game over, the site is no longer yours. 30 | * There are three ways to thwart this scenario and you should do all of them: 31 | * 32 | * 1. Scrub input from users. 33 | * Don't even let them enter data that is known to be potentially harmful. 34 | * 2. Use a templating library that renders text by default. 35 | * React does this, so YES! 36 | * 3. Whenever you have to write raw user content into the document, 37 | * block any content from breaking the current context. 38 | * 39 | * The third is what's going on with the `replace` function below. 40 | * Because we're in a script tag context, 41 | * we cannot allow the closing tag, "", in our output. 42 | * This is an old trick that breaks up the word "script" into a string contatenation. 43 | * It works here because json always uses double quotes to escape strings. 44 | * 45 | * Properly escaping user data for raw output in html is tricky business. 46 | * Whenever possible, avoid it. 47 | * If avoidance is impossible, 48 | * know what you are doing and good luck. 49 | */ 50 | var initScript = 51 | 'main(' + JSON.stringify(data).replace(/script/g, 'scr"+"ipt') + ')'; 52 | 53 | return ( 54 | 55 | 56 | 57 | 61 | 62 | 63 |
    64 | 65 |