react_on_rails 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +17 -6
- data/app/assets/javascripts/react_on_rails.js +153 -0
- data/app/helpers/react_on_rails_helper.rb +57 -126
- data/lib/react_on_rails/server_rendering_pool.rb +46 -2
- data/lib/react_on_rails/version.rb +1 -1
- metadata +3 -3
- data/lib/react_on_rails/react_renderer.rb +0 -137
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc9e4d7fb4efbba5c82f8c056cdc4a25dfd66752
|
4
|
+
data.tar.gz: 4a06b66bf0a5d1b7e88e3bc78d573aafab298994
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4deb110a525ad5bfd570a55ccdedf03807b1cd743126b479773d8d5431e9f4ce5e23204efa881af86b2fc38175c4e27164f98ff79c7489ca0c18b0cb3bf8b943
|
7
|
+
data.tar.gz: 75de61e4804311370c9c86370b079a0456a74e0d638416bf7a65c1333d09a15d745513f090441725ae74d249b15b8b803b5c7692a96d7887729c1ad06b86c789
|
data/README.md
CHANGED
@@ -56,6 +56,11 @@ And then execute:
|
|
56
56
|
|
57
57
|
$ bundle
|
58
58
|
|
59
|
+
## What Happens?
|
60
|
+
|
61
|
+
Here's what the browser will render with a call to the `react_component` helper.
|
62
|
+
![2015-09-28_20-24-35](https://cloud.githubusercontent.com/assets/1118459/10157268/41435186-6624-11e5-9341-6fc4cf35ee90.png)
|
63
|
+
|
59
64
|
## Usage
|
60
65
|
|
61
66
|
*See section below titled "Try it out"*
|
@@ -182,17 +187,17 @@ Contributions and pull requests welcome!
|
|
182
187
|
npm i
|
183
188
|
foreman start
|
184
189
|
```
|
185
|
-
2. Caching is turned for development mode. Open the console and run `Rails.cache.clear` to clear
|
186
|
-
the cache. Note, even if you stop the server, you'll still have the cache entries around.
|
187
190
|
3. Visit http://localhost:3000
|
188
191
|
4. Notice that the first time you hit the page, you'll see a message that server is rendering.
|
189
192
|
See `spec/dummy/app/views/pages/index.html.erb:17` for the generation of that message.
|
190
193
|
5. Look at the layouts in `spec/dummy/app/views/pages` for samples of usage.
|
191
194
|
5. Open up the browser console and see some tracing.
|
192
195
|
6. Open up the source for the page and see the server rendered code.
|
193
|
-
7. If you want to turn
|
194
|
-
`export RAILS_USE_CACHE=
|
195
|
-
|
196
|
+
7. If you want to turn on server caching for development, run the server like:
|
197
|
+
`export RAILS_USE_CACHE=YES && foreman start`
|
198
|
+
2. If you're testing with caching, you'll need to open the console and run `Rails.cache.clear` to clear
|
199
|
+
the cache. Note, even if you stop the server, you'll still have the cache entries around.
|
200
|
+
8. If you click back and forth between the react page links, you can see the rails console
|
196
201
|
log as well as the browser console to see what's going on with regards to server rendering and
|
197
202
|
caching.
|
198
203
|
|
@@ -297,7 +302,13 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
297
302
|
See https://github.com/svenfuchs/gem-release
|
298
303
|
|
299
304
|
```bash
|
300
|
-
gem bump
|
305
|
+
gem bump
|
306
|
+
cd spec/dummy
|
307
|
+
bundle
|
308
|
+
git commit -am "Updated Gemfile.lock"
|
309
|
+
cd ../..
|
310
|
+
gem tag
|
311
|
+
gem release
|
301
312
|
```
|
302
313
|
|
303
314
|
# Authors
|
@@ -0,0 +1,153 @@
|
|
1
|
+
(function() {
|
2
|
+
this.ReactOnRails = {};
|
3
|
+
|
4
|
+
ReactOnRails.clientRenderReactComponent = function(options) {
|
5
|
+
var componentName = options.componentName;
|
6
|
+
var domId = options.domId;
|
7
|
+
var propsVarName = options.propsVarName;
|
8
|
+
var props = options.props;
|
9
|
+
var trace = options.trace;
|
10
|
+
var generatorFunction = options.generatorFunction;
|
11
|
+
var expectTurboLinks = options.expectTurboLinks;
|
12
|
+
|
13
|
+
this[propsVarName] = props;
|
14
|
+
|
15
|
+
var renderIfDomNodePresent = function() {
|
16
|
+
try {
|
17
|
+
var domNode = document.getElementById(domId);
|
18
|
+
if (domNode) {
|
19
|
+
var reactElement = createReactElement(componentName, propsVarName, props,
|
20
|
+
domId, trace, generatorFunction);
|
21
|
+
React.render(reactElement, domNode);
|
22
|
+
}
|
23
|
+
}
|
24
|
+
catch (e) {
|
25
|
+
handleError(e, componentName);
|
26
|
+
}
|
27
|
+
};
|
28
|
+
|
29
|
+
var turbolinksInstalled = typeof(Turbolinks) !== 'undefined';
|
30
|
+
if (!expectTurboLinks || (!turbolinksInstalled && expectTurboLinks)) {
|
31
|
+
if (expectTurboLinks) {
|
32
|
+
console.warn("WARNING: NO TurboLinks detected in JS, but it's in your Gemfile");
|
33
|
+
}
|
34
|
+
document.addEventListener("DOMContentLoaded", function(event) {
|
35
|
+
renderIfDomNodePresent();
|
36
|
+
});
|
37
|
+
} else {
|
38
|
+
function onPageChange(event) {
|
39
|
+
var removePageChangeListener = function() {
|
40
|
+
document.removeEventListener("page:change", onPageChange);
|
41
|
+
document.removeEventListener("page:before-unload", removePageChangeListener);
|
42
|
+
var domNode = document.getElementById(domId);
|
43
|
+
React.unmountComponentAtNode(domNode);
|
44
|
+
};
|
45
|
+
document.addEventListener("page:before-unload", removePageChangeListener);
|
46
|
+
|
47
|
+
renderIfDomNodePresent();
|
48
|
+
}
|
49
|
+
|
50
|
+
document.addEventListener("page:change", onPageChange);
|
51
|
+
}
|
52
|
+
};
|
53
|
+
|
54
|
+
ReactOnRails.serverRenderReactComponent = function(options) {
|
55
|
+
var componentName = options.componentName;
|
56
|
+
var domId = options.domId;
|
57
|
+
var propsVarName = options.propsVarName;
|
58
|
+
var props = options.props;
|
59
|
+
var trace = options.trace;
|
60
|
+
var generatorFunction = options.generatorFunction;
|
61
|
+
|
62
|
+
var htmlResult = '';
|
63
|
+
var consoleReplay = '';
|
64
|
+
|
65
|
+
try {
|
66
|
+
var reactElement = createReactElement(componentName, propsVarName, props,
|
67
|
+
domId, trace, generatorFunction);
|
68
|
+
htmlResult = React.renderToString(reactElement);
|
69
|
+
}
|
70
|
+
catch (e) {
|
71
|
+
htmlResult = handleError(e, componentName);
|
72
|
+
}
|
73
|
+
|
74
|
+
consoleReplay = ReactOnRails.buildConsoleReplay();
|
75
|
+
return JSON.stringify([htmlResult, consoleReplay]);
|
76
|
+
};
|
77
|
+
|
78
|
+
function createReactElement(componentName, propsVarName, props, domId, trace, generatorFunction) {
|
79
|
+
if (trace) {
|
80
|
+
console.log('RENDERED ' + componentName + ' with data_variable ' +
|
81
|
+
propsVarName + ' to dom node with id: ' + domId);
|
82
|
+
}
|
83
|
+
|
84
|
+
if (generatorFunction) {
|
85
|
+
return this[componentName](props);
|
86
|
+
} else {
|
87
|
+
return React.createElement(this[componentName], props);
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
// Passing either componentName or jsCode
|
92
|
+
function handleError(e, componentName, jsCode) {
|
93
|
+
var lineOne =
|
94
|
+
'ERROR: You specified the option generator_function (could be in your defaults) to be\n';
|
95
|
+
var lastLine =
|
96
|
+
'A generator function takes a single arg of props and returns a ReactElement.';
|
97
|
+
|
98
|
+
console.error('Exception in rendering!');
|
99
|
+
|
100
|
+
var msg = '';
|
101
|
+
if (componentName) {
|
102
|
+
var shouldBeGeneratorError = lineOne +
|
103
|
+
'false, but the React component \'' + componentName + '\' seems to be a generator function.\n' +
|
104
|
+
lastLine;
|
105
|
+
var reMatchShouldBeGeneratorError = /Can't add property context, object is not extensible/;
|
106
|
+
if (reMatchShouldBeGeneratorError.test(e.message)) {
|
107
|
+
msg += shouldBeGeneratorError + '\n\n';
|
108
|
+
console.error(shouldBeGeneratorError);
|
109
|
+
}
|
110
|
+
|
111
|
+
var shouldBeGeneratorError = lineOne +
|
112
|
+
'true, but the React component \'' + componentName + '\' is not a generator function.\n' +
|
113
|
+
lastLine;
|
114
|
+
var reMatchShouldNotBeGeneratorError = /Cannot call a class as a function/;
|
115
|
+
if (reMatchShouldNotBeGeneratorError.test(e.message)) {
|
116
|
+
msg += shouldBeGeneratorError + '\n\n';
|
117
|
+
console.error(shouldBeGeneratorError);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
if (jsCode) {
|
122
|
+
console.error('JS code was: ' + jsCode);
|
123
|
+
}
|
124
|
+
|
125
|
+
if (e.fileName) {
|
126
|
+
console.error('location: ' + e.fileName + ':' + e.lineNumber);
|
127
|
+
}
|
128
|
+
console.error('message: ' + e.message);
|
129
|
+
console.error('stack: ' + e.stack);
|
130
|
+
msg += 'Exception in rendering!\n' +
|
131
|
+
(e.fileName ? '\nlocation: ' + e.fileName + ':' + e.lineNumber : '') +
|
132
|
+
'\nMessage: ' + e.message + '\n\n' + e.stack;
|
133
|
+
|
134
|
+
var reactElement = React.createElement('pre', null, msg);
|
135
|
+
return React.renderToString(reactElement);
|
136
|
+
}
|
137
|
+
|
138
|
+
ReactOnRails.buildConsoleReplay = function() {
|
139
|
+
var consoleReplay = '';
|
140
|
+
|
141
|
+
var history = console.history;
|
142
|
+
if (history && history.length > 0) {
|
143
|
+
consoleReplay += '\n<script>';
|
144
|
+
history.forEach(function(msg) {
|
145
|
+
consoleReplay += '\nconsole.' + msg.level + '.apply(console, ' +
|
146
|
+
JSON.stringify(msg.arguments) + ');';
|
147
|
+
});
|
148
|
+
consoleReplay += '\n</script>';
|
149
|
+
}
|
150
|
+
|
151
|
+
return consoleReplay;
|
152
|
+
}
|
153
|
+
}.call(this));
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require "react_on_rails/react_renderer"
|
2
|
-
|
3
1
|
# NOTE:
|
4
2
|
# For any heredoc JS:
|
5
3
|
# 1. The white spacing in this file matters!
|
@@ -54,14 +52,20 @@ module ReactOnRailsHelper
|
|
54
52
|
# The reason is that React is smart about not doing extra work if the server rendering did its job.
|
55
53
|
data_variable_name = "__#{component_name.camelize(:lower)}Data#{react_component_index}__"
|
56
54
|
turbolinks_loaded = Object.const_defined?(:Turbolinks)
|
57
|
-
|
55
|
+
# NOTE: props might include closing script tag that might cause XSS
|
58
56
|
props_string = props.is_a?(String) ? props : props.to_json
|
59
57
|
page_loaded_js = <<-JS
|
60
58
|
(function() {
|
61
59
|
window.#{data_variable_name} = #{props_string};
|
62
|
-
|
63
|
-
|
64
|
-
#{
|
60
|
+
ReactOnRails.clientRenderReactComponent({
|
61
|
+
componentName: '#{react_component_name}',
|
62
|
+
domId: '#{dom_id}',
|
63
|
+
propsVarName: '#{data_variable_name}',
|
64
|
+
props: window.#{data_variable_name},
|
65
|
+
trace: #{trace(options)},
|
66
|
+
generatorFunction: #{generator_function(options)},
|
67
|
+
expectTurboLinks: #{turbolinks_loaded}
|
68
|
+
});
|
65
69
|
})();
|
66
70
|
JS
|
67
71
|
|
@@ -89,6 +93,35 @@ module ReactOnRailsHelper
|
|
89
93
|
HTML
|
90
94
|
end
|
91
95
|
|
96
|
+
# Helper method to take javascript expression and returns the output from evaluating it.
|
97
|
+
# If you have more than one line that needs to be executed, wrap it in an IIFE.
|
98
|
+
# JS exceptions are caught and console messages are handled properly.
|
99
|
+
def server_render_js(js_expression, options = {})
|
100
|
+
wrapper_js = <<-JS
|
101
|
+
(function() {
|
102
|
+
var htmlResult = '';
|
103
|
+
var consoleReplay = '';
|
104
|
+
|
105
|
+
try {
|
106
|
+
htmlResult =
|
107
|
+
(function() {
|
108
|
+
return #{js_expression};
|
109
|
+
})();
|
110
|
+
} catch(e) {
|
111
|
+
htmlResult = handleError(e, null, '#{escape_javascript(js_expression)}');
|
112
|
+
}
|
113
|
+
|
114
|
+
consoleReplay = ReactOnRails.buildConsoleReplay();
|
115
|
+
return JSON.stringify([htmlResult, consoleReplay]);
|
116
|
+
})()
|
117
|
+
JS
|
118
|
+
|
119
|
+
result = ReactOnRails::ServerRenderingPool.server_render_js_with_console_logging(wrapper_js)
|
120
|
+
"#{result[0]}\n#{result[1]}".html_safe
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
92
125
|
def next_react_component_index
|
93
126
|
@react_component_index ||= -1
|
94
127
|
@react_component_index += 1
|
@@ -96,49 +129,28 @@ module ReactOnRailsHelper
|
|
96
129
|
|
97
130
|
# Returns Array [0]: html, [1]: script to console log
|
98
131
|
# NOTE, these are NOT html_safe!
|
99
|
-
def server_rendered_react_component_html(options, props_string, react_component_name,
|
100
|
-
|
101
|
-
render_js_expression = <<-JS
|
102
|
-
(function(React) {
|
103
|
-
#{debug_js(react_component_name, data_variable, dom_id, trace(options))}
|
104
|
-
var reactElement = #{render_js_react_element(react_component_name, props_string, generator_function(options))}
|
105
|
-
return React.renderToString(reactElement);
|
106
|
-
})(this.React);
|
107
|
-
JS
|
108
|
-
# create the server generated html of the react component with props
|
109
|
-
options[:react_component_name] = react_component_name
|
110
|
-
options[:server_side] = true
|
111
|
-
render_js_internal(render_js_expression, options)
|
112
|
-
else
|
113
|
-
["",""]
|
114
|
-
end
|
115
|
-
rescue ExecJS::ProgramError => err
|
116
|
-
raise ReactOnRails::ServerRenderingPool::PrerenderError.new(react_component_name, props_string, err)
|
117
|
-
end
|
132
|
+
def server_rendered_react_component_html(options, props_string, react_component_name, data_variable_name, dom_id)
|
133
|
+
return ["", ""] unless prerender(options)
|
118
134
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
135
|
+
wrapper_js = <<-JS
|
136
|
+
(function() {
|
137
|
+
var props = #{props_string};
|
138
|
+
return ReactOnRails.serverRenderReactComponent({
|
139
|
+
componentName: '#{react_component_name}',
|
140
|
+
domId: '#{dom_id}',
|
141
|
+
propsVarName: '#{data_variable_name}',
|
142
|
+
props: props,
|
143
|
+
trace: #{trace(options)},
|
144
|
+
generatorFunction: #{generator_function(options)}
|
145
|
+
});
|
146
|
+
})()
|
147
|
+
JS
|
127
148
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
# This method could be used by itself to render the output of any javascript that returns a
|
132
|
-
# string of proper HTML.
|
133
|
-
# Returns Array [0]: html, [1]: script to console log
|
134
|
-
def render_js_internal(js_expression, options = {})
|
135
|
-
# TODO: This should be changed so that we don't create a new context every time
|
136
|
-
# Example of doing this here: https://github.com/reactjs/react-rails/tree/master/lib/react/rails
|
137
|
-
ReactOnRails::ReactRenderer.render_js(js_expression,
|
138
|
-
options)
|
149
|
+
ReactOnRails::ServerRenderingPool.server_render_js_with_console_logging(wrapper_js)
|
150
|
+
rescue ExecJS::ProgramError => err
|
151
|
+
raise ReactOnRails::ServerRenderingPool::PrerenderError.new(react_component_name, props_string, err)
|
139
152
|
end
|
140
153
|
|
141
|
-
|
142
154
|
def trace(options)
|
143
155
|
options.fetch(:trace) { ReactOnRails.configuration.trace }
|
144
156
|
end
|
@@ -154,85 +166,4 @@ module ReactOnRailsHelper
|
|
154
166
|
def replay_console(options)
|
155
167
|
options.fetch(:replay_console) { ReactOnRails.configuration.replay_console }
|
156
168
|
end
|
157
|
-
|
158
|
-
def debug_js(react_component_name, data_variable, dom_id, trace)
|
159
|
-
if trace
|
160
|
-
"console.log(\"RENDERED #{react_component_name} with data_variable"\
|
161
|
-
" #{data_variable} to dom node with id: #{dom_id}\");"
|
162
|
-
else
|
163
|
-
""
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
# react_component_name: See app/helpers/react_on_rails_helper.rb:5
|
168
|
-
# props_string: is either the variable name used to hold the props (client side) or the
|
169
|
-
# stringified hash of props from the Ruby server side. In terms of the view helper, one is
|
170
|
-
# simply passing in the Ruby Hash of props.
|
171
|
-
#
|
172
|
-
# Returns the JavaScript code to generate a React element.
|
173
|
-
def render_js_react_element(react_component_name, props_string, generator_function)
|
174
|
-
# "this" is defined by the calling context which is "global" in the execJs
|
175
|
-
# environment or window in the client side context.
|
176
|
-
js_create_element = if generator_function
|
177
|
-
"#{react_component_name}(props)"
|
178
|
-
else
|
179
|
-
"React.createElement(#{react_component_name}, props)"
|
180
|
-
end
|
181
|
-
|
182
|
-
<<-JS
|
183
|
-
(function(React) {
|
184
|
-
var props = #{props_string};
|
185
|
-
return #{js_create_element};
|
186
|
-
})(this.React);
|
187
|
-
JS
|
188
|
-
end
|
189
|
-
|
190
|
-
def define_render_if_dom_node_present(react_component_name, data_variable, dom_id, trace, generator_function)
|
191
|
-
inner_js_code = <<-JS_CODE
|
192
|
-
var domNode = document.getElementById('#{dom_id}');
|
193
|
-
if (domNode) {
|
194
|
-
#{debug_js(react_component_name, data_variable, dom_id, trace)}
|
195
|
-
var reactElement = #{render_js_react_element(react_component_name, data_variable, generator_function)}
|
196
|
-
React.render(reactElement, domNode);
|
197
|
-
}
|
198
|
-
JS_CODE
|
199
|
-
|
200
|
-
<<-JS
|
201
|
-
var renderIfDomNodePresent = function() {
|
202
|
-
#{ReactOnRails::ReactRenderer.wrap_code_with_exception_handler(inner_js_code, react_component_name)}
|
203
|
-
}
|
204
|
-
JS
|
205
|
-
end
|
206
|
-
|
207
|
-
def non_turbolinks_bootstrap
|
208
|
-
<<-JS
|
209
|
-
document.addEventListener("DOMContentLoaded", function(event) {
|
210
|
-
console.log("DOMContentLoaded event fired");
|
211
|
-
renderIfDomNodePresent();
|
212
|
-
});
|
213
|
-
JS
|
214
|
-
end
|
215
|
-
|
216
|
-
def turbolinks_bootstrap(dom_id)
|
217
|
-
<<-JS
|
218
|
-
var turbolinksInstalled = typeof(Turbolinks) !== 'undefined';
|
219
|
-
if (!turbolinksInstalled) {
|
220
|
-
console.warn("WARNING: NO TurboLinks detected in JS, but it's in your Gemfile");
|
221
|
-
#{non_turbolinks_bootstrap}
|
222
|
-
} else {
|
223
|
-
function onPageChange(event) {
|
224
|
-
var removePageChangeListener = function() {
|
225
|
-
document.removeEventListener("page:change", onPageChange);
|
226
|
-
document.removeEventListener("page:before-unload", removePageChangeListener);
|
227
|
-
var domNode = document.getElementById('#{dom_id}');
|
228
|
-
React.unmountComponentAtNode(domNode);
|
229
|
-
};
|
230
|
-
document.addEventListener("page:before-unload", removePageChangeListener);
|
231
|
-
|
232
|
-
renderIfDomNodePresent();
|
233
|
-
}
|
234
|
-
document.addEventListener("page:change", onPageChange);
|
235
|
-
}
|
236
|
-
JS
|
237
|
-
end
|
238
169
|
end
|
@@ -9,9 +9,11 @@ module ReactOnRails
|
|
9
9
|
@@js_context_pool = ConnectionPool.new(options) { create_js_context }
|
10
10
|
end
|
11
11
|
|
12
|
-
def self.
|
12
|
+
def self.eval_js(js_code)
|
13
13
|
@@js_context_pool.with do |js_context|
|
14
|
-
js_context.eval(js_code)
|
14
|
+
result = js_context.eval(js_code)
|
15
|
+
js_context.eval(CLEAR_CONSOLE)
|
16
|
+
result
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
@@ -22,6 +24,7 @@ module ReactOnRails
|
|
22
24
|
base_js_code = <<-JS
|
23
25
|
#{CONSOLE_POLYFILL}
|
24
26
|
#{bundle_js_code};
|
27
|
+
#{::Rails.application.assets['react_on_rails.js'].to_s};
|
25
28
|
JS
|
26
29
|
ExecJS.compile(base_js_code)
|
27
30
|
else
|
@@ -32,6 +35,10 @@ module ReactOnRails
|
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
38
|
+
CLEAR_CONSOLE = <<-JS
|
39
|
+
console.history = []
|
40
|
+
JS
|
41
|
+
|
35
42
|
# Reimplement console methods for replaying on the client
|
36
43
|
CONSOLE_POLYFILL = <<-JS
|
37
44
|
var console = { history: [] };
|
@@ -53,5 +60,42 @@ var console = { history: [] };
|
|
53
60
|
super(message)
|
54
61
|
end
|
55
62
|
end
|
63
|
+
|
64
|
+
# js_code: JavaScript expression that returns a string.
|
65
|
+
# Returns an Array:
|
66
|
+
# [0]: string of HTML for direct insertion on the page by evaluating js_code
|
67
|
+
# [1]: console messages
|
68
|
+
# Note, js_code does not have to be based on React.
|
69
|
+
# js_code MUST RETURN json stringify array of two elements
|
70
|
+
# Calling code will probably call 'html_safe' on return value before rendering to the view.
|
71
|
+
def self.server_render_js_with_console_logging(js_code)
|
72
|
+
if ENV["TRACE_REACT_ON_RAILS"].present? # Set to anything to print generated code.
|
73
|
+
puts "Z" * 80
|
74
|
+
puts "react_renderer.rb: 92"
|
75
|
+
puts "wrote file tmp/server-generated.js"
|
76
|
+
File.write("tmp/server-generated.js", js_code)
|
77
|
+
puts "Z" * 80
|
78
|
+
end
|
79
|
+
|
80
|
+
json_string = eval_js(js_code)
|
81
|
+
# element 0 is the html, element 1 is the script tag for the server console output
|
82
|
+
result = JSON.parse(json_string)
|
83
|
+
|
84
|
+
if ReactOnRails.configuration.logging_on_server
|
85
|
+
console_script = result[1]
|
86
|
+
console_script_lines = console_script.split("\n")
|
87
|
+
console_script_lines = console_script_lines[2..-2]
|
88
|
+
re = /console\.log\.apply\(console, \["\[SERVER\] (?<msg>.*)"\]\);/
|
89
|
+
if console_script_lines
|
90
|
+
console_script_lines.each do |line|
|
91
|
+
match = re.match(line)
|
92
|
+
if match
|
93
|
+
Rails.logger.info { "[react_on_rails] #{match[:msg]}" }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
result
|
99
|
+
end
|
56
100
|
end
|
57
101
|
end
|
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.8
|
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-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -145,11 +145,11 @@ files:
|
|
145
145
|
- LICENSE.txt
|
146
146
|
- README.md
|
147
147
|
- Rakefile
|
148
|
+
- app/assets/javascripts/react_on_rails.js
|
148
149
|
- app/helpers/react_on_rails_helper.rb
|
149
150
|
- docker-compose.yml
|
150
151
|
- lib/react_on_rails.rb
|
151
152
|
- lib/react_on_rails/configuration.rb
|
152
|
-
- lib/react_on_rails/react_renderer.rb
|
153
153
|
- lib/react_on_rails/server_rendering_pool.rb
|
154
154
|
- lib/react_on_rails/version.rb
|
155
155
|
- react_on_rails.gemspec
|
@@ -1,137 +0,0 @@
|
|
1
|
-
# Kudos to react-rails for how to do the polyfill of the console!
|
2
|
-
# https://github.com/reactjs/react-rails/blob/master/lib/react/server_rendering/sprockets_renderer.rb
|
3
|
-
|
4
|
-
# require 'react_on_rails/server_rendering_pool'
|
5
|
-
|
6
|
-
module ReactOnRails
|
7
|
-
class ReactRenderer
|
8
|
-
# Script to write to the browser console.
|
9
|
-
# NOTE: result comes from enclosing closure and is the server generated HTML
|
10
|
-
# that we intend to write to the browser. Thus, the script tag will get executed right after
|
11
|
-
# the HTML is rendered.
|
12
|
-
CONSOLE_REPLAY = <<-JS
|
13
|
-
var history = console.history;
|
14
|
-
if (history && history.length > 0) {
|
15
|
-
consoleReplay += '\\n<script>';
|
16
|
-
history.forEach(function (msg) {
|
17
|
-
consoleReplay += '\\nconsole.' + msg.level + '.apply(console, ' + JSON.stringify(msg.arguments) + ');';
|
18
|
-
});
|
19
|
-
consoleReplay += '\\n</script>';
|
20
|
-
}
|
21
|
-
JS
|
22
|
-
|
23
|
-
DEBUGGER = <<-JS
|
24
|
-
if (typeof window !== 'undefined') { debugger; }
|
25
|
-
JS
|
26
|
-
|
27
|
-
# js_code: JavaScript expression that returns a string.
|
28
|
-
# Returns an Array:
|
29
|
-
# [0]: string of HTML for direct insertion on the page by evaluating js_code
|
30
|
-
# [1]: console messages
|
31
|
-
# Note, js_code does not have to be based on React.
|
32
|
-
# Calling code will probably call 'html_safe' on return value before rendering to the view.
|
33
|
-
def self.render_js(js_code, options = {})
|
34
|
-
component_name = options.fetch(:react_component_name, "")
|
35
|
-
server_side = options.fetch(:server_side, false)
|
36
|
-
|
37
|
-
result_js_code = " htmlResult = #{js_code}"
|
38
|
-
|
39
|
-
js_code_wrapper = <<-JS
|
40
|
-
(function () {
|
41
|
-
var htmlResult = '';
|
42
|
-
var consoleReplay = '';
|
43
|
-
#{ReactOnRails::ReactRenderer.wrap_code_with_exception_handler(result_js_code, component_name)}
|
44
|
-
#{console_replay_js_code(options)}
|
45
|
-
return JSON.stringify([htmlResult, consoleReplay]);
|
46
|
-
})()
|
47
|
-
JS
|
48
|
-
|
49
|
-
if ENV["TRACE_REACT_ON_RAILS"].present? # Set to anything to print generated code.
|
50
|
-
puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
51
|
-
puts "react_renderer.rb: 92"
|
52
|
-
puts "wrote file tmp/server-generated.js"
|
53
|
-
File.write("tmp/server-generated.js", js_code_wrapper)
|
54
|
-
puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
55
|
-
end
|
56
|
-
|
57
|
-
json_string = ReactOnRails::ServerRenderingPool.render(js_code_wrapper)
|
58
|
-
# element 0 is the html, element 1 is the script tag for the server console output
|
59
|
-
result = JSON.parse(json_string)
|
60
|
-
|
61
|
-
if ReactOnRails.configuration.logging_on_server
|
62
|
-
console_script = result[1]
|
63
|
-
console_script_lines = console_script.split("\n")
|
64
|
-
console_script_lines = console_script_lines[2..-2]
|
65
|
-
re = /console\.log\.apply\(console, \["\[SERVER\] (?<msg>.*)"\]\);/
|
66
|
-
if console_script_lines
|
67
|
-
console_script_lines.each do |line|
|
68
|
-
match = re.match(line)
|
69
|
-
if match
|
70
|
-
Rails.logger.info { "[react_on_rails] #{match[:msg]}" }
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
return result
|
76
|
-
end
|
77
|
-
|
78
|
-
def self.wrap_code_with_exception_handler(js_code, component_name)
|
79
|
-
<<-JS
|
80
|
-
try {
|
81
|
-
#{js_code}
|
82
|
-
}
|
83
|
-
catch(e) {
|
84
|
-
var lineOne =
|
85
|
-
'ERROR: You specified the option generator_function (could be in your defaults) to be\\n';
|
86
|
-
var lastLine =
|
87
|
-
'A generator function takes a single arg of props and returns a ReactElement.';
|
88
|
-
|
89
|
-
var msg = '';
|
90
|
-
var shouldBeGeneratorError = lineOne +
|
91
|
-
'false, but the React component \\'#{component_name}\\' seems to be a generator function.\\n' +
|
92
|
-
lastLine;
|
93
|
-
var reMatchShouldBeGeneratorError = /Can't add property context, object is not extensible/;
|
94
|
-
if (reMatchShouldBeGeneratorError.test(e.message)) {
|
95
|
-
msg += shouldBeGeneratorError + '\\n\\n';
|
96
|
-
console.error(shouldBeGeneratorError);
|
97
|
-
}
|
98
|
-
|
99
|
-
var shouldBeGeneratorError = lineOne +
|
100
|
-
'true, but the React component \\'#{component_name}\\' is not a generator function.\\n' +
|
101
|
-
lastLine;
|
102
|
-
var reMatchShouldNotBeGeneratorError = /Cannot call a class as a function/;
|
103
|
-
if (reMatchShouldNotBeGeneratorError.test(e.message)) {
|
104
|
-
msg += shouldBeGeneratorError + '\\n\\n';
|
105
|
-
console.error(shouldBeGeneratorError);
|
106
|
-
}
|
107
|
-
|
108
|
-
#{render_error_messages}
|
109
|
-
}
|
110
|
-
JS
|
111
|
-
end
|
112
|
-
|
113
|
-
private
|
114
|
-
|
115
|
-
def self.render_error_messages
|
116
|
-
<<-JS
|
117
|
-
console.error('Exception in rendering!');
|
118
|
-
if (e.fileName) {
|
119
|
-
console.error('location: ' + e.fileName + ':' + e.lineNumber);
|
120
|
-
}
|
121
|
-
console.error('message: ' + e.message);
|
122
|
-
console.error('stack: ' + e.stack);
|
123
|
-
msg += 'Exception in rendering!\\n' +
|
124
|
-
(e.fileName ? '\\nlocation: ' + e.fileName + ':' + e.lineNumber : '') +
|
125
|
-
'\\nMessage: ' + e.message + '\\n\\n' + e.stack;
|
126
|
-
|
127
|
-
var reactElement = React.createElement('pre', null, msg);
|
128
|
-
result = React.renderToString(reactElement);
|
129
|
-
JS
|
130
|
-
end
|
131
|
-
|
132
|
-
def self.console_replay_js_code(options)
|
133
|
-
replay_console = options.fetch(:replay_console) { ReactOnRails.configuration.replay_console }
|
134
|
-
(replay_console || ReactOnRails.configuration.logging_on_server) ? CONSOLE_REPLAY : ""
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|