# Kudos to react-rails for how to do the polyfill of the console!
# https://github.com/reactjs/react-rails/blob/master/lib/react/server_rendering/sprockets_renderer.rb
# require 'react_on_rails/server_rendering_pool'
module ReactOnRails
class ReactRenderer
# Script to write to the browser console.
# NOTE: result comes from enclosing closure and is the server generated HTML
# that we intend to write to the browser. Thus, the script tag will get executed right after
# the HTML is rendered.
CONSOLE_REPLAY = <<-JS
var history = console.history;
if (history && history.length > 0) {
consoleReplay += '\\n';
}
JS
DEBUGGER = <<-JS
if (typeof window !== 'undefined') { debugger; }
JS
# js_code: JavaScript expression that returns a string.
# Returns an Array:
# [0]: string of HTML for direct insertion on the page by evaluating js_code
# [1]: console messages
# Note, js_code does not have to be based on React.
# Calling code will probably call 'html_safe' on return value before rendering to the view.
def self.render_js(js_code, options = {})
component_name = options.fetch(:react_component_name, "")
server_side = options.fetch(:server_side, false)
result_js_code = " htmlResult = #{js_code}"
js_code_wrapper = <<-JS
(function () {
var htmlResult = '';
var consoleReplay = '';
#{ReactOnRails::ReactRenderer.wrap_code_with_exception_handler(result_js_code, component_name)}
#{console_replay_js_code(options)}
return JSON.stringify([htmlResult, consoleReplay]);
})()
JS
if ENV["TRACE_REACT_ON_RAILS"].present? # Set to anything to print generated code.
puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
puts "react_renderer.rb: 92"
puts "wrote file tmp/server-generated.js"
File.write("tmp/server-generated.js", js_code_wrapper)
puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
end
json_string = ReactOnRails::ServerRenderingPool.render(js_code_wrapper)
# element 0 is the html, element 1 is the script tag for the server console output
result = JSON.parse(json_string)
if ReactOnRails.configuration.logging_on_server
console_script = result[1]
console_script_lines = console_script.split("\n")
console_script_lines = console_script_lines[2..-2]
re = /console\.log\.apply\(console, \["\[SERVER\] (?.*)"\]\);/
if console_script_lines
console_script_lines.each do |line|
match = re.match(line)
if match
Rails.logger.info { "[react_on_rails] #{match[:msg]}" }
end
end
end
end
return result
end
def self.wrap_code_with_exception_handler(js_code, component_name)
<<-JS
try {
#{js_code}
}
catch(e) {
var lineOne =
'ERROR: You specified the option generator_function (could be in your defaults) to be\\n';
var lastLine =
'A generator function takes a single arg of props and returns a ReactElement.';
var msg = '';
var shouldBeGeneratorError = lineOne +
'false, but the React component \\'#{component_name}\\' seems to be a generator function.\\n' +
lastLine;
var reMatchShouldBeGeneratorError = /Can't add property context, object is not extensible/;
if (reMatchShouldBeGeneratorError.test(e.message)) {
msg += shouldBeGeneratorError + '\\n\\n';
console.error(shouldBeGeneratorError);
}
var shouldBeGeneratorError = lineOne +
'true, but the React component \\'#{component_name}\\' is not a generator function.\\n' +
lastLine;
var reMatchShouldNotBeGeneratorError = /Cannot call a class as a function/;
if (reMatchShouldNotBeGeneratorError.test(e.message)) {
msg += shouldBeGeneratorError + '\\n\\n';
console.error(shouldBeGeneratorError);
}
#{render_error_messages}
}
JS
end
private
def self.render_error_messages
<<-JS
console.error('Exception in rendering!');
if (e.fileName) {
console.error('location: ' + e.fileName + ':' + e.lineNumber);
}
console.error('message: ' + e.message);
console.error('stack: ' + e.stack);
msg += 'Exception in rendering!\\n' +
(e.fileName ? '\\nlocation: ' + e.fileName + ':' + e.lineNumber : '') +
'\\nMessage: ' + e.message + '\\n\\n' + e.stack;
var reactElement = React.createElement('pre', null, msg);
result = React.renderToString(reactElement);
JS
end
def self.console_replay_js_code(options)
replay_console = options.fetch(:replay_console) { ReactOnRails.configuration.replay_console }
(replay_console || ReactOnRails.configuration.logging_on_server) ? CONSOLE_REPLAY : ""
end
end
end