├── .autod.conf.js ├── .editorconfig ├── .gitignore ├── .travis.yml ├── History.md ├── README.md ├── example ├── app.js ├── public │ ├── css │ │ └── main.css │ └── js │ │ ├── bundle.js │ │ ├── components │ │ ├── content.jsx │ │ ├── create.jsx │ │ └── item.jsx │ │ └── main.js ├── views │ ├── index.jsx │ └── layout.jsx └── webpack.config.js ├── index.js ├── package.json └── test ├── index.test.js └── support └── views └── home.jsx /.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | prefix: '^', 6 | exclude: [ 7 | 'test/fixtures' 8 | ], 9 | devdep: [ 10 | 'autod', 11 | 'mocha', 12 | 'istanbul', 13 | 'should', 14 | 'babel-register', 15 | 'babel-preset-es2015', 16 | 'babel-preset-react' 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | 9 | coverage.html 10 | coverage/ 11 | cov/ 12 | 13 | node_modules 14 | 15 | dump.rdb 16 | .DS_Store 17 | 18 | test.js 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '4' 5 | - '6' 6 | install: 7 | - npm i npminstall && npminstall 8 | script: 9 | - npm run test-cov 10 | after_script: 11 | - npminstall codecov && codecov 12 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 3.0.0 / 2016-07-27 3 | ================== 4 | 5 | * test: use babel 6 6 | * Bump react dependencies to v15. (#19) 7 | 8 | 2.0.0 / 2015-12-22 9 | ================== 10 | 11 | * refactor: remove babel from module's core 12 | 13 | 1.1.0 / 2015-11-12 14 | ================== 15 | 16 | * Allow choice of React rendering method 17 | * Upgrade react 0.13.3 -> 0.14.2 18 | 19 | 1.0.2 / 2015-06-10 20 | ================== 21 | 22 | * fix options.views 23 | 24 | 1.0.1 / 2015-06-08 25 | ================== 26 | 27 | * fix default babel.only 28 | 29 | 1.0.0 / 2015-06-08 30 | ================== 31 | 32 | * init 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | koa-react-view 2 | --------------- 3 | 4 | [![NPM version][npm-image]][npm-url] 5 | [![build status][travis-image]][travis-url] 6 | [![Test coverage][coveralls-image]][coveralls-url] 7 | [![David deps][david-image]][david-url] 8 | [![node version][node-image]][node-url] 9 | 10 | [npm-image]: https://img.shields.io/npm/v/koa-react-view.svg?style=flat-square 11 | [npm-url]: https://npmjs.org/package/koa-react-view 12 | [travis-image]: https://img.shields.io/travis/koajs/react-view.svg?style=flat-square 13 | [travis-url]: https://travis-ci.org/koajs/react-view 14 | [coveralls-image]: https://img.shields.io/coveralls/koajs/react-view.svg?style=flat-square 15 | [coveralls-url]: https://coveralls.io/r/koajs/react-view?branch=master 16 | [david-image]: https://img.shields.io/david/koajs/react-view.svg?style=flat-square 17 | [david-url]: https://david-dm.org/koajs/react-view 18 | [node-image]: https://img.shields.io/badge/node.js-%3E=_0.12-green.svg?style=flat-square 19 | [node-url]: http://nodejs.org/download/ 20 | 21 | A Koa view engine which renders React components on server. 22 | 23 | ## Installation 24 | 25 | ```bash 26 | $ npm install koa-react-view 27 | ``` 28 | 29 | ## Usage 30 | 31 | ```js 32 | var react = require('koa-react-view'); 33 | var path = require('path'); 34 | var koa = require('koa'); 35 | 36 | var app = koa(); 37 | 38 | var viewpath = path.join(__dirname, 'views'); 39 | var assetspath = path.join(__dirname, 'public'); 40 | 41 | react(app, { 42 | views: viewpath 43 | }); 44 | 45 | app.use(function* () { 46 | this.render(home, {foo: 'bar'}); 47 | }); 48 | 49 | ``` 50 | 51 | This module no longer includes the [Babel] runtime, as that prevented developers 52 | from using the runtime on the server outside of the scope of this module. Additionally, 53 | Babel recommends that the polyfill is only included by the parent app to avoid these 54 | conflicts. If you'd like to use JSX, ES6, or other features that require transpiling, 55 | you can include Babel in your project directly. See [example]. 56 | 57 | ### Options 58 | 59 | option | values | default 60 | -------|--------|-------- 61 | `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 | `""` 62 | `beautify` | `true`: beautify markup before outputting (note, this can affect rendering due to additional whitespace) | `false` 63 | `views` | the root directory of view files | `path.join(__dirname, 'views')` 64 | `extname` | the default view file's extname | `jsx` 65 | `writeResp` | `true`: writes the body response automatically | `true` 66 | `cache` | `true`: cache all the view files | `process.env.NODE_ENV === 'production'` 67 | `internals` | `true`: include React internals in output | `false` 68 | 69 | ### renderToString vs renderToStaticMarkup 70 | 71 | React provides two ways to render components server-side: 72 | 73 | - [ReactDOMServer.renderToStaticMarkup](https://facebook.github.io/react/docs/top-level-api.html#reactdomserver.rendertostaticmarkup) strips out all the React internals, reducing the size of the output. Best for static sites. 74 | 75 | - [ReactDOMServer.renderToString](https://facebook.github.io/react/docs/top-level-api.html#reactdomserver.rendertostring) maintains React internals, allowing for client-side React to process the rendered markup very speedily. Best for an initial server-side rendering of a client-side application. 76 | 77 | By default, the `ReactDOMServer.renderToStaticMarkup` method will be used. It is possible to use `ReactDOMServer.renderToString` instead (and maintain the React internals) by setting the `internals` option to `true`, or by setting the third parameter of `this.render` to `true` on a case-by-case basis. 78 | 79 | ### `ctx.state` 80 | 81 | `koa-react-view` support [ctx.state](https://github.com/koajs/koa/blob/master/docs/api/context.md#ctxstate) in koa. 82 | 83 | ### [example](example) 84 | 85 | ### License 86 | 87 | MIT 88 | 89 | [Babel]: http://babeljs.io/ 90 | [example]: https://github.com/koajs/react-view/blob/master/example/app.js#L25 91 | -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * koa-react-view - example/app.js 3 | * MIT Licensed 4 | */ 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var staticCache = require('koa-static-cache'); 13 | var register = require('babel-register'); 14 | var react = require('..'); 15 | var path = require('path'); 16 | var koa = require('koa'); 17 | 18 | var app = koa(); 19 | 20 | var viewpath = path.join(__dirname, 'views'); 21 | var assetspath = path.join(__dirname, 'public'); 22 | 23 | react(app, { views: viewpath }); 24 | 25 | // imports babel runtime for JSX views, warning: live transpiling 26 | // best to precompile in production deploys for perf + reliability 27 | register({ 28 | presets: [ 'es2015', 'react' ], 29 | extensions: [ '.jsx' ], 30 | }); 31 | 32 | app.use(staticCache(assetspath)); 33 | 34 | app.use(function* () { 35 | this.render('index', { 36 | title: 'List', 37 | list: [ 38 | 'hello koa', 39 | 'hello react' 40 | ] 41 | }); 42 | }); 43 | 44 | app.listen(3000); 45 | console.log('server start listen at 3000'); 46 | -------------------------------------------------------------------------------- /example/public/css/main.css: -------------------------------------------------------------------------------- 1 | li { 2 | margin: 10px; 3 | } 4 | .item { 5 | font-size: 18px; 6 | } 7 | 8 | .remove { 9 | margin-left: 10px; 10 | cursor: pointer; 11 | color: #aaa; 12 | } 13 | 14 | .create-box input { 15 | height: 20px; 16 | width: 200px; 17 | margin: 20px; 18 | } 19 | -------------------------------------------------------------------------------- /example/public/js/components/content.jsx: -------------------------------------------------------------------------------- 1 | var Create = require('./create'); 2 | var Item = require('./item'); 3 | var React = require('react'); 4 | 5 | var Content = React.createClass({ 6 | propTypes: { 7 | list: React.PropTypes.array 8 | }, 9 | 10 | getInitialState: function () { 11 | return { 12 | list: this.props.list 13 | }; 14 | }, 15 | 16 | render: function() { 17 | return ( 18 |
19 | 24 | 25 |
26 | ); 27 | }, 28 | 29 | add: function (content) { 30 | this.setState({ 31 | list: this.state.list.concat(content) 32 | }); 33 | }, 34 | 35 | remove: function (index) { 36 | console.log(index, this.state.list) 37 | this.state.list.splice(index, 1); 38 | this.setState({ 39 | list: this.state.list 40 | }); 41 | } 42 | }); 43 | 44 | module.exports = Content; 45 | -------------------------------------------------------------------------------- /example/public/js/components/create.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var ENTER_KEY_CODE = 13; 4 | 5 | var Create = React.createClass({ 6 | propTypes: { 7 | add: React.PropTypes.func, 8 | }, 9 | 10 | getInitialState: function () { 11 | return { 12 | value: '' 13 | }; 14 | }, 15 | 16 | render: function() { 17 | return ( 18 |
19 | 25 |
26 | ); 27 | }, 28 | 29 | _onKeyDown: function (event) { 30 | if (event.keyCode === ENTER_KEY_CODE) this.save(); 31 | }, 32 | 33 | _onChange: function (event) { 34 | this.state.value = event.target.value; 35 | this.setState({ 36 | value: event.target.value 37 | }); 38 | }, 39 | 40 | save: function () { 41 | if (!this.state.value) return; 42 | this.props.add(this.state.value); 43 | this.setState({ 44 | value: '' 45 | }); 46 | } 47 | }); 48 | 49 | module.exports = Create; 50 | -------------------------------------------------------------------------------- /example/public/js/components/item.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var Item = React.createClass({ 4 | 5 | propTypes: { 6 | remove: React.PropTypes.func, 7 | content: React.PropTypes.string 8 | }, 9 | 10 | render: function() { 11 | return( 12 |
  • 13 | {this.props.content} 14 | x 15 |
  • 16 | ); 17 | } 18 | }); 19 | 20 | module.exports = Item; 21 | -------------------------------------------------------------------------------- /example/public/js/main.js: -------------------------------------------------------------------------------- 1 | var Content = require('./components/content'); 2 | var unescapeHtml = require('unescape-html'); 3 | var React = require('react'); 4 | 5 | function initApp() { 6 | var container = document.getElementById('content'); 7 | var list = unescapeHtml(window.__list__); 8 | list = JSON.parse(list); 9 | // reuse server side render result 10 | React.render( 11 | , 12 | container 13 | ); 14 | } 15 | 16 | initApp(); 17 | -------------------------------------------------------------------------------- /example/views/index.jsx: -------------------------------------------------------------------------------- 1 | var Content = require('../public/js/components/content'); 2 | var escapeHtml = require('escape-html'); 3 | var Layout = require('./layout'); 4 | var React = require('react'); 5 | var ReactDOMServer = require('react-dom/server'); 6 | 7 | var index = React.createClass({ 8 | propTypes: { 9 | title: React.PropTypes.string, 10 | list: React.PropTypes.array 11 | }, 12 | 13 | render: function() { 14 | // pass data to client side js 15 | // xss!!! 16 | var dataScript = `window.__list__ = '${escapeHtml(JSON.stringify(this.props.list))}';`; 17 | // render as a dynamic react component 18 | var contentString = ReactDOMServer.renderToString(); 19 | 20 | return ( 21 | 22 |

    {this.props.title}

    23 |
    24 |
    25 | 26 |
    27 | ); 28 | } 29 | }); 30 | 31 | module.exports = index; 32 | -------------------------------------------------------------------------------- /example/views/layout.jsx: -------------------------------------------------------------------------------- 1 | 2 | var React = require('react'); 3 | 4 | var Layout = React.createClass({ 5 | propTypes: { 6 | title: React.PropTypes.string 7 | }, 8 | 9 | render: function() { 10 | return ( 11 | 12 | 13 | {this.props.title} 14 | 15 | 16 | 17 | {this.props.children} 18 | 19 | 20 | 21 | ); 22 | } 23 | }); 24 | 25 | module.exports = Layout; 26 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './public/js/main.js', 3 | output: { 4 | path: './public/js', 5 | filename: 'bundle.js' 6 | }, 7 | resolve: { 8 | extensions: ['', '.js', '.jsx'] 9 | }, 10 | module: { 11 | loaders: [{ 12 | test: /\.jsx?$/, 13 | loader: 'babel-loader!jsx-loader?harmony' 14 | }] 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * koa-react-view - index.js 3 | * MIT Licensed 4 | */ 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var ReactDOMServer = require('react-dom/server'); 13 | var beautifyHTML = require('js-beautify').html; 14 | var assert = require('assert'); 15 | var copy = require('copy-to'); 16 | var React = require('react'); 17 | var path = require('path'); 18 | 19 | var defaultOptions = { 20 | doctype: '', 21 | beautify: false, 22 | cache: process.env.NODE_ENV === 'production', 23 | extname: 'jsx', 24 | writeResp: true, 25 | views: path.join(__dirname, 'views'), 26 | internals: false 27 | }; 28 | 29 | module.exports = function (app, _options) { 30 | _options = _options || {}; 31 | 32 | var options = {}; 33 | copy(_options).and(defaultOptions).to(options); 34 | options.views = path.resolve(options.views); 35 | options.extname = options.extname.replace(/^\.?/, '.'); 36 | 37 | // match function for cache clean 38 | var match = createMatchFunction(options.views); 39 | 40 | /** 41 | * render react template to html 42 | * 43 | * @param {String} filename 44 | * @param {Object} _locals 45 | * @return {String} 46 | */ 47 | app.context.render = function(filename, _locals, internals) { 48 | // resolve filepath 49 | var filepath = path.join(options.views, filename); 50 | if (filepath.indexOf(options.views) !== 0) { 51 | var err = new Error('Cannot find module ' + filename); 52 | err.code = 'REACT'; 53 | throw err; 54 | } 55 | if (!path.extname(filepath)) filepath += options.extname; 56 | 57 | if (typeof _locals === 'boolean') { 58 | internals = _locals; 59 | _locals = {}; 60 | } 61 | internals = internals !== undefined 62 | ? internals 63 | : options.internals; 64 | 65 | var render = internals 66 | ? ReactDOMServer.renderToString 67 | : ReactDOMServer.renderToStaticMarkup; 68 | 69 | var locals = {}; 70 | // merge koa state 71 | merge(locals, this.state || {}); 72 | merge(locals, _locals); 73 | 74 | var markup = options.doctype || ''; 75 | try { 76 | var component = require(filepath); 77 | // Transpiled ES6 may export components as { default: Component } 78 | component = component.default || component; 79 | markup += render(React.createElement(component, locals)); 80 | } catch (err) { 81 | err.code = 'REACT'; 82 | throw err; 83 | } finally { 84 | if (!options.cache) { 85 | cleanCache(match); 86 | } 87 | } 88 | 89 | if (options.beautify) { 90 | // NOTE: This will screw up some things where whitespace is important, and be 91 | // subtly different than prod. 92 | markup = beautifyHTML(markup); 93 | } 94 | 95 | var writeResp = locals.writeResp === false 96 | ? false 97 | : (locals.writeResp || options.writeResp); 98 | if (writeResp) { 99 | this.type = 'html'; 100 | this.body = markup; 101 | } 102 | 103 | return markup; 104 | }; 105 | }; 106 | 107 | 108 | /** 109 | * merge source to taget 110 | * 111 | * @param {Object} target 112 | * @param {Object} source 113 | * @return {Object} 114 | */ 115 | function merge(target, source) { 116 | for (var key in source) { 117 | target[key] = source[key]; 118 | } 119 | 120 | return target; 121 | } 122 | 123 | /** 124 | * create a match function for clean cache 125 | * 126 | * @param {Mixed} input 127 | * @return {Function} 128 | */ 129 | function createMatchFunction(input) { 130 | if (!Array.isArray(input)) input = [input]; 131 | 132 | input = input.map(function (item) { 133 | return typeof item === 'string' 134 | ? new RegExp('^' + item) 135 | : item; 136 | }); 137 | 138 | return function match(file) { 139 | for (var i = 0; i < input.length; i++) { 140 | if (input[i].test(file)) return true; 141 | } 142 | }; 143 | } 144 | 145 | /** 146 | * Remove all files from the module cache that are in the view folder. 147 | * 148 | * @param {Function} match 149 | */ 150 | function cleanCache(match) { 151 | Object.keys(require.cache).forEach(function(module) { 152 | if (match(require.cache[module].filename)) { 153 | delete require.cache[module]; 154 | } 155 | }); 156 | } 157 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-react-view", 3 | "version": "3.0.0", 4 | "description": "server side react render for koa", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "NODE_ENV=test mocha --require should test/*.test.js", 8 | "test-cov": "NODE_ENV=test istanbul cover ./node_modules/.bin/_mocha -- -u exports --require should test/*.test.js", 9 | "autod": "autod" 10 | }, 11 | "files": [ 12 | "index.js" 13 | ], 14 | "keywords": [], 15 | "author": { 16 | "name": "dead-horse", 17 | "email": "dead_horse@qq.com", 18 | "url": "http://deadhorse.me" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:koajs/react-view" 23 | }, 24 | "license": "MIT", 25 | "dependencies": { 26 | "copy-to": "^2.0.1", 27 | "js-beautify": "^1.6.3", 28 | "react": "^15.2.1", 29 | "react-dom": "^15.2.1" 30 | }, 31 | "devDependencies": { 32 | "autod": "^2.6.1", 33 | "babel-preset-es2015": "^6.9.0", 34 | "babel-preset-react": "^6.11.1", 35 | "babel-register": "^6.11.6", 36 | "egg-ci": "^1.0.3", 37 | "escape-html": "^1.0.3", 38 | "istanbul": "^0.4.4", 39 | "istanbul-harmony": "~0.3.12", 40 | "koa": "^1.2.1", 41 | "koa-static-cache": "^3.1.7", 42 | "mocha": "^2.5.3", 43 | "should": "^10.0.0", 44 | "supertest": "^1.2.0", 45 | "unescape-html": "^1.0.0", 46 | "webpack": "~1.9.10" 47 | }, 48 | "engine": { 49 | "node": ">=0.12" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * koa-react-view - test/index.test.js 3 | * Copyright(c) 2015 dead_horse 4 | * MIT Licensed 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var register = require('babel-register'); 14 | var request = require('supertest'); 15 | var copy = require('copy-to'); 16 | var path = require('path'); 17 | var react = require('..'); 18 | var koa = require('koa'); 19 | 20 | register({ 21 | presets: [ 'es2015', 'react' ], 22 | extensions: [ '.jsx' ], 23 | }); 24 | 25 | describe('koa-react-view', function () { 26 | describe('render', function () { 27 | it('should render html ok', function (done) { 28 | var app = App(); 29 | app.use(function*() { 30 | this.render('home', {title: 'home'}); 31 | }); 32 | 33 | request(app.listen()) 34 | .get('/') 35 | .expect(200) 36 | .expect('
    home
    ', done); 37 | }); 38 | 39 | it('should support ReactDOMServer.renderToString with locals', function (done) { 40 | var app = App(); 41 | app.use(function*() { 42 | this.render('home', { }, true); 43 | }); 44 | 45 | request(app.listen()) 46 | .get('/') 47 | .expect(200) 48 | .expect(/
    <\/div>/i, done); 49 | }); 50 | 51 | it('should support ReactDOMServer.renderToString without locals', function (done) { 52 | var app = App(); 53 | app.use(function*() { 54 | this.render('home', true); 55 | }); 56 | 57 | request(app.listen()) 58 | .get('/') 59 | .expect(200) 60 | .expect(/
    <\/div>/i, done); 61 | }); 62 | 63 | it('should support ReactDOMServer.renderToString using internals option', function (done) { 64 | var app = App({ 65 | internals: true 66 | }); 67 | app.use(function*() { 68 | this.render('home'); 69 | }); 70 | 71 | request(app.listen()) 72 | .get('/') 73 | .expect(200) 74 | .expect(/
    <\/div>/i, done); 75 | }); 76 | 77 | it('should support ctx.state', function (done) { 78 | var app = App(); 79 | app.use(function*() { 80 | this.state.title = 'home'; 81 | this.render('home'); 82 | }); 83 | 84 | request(app.listen()) 85 | .get('/') 86 | .expect(200) 87 | .expect('
    home
    ', done); 88 | }); 89 | 90 | it('ctx.state should be override', function (done) { 91 | var app = App(); 92 | app.use(function*() { 93 | this.state.title = 'home'; 94 | this.render('home', {title: 'index'}); 95 | }); 96 | 97 | request(app.listen()) 98 | .get('/') 99 | .expect(200) 100 | .expect('
    index
    ', done); 101 | }); 102 | 103 | it('should error when filename invalid', function (done) { 104 | var app = App(); 105 | app.use(function*() { 106 | this.state.title = 'home'; 107 | this.render('../home'); 108 | }); 109 | 110 | request(app.listen()) 111 | .get('/') 112 | .expect(500, done); 113 | }); 114 | 115 | it('should error when file not exist', function (done) { 116 | var app = App(); 117 | app.use(function*() { 118 | this.state.title = 'home'; 119 | this.render('not-exist'); 120 | }); 121 | 122 | request(app.listen()) 123 | .get('/') 124 | .expect(500, done); 125 | }); 126 | 127 | it('should render with extname ok', function (done) { 128 | var app = App(); 129 | app.use(function*() { 130 | this.state.title = 'home'; 131 | this.render('home.jsx'); 132 | }); 133 | 134 | request(app.listen()) 135 | .get('/') 136 | .expect(200) 137 | .expect('
    home
    ', done); 138 | }); 139 | 140 | it('should render with beautify ok', function (done) { 141 | var app = App({ 142 | beautify: true 143 | }); 144 | app.use(function*() { 145 | this.state.title = 'home'; 146 | this.render('home.jsx'); 147 | }); 148 | 149 | request(app.listen()) 150 | .get('/') 151 | .expect(200) 152 | .expect('\n
    home
    ', done); 153 | }); 154 | 155 | it('should render with writeResp=false in options', function (done) { 156 | var app = App({ 157 | writeResp: false 158 | }); 159 | app.use(function*() { 160 | this.render('home.jsx'); 161 | }); 162 | 163 | request(app.listen()) 164 | .get('/') 165 | .expect(404, done); 166 | }); 167 | 168 | it('should render with writeResp=false in contenxt', function (done) { 169 | var app = App(); 170 | app.use(function*() { 171 | this.render('home.jsx', { 172 | writeResp: false 173 | }); 174 | }); 175 | 176 | request(app.listen()) 177 | .get('/') 178 | .expect(404, done); 179 | }); 180 | 181 | it('should babel only with regexp', function (done) { 182 | var app = App({ 183 | babel: { 184 | only: [new RegExp('^' + path.join(__dirname, 'support/views'))] 185 | } 186 | }); 187 | 188 | app.use(function*() { 189 | this.state.title = 'home'; 190 | this.render('home.jsx'); 191 | }); 192 | 193 | request(app.listen()) 194 | .get('/') 195 | .expect(200) 196 | .expect('
    home
    ', done); 197 | }); 198 | }); 199 | }); 200 | 201 | function App(options) { 202 | options = options || {}; 203 | copy({ 204 | views: path.join(__dirname, 'support/views') 205 | }).to(options); 206 | 207 | var app = koa(); 208 | react(app, options); 209 | return app; 210 | } 211 | -------------------------------------------------------------------------------- /test/support/views/home.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var home = React.createClass({ 4 | 5 | render: function() { 6 | return ( 7 |
    {this.props.title}
    8 | ); 9 | } 10 | 11 | }); 12 | 13 | module.exports = home; 14 | --------------------------------------------------------------------------------