diff --git a/lib/react/rails/railtie.rb b/lib/react/rails/railtie.rb index ad287f41d..0d8c1ae2b 100644 --- a/lib/react/rails/railtie.rb +++ b/lib/react/rails/railtie.rb @@ -10,6 +10,7 @@ class Railtie < ::Rails::Railtie config.react.addons = false # Server-side rendering config.react.max_renderers = 10 + config.react.benchmark = false config.react.timeout = 20 #seconds config.react.react_js = lambda {File.read(::Rails.application.assets.resolve('react.js'))} config.react.component_filenames = ['components.js'] @@ -72,7 +73,7 @@ class Railtie < ::Rails::Railtie do_setup = lambda do cfg = app.config.react React::Renderer.setup!( cfg.react_js, cfg.components_js, - {:size => cfg.size, :timeout => cfg.timeout}) + {:size => cfg.size, :timeout => cfg.timeout, :benchmark => cfg.benchmark}) end do_setup.call diff --git a/lib/react/renderer.rb b/lib/react/renderer.rb index 31a0bee1f..fb7342adc 100644 --- a/lib/react/renderer.rb +++ b/lib/react/renderer.rb @@ -13,17 +13,18 @@ def initialize(component_name, props, js_message) cattr_accessor :pool def self.setup!(react_js, components_js, args={}) - args.assert_valid_keys(:size, :timeout) + args.assert_valid_keys(:size, :timeout, :benchmark) @@react_js = react_js @@components_js = components_js @@pool.shutdown{} if @@pool reset_combined_js! + @@render_method = args[:benchmark] ? :render_with_benchmark : :render @@pool = ConnectionPool.new(:size => args[:size]||10, :timeout => args[:timeout]||20) { self.new } end def self.render(component, args={}) @@pool.with do |renderer| - renderer.render(component, args) + renderer.send(@@render_method, component, args) end end @@ -74,8 +75,18 @@ def render(component, args={}) }() JS context.eval(jscode).html_safe - rescue ExecJS::ProgramError => e + rescue ::ExecJS::ProgramError => e raise PrerenderError.new(component, react_props, e) end + + def render_with_benchmark(component, args={}) + result = '' + time = Benchmark.realtime do + result = self.render(component, args) + end + ::Rails.logger.info "[React::Renderer#render] Rendered #{component} with #{args} (#{(time*1000).round(1)}ms)" + result + end + end end diff --git a/test/react_renderer_test.rb b/test/react_renderer_test.rb index f01245a43..10f96c054 100644 --- a/test/react_renderer_test.rb +++ b/test/react_renderer_test.rb @@ -18,6 +18,32 @@ class ReactRendererTest < ActiveSupport::TestCase assert_match /data-react-checksum/, result end + test 'prerender errors are thrown with non-json-encoded string' do + not_json_string = %w{todo1 todo2 todo3}.to_s + + err = assert_raises React::Renderer::PrerenderError do + React::Renderer.render "TodoList", not_json_string + end + expected_message = 'Encountered error "Error: Invariant Violation: Tried to merge an object, instead got todo1,todo2,todo3." when prerendering TodoList with ["todo1", "todo2", "todo3"]' + assert_equal expected_message, err.message + end + + test 'Server rendering with a simple Ruby Hash' do + a_hash = {todos: %w{todo1 todo2 todo3}} + + result = React::Renderer.render "TodoList", a_hash + assert_match /todo1.*todo2.*todo3/, result + assert_match /data-react-checksum/, result + end + + test 'Server rendering with a complex Ruby Hash' do + a_hash_with_nest = {data: {some: {thing: [0,1,2]}}, todos: [:some,:nested,:data]} + + result = React::Renderer.render "TodoList", a_hash_with_nest + assert_match /some.*nested.*data/, result + assert_match /data-react-checksum/, result + end + test 'Rendering does not throw an exception when console log api is used' do %W(error info log warn).each do |fn| assert_nothing_raised(ExecJS::ProgramError) do diff --git a/test/view_helper_test.rb b/test/view_helper_test.rb index 7f3445528..0121c3d3e 100644 --- a/test/view_helper_test.rb +++ b/test/view_helper_test.rb @@ -49,6 +49,38 @@ class ViewHelperTest < ActionDispatch::IntegrationTest assert html.include?('data-foo="1"') end + test 'react_component prerender:true and a simple Ruby Hash does not raise' do + a_hash = {todos: %w{todo1 todo2 todo3}} + + assert_nothing_raised(StandardError) do + @helper.react_component('TodoList', a_hash, {prerender:true, tag: :span}) + end + end + + test 'react_component prerender:true and a complex Ruby Hash does not raise' do + a_hash = {data: {some: {thing: [0,1,2]}}, todos: [:some,:nested,:data]} + + assert_nothing_raised(StandardError) do + @helper.react_component('TodoList', a_hash, {prerender:true, tag: :span}) + end + end + + test 'react_component prerender:true and a simple Ruby Hash' do + a_hash = {todos: %w{todo1 todo2 todo3}} + + html = @helper.react_component('TodoList', a_hash, {prerender:true, tag: :span}) + assert html.match(/<\/span>/) + assert html.match(/todo1<\/li>/) + end + + test 'react_component prerender:true and a complex Ruby Hash' do + a_hash = {data: {some: {thing: [0,1,2]}}, todos: [:some,:nested,:data]} + + html = @helper.react_component('TodoList', a_hash, {prerender:true, tag: :span}) + assert html.match(/<\/span>/) + assert html.match(/some<\/li>/) + end + test 'react_ujs works with rendered HTML' do visit '/pages/1' assert page.has_content?('Hello Bob') @@ -98,7 +130,7 @@ class ViewHelperTest < ActionDispatch::IntegrationTest assert_match /data-react-checksum/, page.html assert_match /yep/, page.find("#status").text end - + test 'react server rendering does not include internal properties' do visit '/server/1' assert_no_match /tag=/, page.html