From 5a680c3b972e122ef5a92e67d8183e3f58da3dbc Mon Sep 17 00:00:00 2001 From: Matt Christen Date: Fri, 29 May 2015 20:32:22 -0700 Subject: [PATCH 1/7] Some PUTS for tracking progress --- lib/react/server_rendering/sprockets_renderer.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/react/server_rendering/sprockets_renderer.rb b/lib/react/server_rendering/sprockets_renderer.rb index 129285377..db1a6541e 100644 --- a/lib/react/server_rendering/sprockets_renderer.rb +++ b/lib/react/server_rendering/sprockets_renderer.rb @@ -8,13 +8,17 @@ def initialize(options={}) js_code = GLOBAL_WRAPPER + CONSOLE_POLYFILL filenames.each do |filename| + puts "LOADING #{filename}" js_code << ::Rails.application.assets[filename].to_s end + puts "BEFORE COMPILE" @context = ExecJS.compile(js_code) + puts "AFTER COMPILE" end def render(component_name, props, prerender_options) + puts "BEGIN RENDER" # pass prerender: :static to use renderToStaticMarkup react_render_method = if prerender_options == :static "renderToStaticMarkup" @@ -34,7 +38,9 @@ def render(component_name, props, prerender_options) })() JS + puts "BEFORE EVAL" @context.eval(js_code).html_safe + puts "END RENDER" rescue ExecJS::ProgramError => err raise PrerenderError.new(component_name, props, err) end From 4c0e1ce5a59829ae6919b9852ce116e77c8d2ae2 Mon Sep 17 00:00:00 2001 From: Matt Christen Date: Sat, 30 May 2015 11:48:54 -0700 Subject: [PATCH 2/7] Load pre-compiled assets if compiling is turned off --- .../server_rendering/sprockets_renderer.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/react/server_rendering/sprockets_renderer.rb b/lib/react/server_rendering/sprockets_renderer.rb index db1a6541e..cc163eba7 100644 --- a/lib/react/server_rendering/sprockets_renderer.rb +++ b/lib/react/server_rendering/sprockets_renderer.rb @@ -8,17 +8,22 @@ def initialize(options={}) js_code = GLOBAL_WRAPPER + CONSOLE_POLYFILL filenames.each do |filename| - puts "LOADING #{filename}" - js_code << ::Rails.application.assets[filename].to_s + js_code << load_asset(filename) end - puts "BEFORE COMPILE" @context = ExecJS.compile(js_code) - puts "AFTER COMPILE" + end + + def load_asset(file) + if ::Rails.application.config.assets.compile + ::Rails.application.assets[filename].to_s + else + asset_path = ActionView::Base.new.asset_path(file) + File.read(File.join(::Rails.public_path, asset_path)) + end end def render(component_name, props, prerender_options) - puts "BEGIN RENDER" # pass prerender: :static to use renderToStaticMarkup react_render_method = if prerender_options == :static "renderToStaticMarkup" @@ -38,9 +43,7 @@ def render(component_name, props, prerender_options) })() JS - puts "BEFORE EVAL" @context.eval(js_code).html_safe - puts "END RENDER" rescue ExecJS::ProgramError => err raise PrerenderError.new(component_name, props, err) end From 2ac01d93eee694a6694b84467980f7cad46ee678 Mon Sep 17 00:00:00 2001 From: Matt Christen Date: Sat, 30 May 2015 12:26:35 -0700 Subject: [PATCH 3/7] Bugfix for compiling assets --- lib/react/server_rendering/sprockets_renderer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/react/server_rendering/sprockets_renderer.rb b/lib/react/server_rendering/sprockets_renderer.rb index cc163eba7..100c0e538 100644 --- a/lib/react/server_rendering/sprockets_renderer.rb +++ b/lib/react/server_rendering/sprockets_renderer.rb @@ -16,7 +16,7 @@ def initialize(options={}) def load_asset(file) if ::Rails.application.config.assets.compile - ::Rails.application.assets[filename].to_s + ::Rails.application.assets[file].to_s else asset_path = ActionView::Base.new.asset_path(file) File.read(File.join(::Rails.public_path, asset_path)) From 373f3e00a069fddf10c7c36f713461f93e5037b5 Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Mon, 8 Jun 2015 10:40:12 -0700 Subject: [PATCH 4/7] feat(ExecJSRenderer) add ExecJSRenderer --- lib/react/server_rendering.rb | 1 + .../server_rendering/exec_js_renderer.rb | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 lib/react/server_rendering/exec_js_renderer.rb diff --git a/lib/react/server_rendering.rb b/lib/react/server_rendering.rb index c379be1ae..401e78efe 100644 --- a/lib/react/server_rendering.rb +++ b/lib/react/server_rendering.rb @@ -1,5 +1,6 @@ require 'connection_pool' require 'react/server_rendering/sprockets_renderer' +require 'react/server_rendering/exec_js_renderer' module React module ServerRendering diff --git a/lib/react/server_rendering/exec_js_renderer.rb b/lib/react/server_rendering/exec_js_renderer.rb new file mode 100644 index 000000000..ba851543c --- /dev/null +++ b/lib/react/server_rendering/exec_js_renderer.rb @@ -0,0 +1,31 @@ +module React + module ServerRendering + class ExecJSRenderer + def initialize(options={}) + js_code = options.fetch(:code) || raise("Pass `code:` option to instantiate a JS context!") + @context = ExecJS.compile(js_code) + end + + def render(component_name, props, prerender_options) + js_code = <<-JS + (function () { + var result = React.renderToString(React.createElement(#{component_name}, #{props})); + return result; + })() + JS + + @context.eval(js_code) + rescue ExecJS::ProgramError => err + raise PrerenderError.new(component_name, props, err) + end + + class PrerenderError < RuntimeError + def initialize(component_name, props, js_message) + message = ["Encountered error \"#{js_message}\" when prerendering #{component_name} with #{props}", + js_message.backtrace.join("\n")].join("\n") + super(message) + end + end + end + end +end From e843b97606523fcd06919016cc300ec5e6c26160 Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Mon, 8 Jun 2015 10:50:52 -0700 Subject: [PATCH 5/7] benchmark(ServerRendering) add benchmarks for different renderers --- benchmarks/server_rendering_benchmark.rb | 51 ++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 benchmarks/server_rendering_benchmark.rb diff --git a/benchmarks/server_rendering_benchmark.rb b/benchmarks/server_rendering_benchmark.rb new file mode 100644 index 000000000..cb616a0d6 --- /dev/null +++ b/benchmarks/server_rendering_benchmark.rb @@ -0,0 +1,51 @@ +# - `gem install duktape` +# - Remove `JavaScriptCore` from RUNTIMES if you're not on mac +# - `ruby -I lib benchmarks/server_rendering_benchmark.rb` + +require 'react-rails' +require 'duktape' +require 'benchmark' + +SLOW_COMPONENT = " +var SlowComponent = React.createClass({ + render: function() { + var rand = 0 + for (var i = 0; i < 10000; i++) { + rand = rand + (Math.random() * i) + } + return React.createElement('h1', rand + ' :)') + } +}) +" + +REACT_JS_PATH = File.expand_path("../../vendor/react/react.js", __FILE__) +JS_CODE = File.read(REACT_JS_PATH) + SLOW_COMPONENT + +React::ServerRendering.renderer = React::ServerRendering::ExecJSRenderer +React::ServerRendering.renderer_options = {code: JS_CODE} +React::ServerRendering.pool_timeout = 10 + +def test_runtime(runtime, renders:, pool_size:) + ExecJS.runtime = runtime + React::ServerRendering.pool_size = pool_size + React::ServerRendering.reset_pool + + renders.times do + React::ServerRendering.render("SlowComponent", {}, {}) + end +end + +RUNTIMES = [ + ExecJS::Runtimes::RubyRacer, + ExecJS::Runtimes::Duktape, + ExecJS::Runtimes::JavaScriptCore, + ExecJS::Runtimes::Node, +] + +Benchmark.bm(25) do |x| + [1, 10, 25].each do |pool_size| + RUNTIMES.each do |runtime| + x.report("#{runtime.name}, #{pool_size}x") { test_runtime(runtime, renders: 50, pool_size: pool_size)} + end + end +end \ No newline at end of file From bb781c5178968c8c9b218600de7705cea042bedd Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Mon, 8 Jun 2015 11:12:01 -0700 Subject: [PATCH 6/7] test(ServerRendering) benchmark with lots of threads --- benchmarks/server_rendering_benchmark.rb | 30 +++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/benchmarks/server_rendering_benchmark.rb b/benchmarks/server_rendering_benchmark.rb index cb616a0d6..4efca7bcc 100644 --- a/benchmarks/server_rendering_benchmark.rb +++ b/benchmarks/server_rendering_benchmark.rb @@ -23,15 +23,23 @@ React::ServerRendering.renderer = React::ServerRendering::ExecJSRenderer React::ServerRendering.renderer_options = {code: JS_CODE} -React::ServerRendering.pool_timeout = 10 +React::ServerRendering.pool_timeout = 1000 -def test_runtime(runtime, renders:, pool_size:) +def test_runtime(runtime, renders:, pool_size:, threaded:) ExecJS.runtime = runtime React::ServerRendering.pool_size = pool_size React::ServerRendering.reset_pool - - renders.times do - React::ServerRendering.render("SlowComponent", {}, {}) + if threaded + threads = renders.times.map do + Thread.new do + React::ServerRendering.render("SlowComponent", {}, {}) + end + end + threads.map(&:join) + else + renders.times.map do + React::ServerRendering.render("SlowComponent", {}, {}) + end end end @@ -42,10 +50,14 @@ def test_runtime(runtime, renders:, pool_size:) ExecJS::Runtimes::Node, ] -Benchmark.bm(25) do |x| - [1, 10, 25].each do |pool_size| - RUNTIMES.each do |runtime| - x.report("#{runtime.name}, #{pool_size}x") { test_runtime(runtime, renders: 50, pool_size: pool_size)} +Benchmark.bm(35) do |x| + [true, false].each do |threaded| + [1, 10, 25].each do |pool_size| + RUNTIMES.each do |runtime| + x.report("#{threaded ? "threaded, " : ""}#{pool_size} conn, #{runtime.name}") do + test_runtime(runtime, renders: 50, pool_size: pool_size, threaded: true) + end + end end end end \ No newline at end of file From f9a8b98e72b98de348a5ad3b2bfdd2084d04104b Mon Sep 17 00:00:00 2001 From: Matt Christen Date: Mon, 8 Jun 2015 13:23:39 -0700 Subject: [PATCH 7/7] Configurable Thread size; multiple requests per thread --- benchmarks/server_rendering_benchmark.rb | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/benchmarks/server_rendering_benchmark.rb b/benchmarks/server_rendering_benchmark.rb index 4efca7bcc..274d3a120 100644 --- a/benchmarks/server_rendering_benchmark.rb +++ b/benchmarks/server_rendering_benchmark.rb @@ -25,14 +25,17 @@ React::ServerRendering.renderer_options = {code: JS_CODE} React::ServerRendering.pool_timeout = 1000 -def test_runtime(runtime, renders:, pool_size:, threaded:) +def test_runtime(runtime, renders:, pool_size:, threaded:, thread_size:) ExecJS.runtime = runtime React::ServerRendering.pool_size = pool_size React::ServerRendering.reset_pool if threaded - threads = renders.times.map do + threads = thread_size.times.map do Thread.new do - React::ServerRendering.render("SlowComponent", {}, {}) + renders.times do + React::ServerRendering.render("SlowComponent", {}, {}) + Thread.pass + end end end threads.map(&:join) @@ -50,14 +53,16 @@ def test_runtime(runtime, renders:, pool_size:, threaded:) ExecJS::Runtimes::Node, ] -Benchmark.bm(35) do |x| +Benchmark.bm(45) do |x| [true, false].each do |threaded| [1, 10, 25].each do |pool_size| - RUNTIMES.each do |runtime| - x.report("#{threaded ? "threaded, " : ""}#{pool_size} conn, #{runtime.name}") do - test_runtime(runtime, renders: 50, pool_size: pool_size, threaded: true) + [1, 2, 4, 8].each do |thread_size| + RUNTIMES.each do |runtime| + x.report("#{threaded ? "threaded, " : ""}#{pool_size} conn, #{thread_size} threads, #{runtime.name}") do + test_runtime(runtime, renders: 100, pool_size: pool_size, threaded: true, thread_size: thread_size) + end end end end end -end \ No newline at end of file +end