Skip to content

[testing] Execjs runtime benchmarks #290

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
68 changes: 68 additions & 0 deletions benchmarks/server_rendering_benchmark.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# - `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 = 1000

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 = thread_size.times.map do
Thread.new do
renders.times do
React::ServerRendering.render("SlowComponent", {}, {})
Thread.pass
end
end
end
threads.map(&:join)
else
renders.times.map do
React::ServerRendering.render("SlowComponent", {}, {})
end
end
end

RUNTIMES = [
ExecJS::Runtimes::RubyRacer,
ExecJS::Runtimes::Duktape,
ExecJS::Runtimes::JavaScriptCore,
ExecJS::Runtimes::Node,
]

Benchmark.bm(45) do |x|
[true, false].each do |threaded|
[1, 10, 25].each do |pool_size|
[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
1 change: 1 addition & 0 deletions lib/react/server_rendering.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'connection_pool'
require 'react/server_rendering/sprockets_renderer'
require 'react/server_rendering/exec_js_renderer'

module React
module ServerRendering
Expand Down
31 changes: 31 additions & 0 deletions lib/react/server_rendering/exec_js_renderer.rb
Original file line number Diff line number Diff line change
@@ -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
11 changes: 10 additions & 1 deletion lib/react/server_rendering/sprockets_renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@ def initialize(options={})
js_code = GLOBAL_WRAPPER + CONSOLE_POLYFILL

filenames.each do |filename|
js_code << ::Rails.application.assets[filename].to_s
js_code << load_asset(filename)
end

@context = ExecJS.compile(js_code)
end

def load_asset(file)
if ::Rails.application.config.assets.compile
::Rails.application.assets[file].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)
# pass prerender: :static to use renderToStaticMarkup
react_render_method = if prerender_options == :static
Expand Down