react_on_rails 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +41 -12
- data/app/helpers/react_on_rails_helper.rb +23 -10
- data/lib/react_on_rails/react_renderer.rb +40 -25
- data/lib/react_on_rails/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47bf68c00a2b017047097d27b50e92d6f417b071
|
4
|
+
data.tar.gz: 9eca8eecbee44ad2c857680735b44e61027f365c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 566649c85b68a57e26d9ac79bcd9f2b23802c288eb15c4032542fdbe96bf79b6fd9121ced75bb73fe28e1d20ba235e0b155e10d00ac5ae63a64d8db6aa2e4781
|
7
|
+
data.tar.gz: 4ca24d4f09242dd39d0c7842a3c9383c47f9515720bb78a5dd6e575ec0657d293ab2f343000db7d6a4df4306a159374e77d9e67a7afd67650bae3d7d14b7a3b4
|
data/README.md
CHANGED
@@ -19,14 +19,30 @@ Supports:
|
|
19
19
|
6. Server side rendering with fragment caching
|
20
20
|
7. react-router for client side rendering (and maybe server side eventually)
|
21
21
|
|
22
|
-
|
22
|
+
## OPEN ISSUES
|
23
|
+
1. We've got many open issues. However, none of these should stop you from using this gem if you're using React + Webpack with Rails, especially if you are client rendering.
|
24
|
+
2. Almost all the open issues are nice to haves like more tests, or some things that would be nice to have for server rendering.
|
25
|
+
3. If you want to work on any of the open issues, please comment on the issue. My team is mentoring anybody that's trying to help with the issues. We've got a private slack room for discussing React + Webpack with Rails.
|
26
|
+
4. Longer term, we hope to put in many conveniences into this gem, in terms of Webpack + Rails integration.
|
27
|
+
|
28
|
+
## Links
|
23
29
|
1. https://github.com/justin808/react-webpack-rails-tutorial/ See https://github.com/shakacode/react-webpack-rails-tutorial/pull/84 for how we integrated it!
|
24
30
|
2. http://www.railsonmaui.com/blog/2014/10/03/integrating-webpack-and-the-es6-transpiler-into-an-existing-rails-project/
|
25
31
|
3. http://forum.railsonmaui.com
|
26
|
-
|
32
|
+
4. Interested in consulting for implementing React with Rails, [email us! contact@shakacode.com](mailto: contact@shakacode.com).
|
33
|
+
5. If this project is interesting to you, [email us! contact@shakacode.com](mailto: contact@shakacode.com). We're looking for great
|
27
34
|
developers that want to work with Rails + React with a distributed, worldwide team, for our own
|
28
35
|
products, client work, and open source.
|
29
36
|
|
37
|
+
## How is different than the [react-rails gem](https://github.com/reactjs/react-rails)?
|
38
|
+
1. `react_on_rails` depends on [webpack](http://webpack.github.io/). `react-rails` integrates closely with sprockets and
|
39
|
+
helps you integrate JSX and the react code into a Rails project.
|
40
|
+
2. Likewise, using Webpack as show in the [react-webpack-rails-tutorial](https://github.com/justin808/react-webpack-rails-tutorial/)
|
41
|
+
does involve some extra setup. However, we feel that tight and simple integration with the node ecosystem is more than
|
42
|
+
worth any minor setup costs.
|
43
|
+
3. `react-rails` depends on `jquery-ujs` for client side rendering. `react_on_rails` has it's own JS code that does not
|
44
|
+
depend on jquery.
|
45
|
+
|
30
46
|
## Application Installation
|
31
47
|
|
32
48
|
Add these lines to your application's Gemfile, sustituting your preferable JavaScript engine.
|
@@ -48,12 +64,12 @@ And then execute:
|
|
48
64
|
The main API is a helper:
|
49
65
|
|
50
66
|
```ruby
|
51
|
-
react_component(component_name, props = {}, options = {})
|
67
|
+
<%= react_component(component_name, props = {}, options = {}) %>
|
52
68
|
```
|
53
69
|
|
54
70
|
Params are:
|
55
71
|
|
56
|
-
* **react_component_name**: can be a React component, created using a ES6 class, or React.createClass,
|
72
|
+
* **react_component_name**: [string] can be a React component, created using a ES6 class, or React.createClass,
|
57
73
|
or a `generator function` that returns a React component
|
58
74
|
|
59
75
|
using ES6
|
@@ -76,12 +92,12 @@ Params are:
|
|
76
92
|
```javascript
|
77
93
|
global.MyReactComponentApp = MyReactComponentApp;
|
78
94
|
```
|
79
|
-
|
80
|
-
`spec/dummy/client/app/startup/ClientReduxApp.jsx` for examples of this.
|
95
|
+
If you're curious as to what the gem generates for the server and client rendering, see [`spec/dummy/client/app/startup/serverGlobals.jsx`](https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/spec/sample_generated_js/server-generated.js)
|
96
|
+
and [`spec/dummy/client/app/startup/ClientReduxApp.jsx`](https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/spec/sample_generated_js/client-generated.js) for examples of this. Note, this is not the code that you are providing. You can see the client code by viewing the page source.
|
81
97
|
|
82
|
-
* **props**:
|
98
|
+
* **props**: [hash] Properties to pass to the react object
|
83
99
|
|
84
|
-
* **options:**
|
100
|
+
* **options:** [hash]
|
85
101
|
* **generator_function**: <true/false> default is false, set to true if you want to use a generator function rather than a React Component.
|
86
102
|
* **prerender**: <true/false> set to false when debugging!
|
87
103
|
* **trace**: <true/false> set to true to print additional debugging information in the browser default is true for development, off otherwise
|
@@ -98,11 +114,27 @@ Params are:
|
|
98
114
|
window.HelloWorld = HelloWorld;
|
99
115
|
```
|
100
116
|
3. Follow the examples in `spec/dummy/client/app/startup/serverGlobals.jsx` to expose your react components
|
101
|
-
for
|
117
|
+
for server side rendering.
|
102
118
|
```ruby
|
103
119
|
import HelloWorld from '../components/HelloWorld';
|
104
120
|
global.HelloWorld = HelloWorld;
|
105
121
|
```
|
122
|
+
|
123
|
+
## Server Rendering Tips
|
124
|
+
|
125
|
+
- Your code can't reference `document`. Server side JS execution does not have access to `document`, so jQuery and some
|
126
|
+
other libs won't work in this environment. You can debug this by putting in `console.log`
|
127
|
+
statements in your code.
|
128
|
+
- You can conditionally avoid running code that references document by passing in a boolean prop to your top level react
|
129
|
+
component. Since the passed in props Hash from the view helper applies to client and server side code, the best way to
|
130
|
+
do this is to use a generator function.
|
131
|
+
|
132
|
+
You might do something like this in some file for your top level component:
|
133
|
+
```javascript
|
134
|
+
global.App = () => <MyComponent serverSide={true} />;
|
135
|
+
```
|
136
|
+
|
137
|
+
The point is that you have separate files for top level client or server side, and you pass some extra option indicating that rendering is happening server sie.
|
106
138
|
|
107
139
|
## Optional Configuration
|
108
140
|
|
@@ -174,9 +206,6 @@ Contributions and pull requests welcome!
|
|
174
206
|
the `div` element where the inline JavaScript will render the component. You might also notice
|
175
207
|
how the props you pass (a Ruby Hash) becomes inline JavaScript on the HTML page.
|
176
208
|
|
177
|
-
TODO: Check if this is true still: If you're only doing client rendering, you still *MUST* create an empty version of this file. This
|
178
|
-
will soon change so that this is not necessary.
|
179
|
-
|
180
209
|
## JavaScript Runtime Configuration
|
181
210
|
See this [discussion on JavaScript performance](https://github.com/shakacode/react_on_rails/issues/21).
|
182
211
|
The net result is that you want to add this line to your Gemfile to get therubyracer as your default
|
@@ -60,8 +60,9 @@ module ReactOnRailsHelper
|
|
60
60
|
data_from_server_script_tag = javascript_tag(page_loaded_js)
|
61
61
|
|
62
62
|
# Create the HTML rendering part
|
63
|
-
server_rendered_html =
|
64
|
-
server_rendered_react_component_html(options, props, react_component_name
|
63
|
+
server_rendered_html, console_script =
|
64
|
+
server_rendered_react_component_html(options, props, react_component_name,
|
65
|
+
data_variable_name, dom_id)
|
65
66
|
|
66
67
|
rendered_output = content_tag(:div,
|
67
68
|
server_rendered_html,
|
@@ -71,6 +72,7 @@ module ReactOnRailsHelper
|
|
71
72
|
<<-HTML.html_safe
|
72
73
|
#{data_from_server_script_tag}
|
73
74
|
#{rendered_output}
|
75
|
+
#{console_script}
|
74
76
|
HTML
|
75
77
|
end
|
76
78
|
|
@@ -79,22 +81,23 @@ module ReactOnRailsHelper
|
|
79
81
|
@react_component_index += 1
|
80
82
|
end
|
81
83
|
|
82
|
-
|
84
|
+
# Returns Array [0]: html, [1]: script to console log
|
85
|
+
def server_rendered_react_component_html(options, props, react_component_name, data_variable, dom_id)
|
83
86
|
if prerender(options)
|
84
87
|
render_js_expression = <<-JS
|
85
88
|
(function(React) {
|
89
|
+
#{debug_js(react_component_name, data_variable, dom_id, trace(options))}
|
86
90
|
var reactElement = #{render_js_react_element(react_component_name, props.to_json, generator_function(options))}
|
87
91
|
return React.renderToString(reactElement);
|
88
92
|
})(this.React);
|
89
93
|
JS
|
90
94
|
# create the server generated html of the react component with props
|
91
95
|
options[:react_component_name] = react_component_name
|
92
|
-
|
93
|
-
|
96
|
+
options[:server_side] = true
|
97
|
+
render_js_internal(render_js_expression, options)
|
94
98
|
else
|
95
|
-
|
99
|
+
["",""]
|
96
100
|
end
|
97
|
-
server_rendered_react_component_html
|
98
101
|
end
|
99
102
|
|
100
103
|
# Takes javascript code and returns the output from it. This is called by react_component, which
|
@@ -102,13 +105,23 @@ module ReactOnRailsHelper
|
|
102
105
|
# This method could be used by itself to render the output of any javascript that returns a
|
103
106
|
# string of proper HTML.
|
104
107
|
def render_js(js_expression, options = {})
|
108
|
+
result = render_js_internal(js_expression, options)
|
109
|
+
"#{result[0]}\n#{result[1]}".html_safe
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
# Takes javascript code and returns the output from it. This is called by react_component, which
|
114
|
+
# sets up the JS code for rendering a react component.
|
115
|
+
# This method could be used by itself to render the output of any javascript that returns a
|
116
|
+
# string of proper HTML.
|
117
|
+
# Returns Array [0]: html, [1]: script to console log
|
118
|
+
def render_js_internal(js_expression, options = {})
|
105
119
|
# TODO: This should be changed so that we don't create a new context every time
|
106
120
|
# Example of doing this here: https://github.com/reactjs/react-rails/tree/master/lib/react/rails
|
107
121
|
ReactOnRails::ReactRenderer.new(options).render_js(js_expression,
|
108
|
-
options)
|
122
|
+
options)
|
109
123
|
end
|
110
124
|
|
111
|
-
private
|
112
125
|
|
113
126
|
def trace(options)
|
114
127
|
options.fetch(:trace) { ReactOnRails.configuration.trace }
|
@@ -124,7 +137,7 @@ module ReactOnRailsHelper
|
|
124
137
|
|
125
138
|
def debug_js(react_component_name, data_variable, dom_id, trace)
|
126
139
|
if trace
|
127
|
-
"console.log(\"
|
140
|
+
"console.log(\"RENDERED #{react_component_name} with data_variable"\
|
128
141
|
" #{data_variable} to dom node with id: #{dom_id}\");"
|
129
142
|
else
|
130
143
|
""
|
@@ -8,7 +8,11 @@ module ReactOnRails
|
|
8
8
|
var console = { history: [] };
|
9
9
|
['error', 'log', 'info', 'warn'].forEach(function (level) {
|
10
10
|
console[level] = function () {
|
11
|
-
|
11
|
+
var argArray = Array.prototype.slice.call(arguments);
|
12
|
+
if (argArray.length > 0) {
|
13
|
+
argArray[0] = '[SERVER] ' + argArray[0];
|
14
|
+
}
|
15
|
+
console.history.push({level: level, arguments: argArray});
|
12
16
|
};
|
13
17
|
});
|
14
18
|
JS
|
@@ -20,11 +24,11 @@ var console = { history: [] };
|
|
20
24
|
CONSOLE_REPLAY = <<-JS
|
21
25
|
var history = console.history;
|
22
26
|
if (history && history.length > 0) {
|
23
|
-
|
27
|
+
consoleReplay += '\\n<script>';
|
24
28
|
history.forEach(function (msg) {
|
25
|
-
|
29
|
+
consoleReplay += '\\nconsole.' + msg.level + '.apply(console, ' + JSON.stringify(msg.arguments) + ');';
|
26
30
|
});
|
27
|
-
|
31
|
+
consoleReplay += '\\n</script>';
|
28
32
|
}
|
29
33
|
JS
|
30
34
|
|
@@ -37,25 +41,28 @@ var console = { history: [] };
|
|
37
41
|
end
|
38
42
|
|
39
43
|
# js_code: JavaScript expression that returns a string.
|
40
|
-
# Returns
|
44
|
+
# Returns an Array:
|
45
|
+
# [0]: string of HTML for direct insertion on the page by evaluating js_code
|
46
|
+
# [1]: console messages
|
41
47
|
# Note, js_code does not have to be based on React.
|
42
48
|
# Calling code will probably call 'html_safe' on return value before rendering to the view.
|
43
49
|
def render_js(js_code, options = {})
|
44
50
|
component_name = options.fetch(:react_component_name, "")
|
51
|
+
server_side = options.fetch(:server_side, false)
|
45
52
|
|
46
|
-
result_js_code = "
|
53
|
+
result_js_code = " htmlResult = #{js_code}"
|
47
54
|
|
48
55
|
js_code_wrapper = <<-JS
|
49
56
|
(function () {
|
50
|
-
var
|
57
|
+
var htmlResult = '';
|
58
|
+
var consoleReplay = '';
|
51
59
|
#{ReactOnRails::ReactRenderer.wrap_code_with_exception_handler(result_js_code, component_name)}
|
52
|
-
#{
|
53
|
-
return
|
60
|
+
#{console_replay_js_code}
|
61
|
+
return JSON.stringify([htmlResult, consoleReplay]);
|
54
62
|
})()
|
55
63
|
JS
|
56
64
|
|
57
|
-
|
58
|
-
if trace_rails_on_maui
|
65
|
+
if ENV["TRACE_REACT_ON_RAILS"].present? # Set to anything to print generated code.
|
59
66
|
puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
60
67
|
puts "react_renderer.rb: 92"
|
61
68
|
puts "wrote file tmp/server-generated.js"
|
@@ -64,10 +71,12 @@ var console = { history: [] };
|
|
64
71
|
end
|
65
72
|
|
66
73
|
if js_context
|
67
|
-
js_context.eval(js_code_wrapper)
|
74
|
+
json_string = js_context.eval(js_code_wrapper)
|
68
75
|
else
|
69
|
-
ExecJS.eval(js_code_wrapper)
|
76
|
+
json_string = ExecJS.eval(js_code_wrapper)
|
70
77
|
end
|
78
|
+
# element 0 is the html, element 1 is the script tag for the server console output
|
79
|
+
JSON.parse(json_string)
|
71
80
|
end
|
72
81
|
|
73
82
|
def self.wrap_code_with_exception_handler(js_code, component_name)
|
@@ -77,13 +86,13 @@ var console = { history: [] };
|
|
77
86
|
}
|
78
87
|
catch(e) {
|
79
88
|
var lineOne =
|
80
|
-
'ERROR: You
|
89
|
+
'ERROR: You specified the option generator_function (could be in your defaults) to be\\n';
|
81
90
|
var lastLine =
|
82
91
|
'A generator function takes a single arg of props and returns a ReactElement.';
|
83
92
|
|
84
93
|
var msg = '';
|
85
94
|
var shouldBeGeneratorError = lineOne +
|
86
|
-
'false, but the
|
95
|
+
'false, but the React component \\'#{component_name}\\' seems to be a generator function.\\n' +
|
87
96
|
lastLine;
|
88
97
|
var reMatchShouldBeGeneratorError = /Can't add property context, object is not extensible/;
|
89
98
|
if (reMatchShouldBeGeneratorError.test(e.message)) {
|
@@ -92,7 +101,7 @@ var console = { history: [] };
|
|
92
101
|
}
|
93
102
|
|
94
103
|
var shouldBeGeneratorError = lineOne +
|
95
|
-
'true, but the
|
104
|
+
'true, but the React component \\'#{component_name}\\' is not a generator function.\\n' +
|
96
105
|
lastLine;
|
97
106
|
var reMatchShouldNotBeGeneratorError = /Cannot call a class as a function/;
|
98
107
|
if (reMatchShouldNotBeGeneratorError.test(e.message)) {
|
@@ -100,25 +109,31 @@ var console = { history: [] };
|
|
100
109
|
console.error(shouldBeGeneratorError);
|
101
110
|
}
|
102
111
|
|
103
|
-
|
112
|
+
#{render_error_messages}
|
113
|
+
}
|
114
|
+
JS
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def self.render_error_messages
|
120
|
+
<<-JS
|
121
|
+
console.error('Exception in rendering!');
|
104
122
|
if (e.fileName) {
|
105
|
-
console.error('
|
123
|
+
console.error('location: ' + e.fileName + ':' + e.lineNumber);
|
106
124
|
}
|
107
|
-
console.error('
|
108
|
-
console.error('
|
109
|
-
msg += '
|
125
|
+
console.error('message: ' + e.message);
|
126
|
+
console.error('stack: ' + e.stack);
|
127
|
+
msg += 'Exception in rendering!\\n' +
|
110
128
|
(e.fileName ? '\\nlocation: ' + e.fileName + ':' + e.lineNumber : '') +
|
111
129
|
'\\nMessage: ' + e.message + '\\n\\n' + e.stack;
|
112
130
|
|
113
131
|
var reactElement = React.createElement('pre', null, msg);
|
114
132
|
result = React.renderToString(reactElement);
|
115
|
-
}
|
116
133
|
JS
|
117
134
|
end
|
118
135
|
|
119
|
-
|
120
|
-
|
121
|
-
def after_render
|
136
|
+
def console_replay_js_code
|
122
137
|
@replay_console ? CONSOLE_REPLAY : ""
|
123
138
|
end
|
124
139
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: react_on_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Gordon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-09-
|
11
|
+
date: 2015-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|