├── .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 | [](https://www.npmjs.com/package/think-react-render)
6 | [](https://travis-ci.org/alphatr/think-react-render)
7 | [](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 | [](https://www.npmjs.com/package/think-react-render)
6 | [](https://travis-ci.org/alphatr/think-react-render)
7 | [](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 |
--------------------------------------------------------------------------------