react_on_rails 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+

|
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
|