Skip to content

Commit 799ffce

Browse files
committed
feat(ServerRendering) add ServerRendering and SprocketsRenderer
1 parent 491715c commit 799ffce

File tree

7 files changed

+143
-0
lines changed

7 files changed

+143
-0
lines changed

Guardfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
guard :minitest do
2+
# with Minitest::Unit
3+
watch(%r{^test/(.*)\/?(.*)_test\.rb$})
4+
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
5+
watch(%r{^test/test_helper\.rb$}) { 'test' }
6+
end

lib/react-rails.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
require 'react/renderer'
33
require 'react/rails'
44
require 'react/console'
5+
require 'react/server_rendering'
56

lib/react/server_rendering.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
require 'connection_pool'
2+
require 'react/server_rendering/sprockets_renderer'
3+
4+
module React
5+
module ServerRendering
6+
mattr_accessor :renderer, :renderer_options,
7+
:pool_size, :pool_timeout
8+
9+
self.pool_size = 10
10+
self.pool_timeout = 20
11+
12+
def self.reset_pool
13+
options = {size: pool_size, timeout: pool_timeout}
14+
@@pool = ConnectionPool.new(options) { create_renderer }
15+
end
16+
17+
def self.render(component_name, props)
18+
@@pool.with do |renderer|
19+
renderer.render(component_name, props)
20+
end
21+
end
22+
23+
def self.create_renderer
24+
renderer.new(renderer_options)
25+
end
26+
end
27+
end
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module React
2+
module ServerRendering
3+
class SprocketsRenderer
4+
def initialize(options={})
5+
filenames = options.fetch(:setup, ["react.js", "components.js"])
6+
@replay_console = options.fetch(:replay_console, true)
7+
8+
js_code = SetupJavascript.new(filenames).to_s
9+
@context = ExecJS.compile(js_code)
10+
end
11+
12+
def render(component_name, props)
13+
if !props.is_a?(String)
14+
props = props.to_json
15+
end
16+
js_code = <<-JS
17+
(function () {
18+
var result = React.renderToString(React.createElement(#{component_name}, #{props}));
19+
return result;
20+
})()
21+
JS
22+
@context.eval(js_code).html_safe
23+
# handle error
24+
end
25+
26+
class SetupJavascript
27+
GLOBAL_WRAPPER = <<-JS
28+
var global = global || this;
29+
var self = self || this;
30+
var window = window || this;
31+
JS
32+
def initialize(filenames)
33+
js_code = ""
34+
filenames.each do |filename|
35+
js_code << ::Rails.application.assets[filename].to_s
36+
end
37+
@wrapped_code = GLOBAL_WRAPPER + js_code
38+
end
39+
40+
def to_s
41+
@wrapped_code
42+
end
43+
end
44+
end
45+
end
46+
end

react-rails.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Gem::Specification.new do |s|
1818
s.add_development_dependency 'bundler', '>= 1.2.2'
1919
s.add_development_dependency 'coffee-rails'
2020
s.add_development_dependency 'es5-shim-rails', '>= 2.0.5'
21+
s.add_development_dependency 'guard'
22+
s.add_development_dependency 'guard-minitest'
2123
s.add_development_dependency 'jbuilder'
2224
s.add_development_dependency 'poltergeist', '>= 0.3.3'
2325
s.add_development_dependency 'test-unit', '~> 2.5'
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
require 'test_helper'
2+
3+
class SprocketsRendererTest < ActiveSupport::TestCase
4+
setup do
5+
@renderer = React::ServerRendering::SprocketsRenderer.new({})
6+
end
7+
8+
test '#render returns HTML' do
9+
result = @renderer.render("Todo", {todo: "write tests"})
10+
# skip reactid & checksum:
11+
assert_match(/<li.*write tests<\/li>/, result)
12+
end
13+
end

test/react/server_rendering_test.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
require 'test_helper'
2+
3+
class NullRenderer
4+
def initialize(options)
5+
# in this case, options is actually a string (just for testing)
6+
@name = options
7+
end
8+
9+
def render(component_name, props)
10+
"#{@name} rendered #{component_name} with #{props}"
11+
end
12+
end
13+
14+
class ReactServerRenderingTest < ActiveSupport::TestCase
15+
setup do
16+
React::ServerRendering.renderer_options = "TEST"
17+
React::ServerRendering.renderer = NullRenderer
18+
React::ServerRendering.reset_pool
19+
end
20+
21+
test '.create_renderer makes a renderer with initialization options' do
22+
mock_renderer = MiniTest::Mock.new
23+
mock_renderer.expect(:new, :fake_renderer, [{mock: true}])
24+
React::ServerRendering.renderer = mock_renderer
25+
React::ServerRendering.renderer_options = {mock: true}
26+
renderer = React::ServerRendering.create_renderer
27+
assert_equal(:fake_renderer, renderer)
28+
end
29+
30+
test '.render returns a rendered string' do
31+
props = {"props" => true}
32+
result = React::ServerRendering.render("MyComponent", props)
33+
assert_equal("TEST rendered MyComponent with #{props}", result)
34+
end
35+
36+
test '.reset_pool forgets old renderers' do
37+
# At first, they use the first options:
38+
assert_match(/^TEST/, React::ServerRendering.render(nil, nil))
39+
assert_match(/^TEST/, React::ServerRendering.render(nil, nil))
40+
41+
# Then change the init options and clear the pool:
42+
React::ServerRendering.renderer_options = "DIFFERENT"
43+
React::ServerRendering.reset_pool
44+
# New renderers are created with the new init options:
45+
assert_match(/^DIFFERENT/, React::ServerRendering.render(nil, nil))
46+
assert_match(/^DIFFERENT/, React::ServerRendering.render(nil, nil))
47+
end
48+
end

0 commit comments

Comments
 (0)