├── .npmignore ├── .babelrc ├── test ├── component │ ├── ext.react │ ├── nojsx.jsx │ ├── attr.jsx │ └── app.jsx ├── app │ └── app.jsx └── index.js ├── .travis.yml ├── .gitignore ├── package.json ├── README_zh-CN.md ├── README.md ├── src └── index.js └── .eslintrc /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["add-module-exports", "transform-es2015-modules-commonjs"] 4 | } 5 | -------------------------------------------------------------------------------- /test/component/ext.react: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | module.exports = React.createClass({ 5 | displayName: 'App', 6 | render: function render() { 7 | return (
other-app
); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /test/app/app.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | module.exports = React.createClass({ 5 | displayName: 'App', 6 | render: function render() { 7 | return ( 8 |
other-app
9 | ); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /test/component/nojsx.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | module.exports = React.createClass({ 5 | displayName: 'Nojsx', 6 | render: function render() { 7 | return React.createElement('div', {className: 'nojsx'}, 'this is children'); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4' 4 | sudo: false 5 | script: 6 | - "npm run test-cov" 7 | after_script: "npm install coveralls && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" 8 | 9 | branches: 10 | only: 11 | - master 12 | -------------------------------------------------------------------------------- /test/component/attr.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | module.exports = React.createClass({ 5 | displayName: 'Attr', 6 | render: function render() { 7 | return ( 8 |
9 | ); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /test/component/app.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | module.exports = React.createClass({ 5 | displayName: 'App', 6 | render: function render() { 7 | var children = this.props.children; 8 | return ( 9 |
{children}
10 | ); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | lib/ 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "think-react-render", 3 | "description": "react server side rendering for thinkjs 2.x", 4 | "version": "2.0.0", 5 | "author": {}, 6 | "scripts": { 7 | "test": "mocha --reporter spec --timeout 5000 --recursive test/", 8 | "test-cov": "istanbul cover ./node_modules/mocha/bin/_mocha -- -t 5000 --recursive -R spec test/", 9 | "compile": "babel src/ --out-dir lib/", 10 | "watch-compile": "npm run compile -- --watch", 11 | "prepublish": "npm run compile", 12 | "eslint": "eslint src/" 13 | }, 14 | "contributors": [], 15 | "main": "lib/index.js", 16 | "dependencies": { 17 | "babel-runtime": "6.20.x", 18 | "node-jsx": "0.13.x", 19 | "react": "15.4.x", 20 | "react-dom": "15.4.x" 21 | }, 22 | "devDependencies": { 23 | "babel-cli": "6.18.x", 24 | "babel-eslint": "7.1.x", 25 | "babel-plugin-add-module-exports": "^0.2.1", 26 | "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", 27 | "babel-preset-es2015": "^6.18.0", 28 | "chai": "3.5.x", 29 | "coveralls": "2.11.x", 30 | "eslint": "3.13.x", 31 | "istanbul": "0.4.x", 32 | "jsdom": "9.9.x", 33 | "mocha": "3.2.x", 34 | "thinkjs": "2.x.x" 35 | }, 36 | "keywords": [ 37 | "thinkjs", 38 | "react", 39 | "render" 40 | ], 41 | "repository": { 42 | "type": "git", 43 | "url": "https://github.com/AlphaTr/think-react-render" 44 | }, 45 | "engines": { 46 | "node": ">=0.12.0" 47 | }, 48 | "license": "MIT", 49 | "readmeFilename": "README.md", 50 | "bugs": { 51 | "url": "https://github.com/AlphaTr/think-react-render/issues" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | # think-react-render 2 | 3 | thinkjs 中间件,使用 thinkjs 来做 React 服务端渲染 4 | 5 | [![NPM version](https://img.shields.io/npm/v/think-react-render.svg?style=flat-square)](https://www.npmjs.com/package/think-react-render) 6 | [![Build Status](https://img.shields.io/travis/alphatr/think-react-render/master.svg?style=flat-square)](https://travis-ci.org/alphatr/think-react-render) 7 | [![Coverage Status](https://img.shields.io/coveralls/alphatr/think-react-render/master.svg?style=flat-square)](https://coveralls.io/github/alphatr/think-react-render) 8 | 9 | ## 安装 10 | 11 | ``` 12 | npm install think-react-render 13 | ``` 14 | 15 | 建议使用 npm.taobao.org 源来安装,详见 [npm.taobao.org](http://npm.taobao.org/) 16 | 17 | ## 在 thinkjs 中使用 18 | 19 | 编辑 `bootstrap/middleware.js`,注册中间件 20 | 21 | ```javascript 22 | var reactRender = require('think-react-render'); 23 | think.middleware('react_render', reactRender); 24 | ``` 25 | 26 | 编辑 `config/hook.js`,调用中间件 27 | 28 | ```javascript 29 | module.exports = { 30 | 'view_parse': ['append', 'react_render'] 31 | }; 32 | ``` 33 | 34 | 然后在模板文件中书写 component, 中间件会将首字母大写的标签识别为 component 并进行服务端渲染。这里仅仅支持 JSX 语法的一小部分,不支持嵌套等,所以这里仅仅作为一个入口,而大部分逻辑写在 jsx 文件中。 35 | 36 | ``` 37 | 38 | 39 | 40 | 41 | React 42 | 43 | 44 | 45 | 46 | 47 | ``` 48 | 这里将会渲染 App 组件,它带有一个 name 的属性。name 的属性值这里沿用 React 的做法,appname 是一个变量名称,如果要使用直接的字符串,请使用 `name={"string"}` 或者 `name="string"` 这种形式。 49 | 50 | 在 controller 中使用 `this.assign()` 方法将数据 assign 到模板文件 51 | 52 | ```javascript 53 | var Base = require('./base.js'); 54 | 55 | module.exports = think.controller(Base, { 56 | indexAction: function (self) { 57 | this.assign('appname', "think-react-render"); 58 | return this.display(); 59 | } 60 | }); 61 | ``` 62 | 63 | 这里 assign 的 `appname` 将会被用到 上面的 `App` 组件中 64 | 65 | 在 `view/component` 目录下创建你的 component 文件,例如上面的 `app.jsx`: 66 | 67 | ```javascript 68 | var React = require('react'); 69 | 70 | module.exports = React.createClass({ 71 | render: function () { 72 | return ( 73 |
{this.props.name}
74 | ); 75 | } 76 | }); 77 | ``` 78 | 79 | 最后渲染的结果就是这样子 80 | 81 | ``` 82 | 83 | 84 | 85 | 86 | React 87 | 88 | 89 |
think-react-render
90 | 91 | 92 | ``` 93 | 94 | ## 配置选项 95 | 96 | 可以创建 `config/react_render.js` 配置文件: 97 | 98 | ```javascript 99 | module.exports = { 100 | jsx: true, // 是否使用 jsx 语法,默认使用 101 | extension: '.jsx', // component 文件的后缀,默认是 jsx, 102 | root_path: 'component', // component 文件的路径,如果是相对地址,那就是相对于 view.root_path 的,同时支持绝对地址 103 | left_delimiter: '{', // 在模板文件中 component 属性的定界符 如 "name={appname}",不对 component 中的有影响,当和模板的定界符冲突就需要更改 104 | right_delimiter: '}', // 同上 105 | lower_name: true // 是否 component 的文件名使用小写 106 | }; 107 | ``` 108 | 109 | ## LICENSE 110 | 111 | MIT 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Think-react-render 2 | 3 | react server side rendering for thinkjs 2.x 4 | 5 | [![NPM version](https://img.shields.io/npm/v/think-react-render.svg?style=flat-square)](https://www.npmjs.com/package/think-react-render) 6 | [![Build Status](https://img.shields.io/travis/alphatr/think-react-render/master.svg?style=flat-square)](https://travis-ci.org/alphatr/think-react-render) 7 | [![Coverage Status](https://img.shields.io/coveralls/alphatr/think-react-render/master.svg?style=flat-square)](https://coveralls.io/github/alphatr/think-react-render) 8 | 9 | [中文文档](https://github.com/alphatr/think-react-render/blob/master/README_zh-CN.md) 10 | 11 | ## Install 12 | 13 | ``` 14 | npm install think-react-render 15 | ``` 16 | 17 | ## How to use in thinkjs 18 | 19 | open the middleware configuration file `bootstrap/middleware.js`, and add this content as follows for register middleware: 20 | 21 | ```javascript 22 | var reactRender = require('think-react-render'); 23 | think.middleware('react_render', reactRender); 24 | ``` 25 | 26 | edit the hook configuration file `config/hook.js`, edit the configuration properties, it can auto executes in each request and after the view parse. 27 | 28 | ```javascript 29 | module.exports = { 30 | 'view_parse': ['append', 'react_render'] 31 | }; 32 | ``` 33 | 34 | write React component in the view files, the middleware will capitalize the first letter of the tag identified as a React component and rendering in service side. 35 | 36 | this is a part of JSX Syntax. doesn't support child component, so you can use it as a entrance. 37 | 38 | ``` 39 | 40 | 41 | 42 | 43 | React 44 | 45 | 46 | 47 | 48 | 49 | ``` 50 | 51 | use `this.assign()` method in controller to assign the data to view files. 52 | 53 | ```javascript 54 | var Base = require('./base.js'); 55 | 56 | module.exports = think.controller(Base, { 57 | indexAction: function (self) { 58 | this.assign('appname', "think-react-render"); 59 | return this.display(); 60 | } 61 | }); 62 | ``` 63 | 64 | create your self component files in the `view/component` directory, such as the follows `app.jsx`: 65 | 66 | ```javascript 67 | var React = require('react'); 68 | 69 | module.exports = React.createClass({ 70 | render: function () { 71 | return ( 72 |
{this.props.name}
73 | ); 74 | } 75 | }); 76 | ``` 77 | 78 | Here are the results rendered 79 | 80 | ``` 81 | 82 | 83 | 84 | 85 | React 86 | 87 | 88 |
think-react-render
89 | 90 | 91 | ``` 92 | 93 | ## Configuration 94 | 95 | you can create `config/react_render.js` configuration file as follow: 96 | 97 | ```javascript 98 | module.exports = { 99 | jsx: true, // use jsx syntax? default is true 100 | extension: '.jsx', // The extension of component files, default is .jsx, 101 | root_path: 'component', // Component file path, the path relative for `view.root_path`, and this support absolute path. 102 | left_delimiter: '{', // The delimiter of component arrribute in view files, such as "name={appname}", this configuration doesn't work for react component file, you can change it when the default value conflict with view file syntax. 103 | right_delimiter: '}', // such as the above 104 | lower_name: true // is the component use lower case filename. 105 | }; 106 | ``` 107 | 108 | ## LICENSE 109 | 110 | MIT 111 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import ReactDOMServer from 'react-dom/server'; 5 | import path from 'path'; 6 | 7 | export default class extends think.middleware.base { 8 | 9 | /** 10 | * 初始化 11 | * @param {HTTP} http http object 12 | * @return {undefined} 13 | */ 14 | init(http) { 15 | super.init(http); 16 | let defaultOption = { 17 | jsx: true, 18 | extension: '.jsx', 19 | 'root_path': 'component', 20 | 'lower_name': true, 21 | 'left_delimiter': '{', 22 | 'right_delimiter': '}' 23 | }; 24 | 25 | this.option = think.extend(defaultOption, this.config('react_render')); 26 | 27 | if (this.option.jsx) { 28 | require('node-jsx').install({extension: this.option.extension}); 29 | } 30 | } 31 | 32 | /** 33 | * 将组件对象使用 React 渲染成 html 字符串 34 | * @param {Object} component 组件对象 35 | * @return {String} 渲染结果 36 | */ 37 | render(component) { 38 | var reactPath = path.join(this.option.root_path, component.name + this.option.extension); 39 | if (this.option.lower_name) { 40 | reactPath = path.join(this.option.root_path, component.name.toLowerCase() + this.option.extension); 41 | } 42 | 43 | var rootPath = path.resolve(this.config('view.root_path'), reactPath); 44 | 45 | // todo: 判断路径存在 46 | var app = React.createFactory(require(rootPath)); 47 | var str = ReactDOMServer.renderToString(app(component.attrs)); 48 | return str; 49 | } 50 | 51 | /** 52 | * 解析 content 字符串,返回渲染结果 53 | * @param {String} content 渲染的字符串 54 | * @return {String} react 渲染后的结果 55 | */ 56 | parse(content) { 57 | var self = this; 58 | var doubleRegex = /\s*<\s*([A-Z][a-zA-Z-]+)(\s+[^>]+?)?\s*>(.*?)<\s*\/\s*\1\s*>\s*/g; 59 | var singleRegex = /\s*<\s*([A-Z][a-zA-Z-]+)(\s+[^>]+?)?\s*\/\s*>\s*/g; 60 | 61 | var replaceFn = function replace(full, app, attrs, children) { 62 | attrs = self.attrParse(attrs || ''); 63 | 64 | if (children) { 65 | attrs.children = children; 66 | } 67 | 68 | return self.render({name: app, attrs}); 69 | }; 70 | 71 | return content.replace(doubleRegex, replaceFn).replace(singleRegex, replaceFn); 72 | } 73 | 74 | /** 75 | * 解析 component 的属性 76 | * @param {String} attrStr 属性字符串 77 | * @return {Object} 解析后的属性对象 78 | */ 79 | attrParse(attrStr) { 80 | var attrObj = {}; 81 | var tVar = this.tVar; 82 | 83 | var encodeReg = str => str.replace(/([\*\.\?\+\$\^\[\]\(\)\{\}\|\\\/])/ig, match => '\\' + match); 84 | let leftDelimiter = encodeReg(this.option.left_delimiter); 85 | let rightDelimiter = encodeReg(this.option.right_delimiter); 86 | var delimiterRegex = new RegExp(`(^${leftDelimiter}\s*)|(\s*${rightDelimiter}$)`, 'g'); 87 | 88 | var getValue = key => { 89 | if (/(^['"])|(['"]$)/g.test(key)) { 90 | return key.replace(/(^['"])|(['"]$)/g, ''); 91 | } 92 | 93 | var temp = tVar; 94 | key.split('.').forEach(item => temp = temp[item]); 95 | return temp; 96 | }; 97 | 98 | attrStr.split(/\s+/g).forEach(attr => { 99 | var key = attr.split('=')[0].trim(); 100 | var tempVal = attr.split('=').slice(1).join('='); 101 | var value = false; 102 | 103 | if (tempVal) { 104 | if (delimiterRegex.test(tempVal)) { 105 | value = getValue(tempVal.replace(delimiterRegex, '').trim()); 106 | } else { 107 | value = tempVal.replace(/(^['"])|(['"]$)/g, ''); 108 | } 109 | } 110 | 111 | if (key && !attrObj[key]) { 112 | attrObj[key] = value; 113 | return; 114 | } 115 | 116 | // todo: warning 117 | }); 118 | 119 | return attrObj; 120 | } 121 | 122 | /** 123 | * middleware 入口 124 | * @param {String} content 渲染后的页面字符串 125 | * @return {Promise} React 服务端渲染后的字符串 126 | */ 127 | run(content) { 128 | this.tVar = this.http._view.tVar; 129 | return this.parse(content); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "node": true, 5 | "es6": true, 6 | "mocha": true 7 | }, 8 | "rules": { 9 | "comma-dangle": 2, 10 | "no-cond-assign": 2, 11 | "no-console": 1, 12 | "no-constant-condition": 2, 13 | "no-control-regex": 2, 14 | "no-debugger": 2, 15 | "no-dupe-args": 2, 16 | "no-dupe-keys": 2, 17 | "no-duplicate-case": 2, 18 | "no-empty-character-class": 2, 19 | "no-empty": 2, 20 | "no-ex-assign": 2, 21 | "no-extra-boolean-cast": 2, 22 | "no-extra-parens": 2, 23 | "no-extra-semi": 2, 24 | "no-func-assign": 2, 25 | "no-invalid-regexp": 2, 26 | "no-irregular-whitespace": 2, 27 | "no-negated-in-lhs": 2, 28 | "no-obj-calls": 2, 29 | "no-regex-spaces": 2, 30 | "no-sparse-arrays": 2, 31 | "no-unexpected-multiline": 2, 32 | "no-unreachable": 2, 33 | "use-isnan": 2, 34 | "valid-jsdoc": 2, 35 | "valid-typeof": 2, 36 | 37 | "accessor-pairs": [2, {"getWithoutSet": true}], 38 | "block-scoped-var": 2, 39 | "complexity": [2, 10], 40 | "consistent-return": 2, 41 | "curly": 2, 42 | "default-case": 1, 43 | "dot-location": [2, "property"], 44 | "dot-notation": 2, 45 | "eqeqeq": 2, 46 | "no-alert": 2, 47 | "no-caller": 2, 48 | "no-div-regex": 2, 49 | "no-else-return": 2, 50 | "no-empty-label": 2, 51 | "no-empty-pattern": 2, 52 | "no-eq-null": 2, 53 | "no-eval": 2, 54 | "no-extend-native": 2, 55 | "no-extra-bind": 2, 56 | "no-fallthrough": 2, 57 | "no-floating-decimal": 2, 58 | "no-implicit-coercion": [2, { 59 | "boolean": false, 60 | "number": false, 61 | "string": false 62 | }], 63 | "no-implied-eval": 2, 64 | "no-invalid-this": 2, 65 | "no-iterator": 2, 66 | "no-labels": 2, 67 | "no-lone-blocks": 2, 68 | "no-loop-func": 2, 69 | "no-multi-spaces": [1, {"exceptions": {"VariableDeclarator": true, "ImportDeclaration": true}}], 70 | "no-multi-str": 2, 71 | "no-native-reassign": 2, 72 | "no-new-func": 2, 73 | "no-new-wrappers": 2, 74 | "no-new": 2, 75 | "no-octal-escape": 2, 76 | "no-octal": 2, 77 | "no-process-env": 2, 78 | "no-proto": 2, 79 | "no-redeclare": 2, 80 | "no-return-assign": [2, "always"], 81 | "no-script-url": 2, 82 | "no-self-compare": 2, 83 | "no-sequences": 2, 84 | "no-throw-literal": 2, 85 | "no-unused-expressions": 2, 86 | "no-useless-call": 2, 87 | "no-useless-concat": 2, 88 | "no-void": 2, 89 | "no-warning-comments": [1, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], 90 | "no-with": 2, 91 | "radix": 2, 92 | "wrap-iife": [2, "outside"], 93 | "yoda": 2, 94 | 95 | "strict": [2, "global"], 96 | 97 | "no-delete-var": 2, 98 | "no-label-var": 2, 99 | "no-shadow-restricted-names": 2, 100 | "no-undef-init": 2, 101 | "no-undef": 2, 102 | "no-undefined": 2, 103 | "no-unused-vars": 2, 104 | "no-use-before-define": 2, 105 | 106 | "callback-return": [2, ["callback", "cb", "next"]], 107 | "handle-callback-err": [2, "^(err|error)$" ], 108 | "no-mixed-requires": [1, {"grouping": false}], 109 | "no-new-require": 2, 110 | "no-path-concat": 2, 111 | "no-process-exit": 2, 112 | "no-sync": 2, 113 | 114 | "array-bracket-spacing": [2, "never"], 115 | "block-spacing": [2, "never"], 116 | "brace-style": [2, "1tbs", { "allowSingleLine": false }], 117 | "camelcase": [1, {"properties": "always"}], 118 | "comma-spacing": [2, {"before": false, "after": true}], 119 | "comma-style": [2, "last"], 120 | "computed-property-spacing": [2, "never"], 121 | "consistent-this": [2, "self"], 122 | "eol-last": 2, 123 | "func-names": 2, 124 | "func-style": [2, "expression", {"allowArrowFunctions": true}], 125 | "indent": [1, 4, {"SwitchCase": 1}], 126 | "key-spacing": [1, {"beforeColon": false, "afterColon": true}], 127 | "linebreak-style": [2, "unix"], 128 | "lines-around-comment": [1, { 129 | "beforeBlockComment": true, 130 | "beforeLineComment": true, 131 | "allowBlockStart": true, 132 | "allowObjectStart": true 133 | }], 134 | "max-depth": [1, 8], 135 | "max-len": [1, 120, 4, {"ignoreComments": true, "ignoreUrls": true}], 136 | "max-nested-callbacks": [2, 5], 137 | "max-params": [2, 5], 138 | "max-statements": [2, 12], 139 | "new-cap": [2, {"capIsNewExceptions": [], "newIsCapExceptions": []}], 140 | "new-parens": 2, 141 | "no-array-constructor": 2, 142 | "no-bitwise": 2, 143 | "no-continue": 1, 144 | "no-inline-comments": 1, 145 | "no-lonely-if": 2, 146 | "no-mixed-spaces-and-tabs": 2, 147 | "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 1}], 148 | "no-negated-condition": 2, 149 | "no-nested-ternary": 2, 150 | "no-new-object": 2, 151 | "no-restricted-syntax": [2, "WithStatement"], 152 | "no-spaced-func": 2, 153 | "no-trailing-spaces": 2, 154 | "no-unneeded-ternary": 2, 155 | "object-curly-spacing": [2, "never"], 156 | "operator-assignment": [2, "always"], 157 | "operator-linebreak": [2, "none"], 158 | "quote-props": [2, "as-needed", {"keywords": true, "numbers": true, "unnecessary": false}], 159 | "quotes": [2, "single", "avoid-escape"], 160 | "semi-spacing": [2, {"before": false, "after": true}], 161 | "semi": [2, "always"], 162 | "space-after-keywords": [1, "always"], 163 | "space-before-blocks": [1, { "functions": "always", "keywords": "always" }], 164 | "space-before-function-paren": [1, {"anonymous": "always", "named": "never"}], 165 | "space-before-keywords": [1, "always"], 166 | "space-in-parens": [1, "never"], 167 | "space-infix-ops": [1, {"int32Hint": true}], 168 | "space-return-throw-case": 1, 169 | "space-unary-ops": 1, 170 | "spaced-comment": [1, "always"] 171 | }, 172 | "globals": { 173 | "think": true, 174 | "thinkCache": true 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('chai').assert; 4 | var path = require('path'); 5 | var http = require('http'); 6 | var jsDom = require('jsdom'); 7 | 8 | var Thinkjs = require('thinkjs'); 9 | var instance = new Thinkjs(); 10 | instance.load(); 11 | 12 | var ReactRender = require('../lib/index.js'); 13 | 14 | var component = path.resolve(__dirname, 'component'); 15 | 16 | var getHttp = function getHttp(options) { 17 | var req = new http.IncomingMessage(); 18 | req.headers = { 19 | 'host': 'www.thinkjs.org', 20 | 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 21 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_0) AppleWebKit' 22 | }; 23 | req.method = 'GET'; 24 | req.httpVersion = '1.1'; 25 | req.url = '/index/index'; 26 | 27 | var res = new http.ServerResponse(req); 28 | res.write = function write() { 29 | return true; 30 | }; 31 | 32 | return think.http(req, res).then(function setOption(http) { 33 | http._view = {}; 34 | http._view.tVar = options._val || {}; 35 | http.assign = function assign() { 36 | return http._view.tVar; 37 | }; 38 | 39 | if (options) { 40 | for (var key in options) { 41 | http[key] = options[key]; 42 | } 43 | } 44 | return http; 45 | }); 46 | }; 47 | 48 | var execMiddleware = function execMiddleware(options, content) { 49 | return getHttp(options).then(function runMiddleWare(http) { 50 | var instance = new ReactRender(http); 51 | return instance.run(content); 52 | }); 53 | }; 54 | 55 | describe('react component parse', function reactRender() { 56 | it('(self-closing tag without any arrtibutes and space)', function test() { 57 | execMiddleware({ 58 | _config: {'react_render': {'root_path': component}} 59 | }, '').then(function render(data) { 60 | jsDom.env(data, function domParse(err, window) { 61 | var app = window.document.querySelector('.app'); 62 | assert.isNull(err); 63 | assert.equal(app.nodeType, window.Node.ELEMENT_NODE); 64 | assert.equal(app.innerHTML, ''); 65 | }); 66 | }); 67 | }); 68 | 69 | it('< App / >(self-closing tag with space)', function test() { 70 | execMiddleware({ 71 | _config: {'react_render': {'root_path': component}} 72 | }, '< App / >').then(function render(data) { 73 | jsDom.env(data, function domParse(err, window) { 74 | var app = window.document.querySelector('.app'); 75 | assert.isNull(err); 76 | assert.equal(app.nodeType, window.Node.ELEMENT_NODE); 77 | assert.equal(app.innerHTML, ''); 78 | }); 79 | }); 80 | }); 81 | 82 | it('(tag pairs without any arrtibutes and space)', function test() { 83 | execMiddleware({ 84 | _config: {'react_render': {'root_path': component}} 85 | }, '').then(function render(data) { 86 | jsDom.env(data, function domParse(err, window) { 87 | var app = window.document.querySelector('.app'); 88 | assert.isNull(err); 89 | assert.equal(app.nodeType, window.Node.ELEMENT_NODE); 90 | assert.equal(app.innerHTML, ''); 91 | }); 92 | }); 93 | }); 94 | 95 | it('children(tag pairs with children text)', function test() { 96 | execMiddleware({ 97 | _config: {'react_render': {'root_path': component}} 98 | }, 'children').then(function render(data) { 99 | jsDom.env(data, function domParse(err, window) { 100 | var app = window.document.querySelector('.app'); 101 | assert.isNull(err); 102 | assert.equal(app.nodeType, window.Node.ELEMENT_NODE); 103 | assert.equal(app.innerHTML, 'children'); 104 | }); 105 | }); 106 | }); 107 | 108 | it('< App > < / App >(tag pairs with space)', function test() { 109 | execMiddleware({ 110 | _config: {'react_render': {'root_path': component}} 111 | }, '< App > < / App >').then(function render(data) { 112 | jsDom.env(data, function domParse(err, window) { 113 | var app = window.document.querySelector('.app'); 114 | assert.isNull(err); 115 | assert.equal(app.nodeType, window.Node.ELEMENT_NODE); 116 | assert.equal(app.innerHTML, ' '); 117 | }); 118 | }); 119 | }); 120 | }); 121 | 122 | describe('react attributes parse', function reactRender() { 123 | it('(self-closing tag with one arrtibute)', function test() { 124 | var tVar = { 125 | name: 'attributes name' 126 | }; 127 | 128 | execMiddleware({ 129 | _config: {'react_render': {'root_path': component}}, 130 | _view: {tVar: tVar} 131 | }, '').then(function render(data) { 132 | jsDom.env(data, function domParse(err, window) { 133 | var app = window.document.querySelector('.attr'); 134 | assert.isNull(err); 135 | assert.equal(app.getAttribute('name'), tVar.name); 136 | }); 137 | }); 138 | }); 139 | 140 | it('(tag pairs with arrtibutes)', function test() { 141 | var tVar = { 142 | name: 'attributes name' 143 | }; 144 | 145 | execMiddleware({ 146 | _config: {'react_render': {'root_path': component}}, 147 | _view: {tVar: tVar} 148 | }, '').then(function render(data) { 149 | jsDom.env(data, function domParse(err, window) { 150 | var app = window.document.querySelector('.attr'); 151 | assert.isNull(err); 152 | assert.equal(app.getAttribute('name'), tVar.name); 153 | }); 154 | }); 155 | }); 156 | 157 | 158 | it('(tag with multiple arrtibutes)', function test() { 159 | var tVar = { 160 | name: 'attributes name', 161 | type: 'input' 162 | }; 163 | 164 | execMiddleware({ 165 | _config: {'react_render': {'root_path': component}}, 166 | _view: {tVar: tVar} 167 | }, '').then(function render(data) { 168 | jsDom.env(data, function domParse(err, window) { 169 | var app = window.document.querySelector('.attr'); 170 | assert.isNull(err); 171 | assert.equal(app.getAttribute('name'), tVar.name); 172 | assert.equal(app.getAttribute('type'), tVar.type); 173 | }); 174 | }); 175 | }); 176 | 177 | it('(tag with arrtibutes and new line)', function test() { 178 | var tVar = { 179 | name: 'attributes name', 180 | type: 'input' 181 | }; 182 | 183 | execMiddleware({ 184 | _config: {'react_render': {'root_path': component}}, 185 | _view: {tVar: tVar} 186 | }, '').then(function render(data) { 187 | jsDom.env(data, function domParse(err, window) { 188 | var app = window.document.querySelector('.attr'); 189 | assert.isNull(err); 190 | assert.equal(app.getAttribute('name'), tVar.name); 191 | assert.equal(app.getAttribute('type'), tVar.type); 192 | }); 193 | }); 194 | }); 195 | 196 | it('(string attribute value)', function test() { 197 | var tVar = { 198 | name: 'attributes' 199 | }; 200 | 201 | execMiddleware({ 202 | _config: {'react_render': {'root_path': component}}, 203 | _view: {tVar: tVar} 204 | }, '').then(function render(data) { 205 | jsDom.env(data, function domParse(err, window) { 206 | var app = window.document.querySelector('.attr'); 207 | assert.isNull(err); 208 | assert.equal(app.getAttribute('name'), 'name'); 209 | }); 210 | }); 211 | }); 212 | 213 | it('(other type string attribute value)', function test() { 214 | var tVar = { 215 | name: 'attributes', 216 | type: 'input' 217 | }; 218 | 219 | execMiddleware({ 220 | _config: {'react_render': {'root_path': component}}, 221 | _view: {tVar: tVar} 222 | }, '').then(function render(data) { 223 | jsDom.env(data, function domParse(err, window) { 224 | var app = window.document.querySelector('.attr'); 225 | assert.isNull(err); 226 | assert.equal(app.getAttribute('name'), 'name'); 227 | }); 228 | }); 229 | }); 230 | 231 | it('(arrtibutes value is a object arrtibute)', function test() { 232 | var tVar = { 233 | obj: { 234 | name: 'obj-name' 235 | } 236 | }; 237 | 238 | execMiddleware({ 239 | _config: {'react_render': {'root_path': component}}, 240 | _view: {tVar: tVar} 241 | }, '').then(function render(data) { 242 | jsDom.env(data, function domParse(err, window) { 243 | var app = window.document.querySelector('.attr'); 244 | assert.isNull(err); 245 | assert.equal(app.getAttribute('name'), tVar.obj.name); 246 | }); 247 | }); 248 | }); 249 | }); 250 | 251 | describe('options', function reactRender() { 252 | it('root_path test', function test() { 253 | var appPath = path.resolve(__dirname, 'app'); 254 | 255 | execMiddleware({ 256 | _config: {'react_render': {'root_path': appPath}} 257 | }, '').then(function render(data) { 258 | jsDom.env(data, function domParse(err, window) { 259 | var app = window.document.querySelector('.other-app'); 260 | assert.isNull(err); 261 | assert.equal(app.innerHTML, 'other-app'); 262 | }); 263 | }); 264 | }); 265 | 266 | it('jsx options', function test() { 267 | execMiddleware({ 268 | _config: { 269 | 'react_render': { 270 | 'jsx': false, 271 | 'root_path': component 272 | } 273 | } 274 | }, '').then(function render(data) { 275 | jsDom.env(data, function domParse(err, window) { 276 | var app = window.document.querySelector('.nojsx'); 277 | assert.isNull(err); 278 | assert.equal(app.innerHTML, 'this is children'); 279 | }); 280 | }); 281 | }); 282 | 283 | it('delimiter options', function test() { 284 | var tVar = { 285 | name: 'attributes name' 286 | }; 287 | 288 | execMiddleware({ 289 | _config: { 290 | 'react_render': { 291 | 'left_delimiter': '[', 292 | 'right_delimiter': ']', 293 | 'root_path': component 294 | } 295 | }, 296 | _view: {tVar: tVar} 297 | }, '').then(function render(data) { 298 | jsDom.env(data, function domParse(err, window) { 299 | var app = window.document.querySelector('.attr'); 300 | assert.isNull(err); 301 | assert.equal(app.getAttribute('name'), tVar.name); 302 | }); 303 | }); 304 | }); 305 | }); 306 | --------------------------------------------------------------------------------