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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 829e8c7c5f08f5a219d996d2356c0bc6a0125b42
4
- data.tar.gz: 1d5bf7c969a5f182e2f81bee3626f4f2acd0ff85
3
+ metadata.gz: bc9e4d7fb4efbba5c82f8c056cdc4a25dfd66752
4
+ data.tar.gz: 4a06b66bf0a5d1b7e88e3bc78d573aafab298994
5
5
  SHA512:
6
- metadata.gz: fa2ef614a94bba6bcda082f6398889732ade8cc7c8885ba305fdee9638a599ac3b0e85d77175c4c2a05875463c3a654b76037de58830276c9b144ccb2bec9e39
7
- data.tar.gz: 468c2014a1132b53ee9de227a10a30a4f4a07f435b1caa4ee64311541d1bb8ce034c235aaa5b51497e60f7ed852bf7fec499eed7b2d90b72e4e3b5bb50751ff8
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 off server caching, run the server like:
194
- `export RAILS_USE_CACHE=N && foreman start`
195
- 8. If you click back and forth between the about and react page links, you can see the rails console
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 --tag --release
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
- install_render_events = turbolinks_loaded ? turbolinks_bootstrap(dom_id) : non_turbolinks_bootstrap
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
- #{define_render_if_dom_node_present(react_component_name, data_variable_name, dom_id,
63
- trace(options), generator_function(options))}
64
- #{install_render_events}
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, data_variable, dom_id)
100
- if prerender(options)
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
- # Takes javascript code and returns the output from it. This is called by react_component, which
120
- # sets up the JS code for rendering a react component.
121
- # This method could be used by itself to render the output of any javascript that returns a
122
- # string of proper HTML.
123
- def render_js(js_expression, options = {})
124
- result = render_js_internal(js_expression, options)
125
- "#{result[0]}\n#{result[1]}".html_safe
126
- end
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
- private
129
- # Takes javascript code and returns the output from it. This is called by react_component, which
130
- # sets up the JS code for rendering a react component.
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.render(js_code)
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
@@ -1,3 +1,3 @@
1
1
  module ReactOnRails
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.8"
3
3
  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.7
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-28 00:00:00.000000000 Z
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