From a325c0b023c44436bdb9c0c2fd278320c7dbdc4d Mon Sep 17 00:00:00 2001 From: Alexander Panasyuk Date: Fri, 13 Mar 2015 12:02:58 +0700 Subject: [PATCH 01/10] Reduced size of initial inline props for component by moving this unescaped data to the end of body. This makes markup more clean and simple and reduces required bandwith for highload applications. --- lib/assets/javascripts/react_ujs.js.erb | 6 ++++-- lib/react/rails/view_helper.rb | 22 +++++++++++++++++++++- react-rails.gemspec | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/assets/javascripts/react_ujs.js.erb b/lib/assets/javascripts/react_ujs.js.erb index 7d1262c8d..aac45537a 100644 --- a/lib/assets/javascripts/react_ujs.js.erb +++ b/lib/assets/javascripts/react_ujs.js.erb @@ -8,7 +8,7 @@ // create the namespace window.ReactRailsUJS = { CLASS_NAME_ATTR: 'data-react-class', - PROPS_ATTR: 'data-react-props', + PROPS_ID_ATTR: 'data-react-props-id', RAILS_ENV_DEVELOPMENT: <%= Rails.env == "development" %>, // helper method for the mount and unmount methods to find the // `data-react-class` DOM elements @@ -33,7 +33,9 @@ // Assume className is simple and can be found at top-level (window). // Fallback to eval to handle cases like 'My.React.ComponentName'. var constructor = window[className] || eval.call(window, className); - var propsJson = node.getAttribute(window.ReactRailsUJS.PROPS_ATTR); + var propsId = node.getAttribute(window.ReactRailsUJS.PROPS_ID_ATTR); + var propsElement = document.getElementById(propsId); + var propsJson = propsElement && propsElement.text; var props = propsJson && JSON.parse(propsJson); React.render(React.createElement(constructor, props), node); diff --git a/lib/react/rails/view_helper.rb b/lib/react/rails/view_helper.rb index 5abb55e7a..e2016e5ef 100644 --- a/lib/react/rails/view_helper.rb +++ b/lib/react/rails/view_helper.rb @@ -13,7 +13,7 @@ def react_component(name, args = {}, options = {}, &block) html_options = options.reverse_merge(:data => {}) html_options[:data].tap do |data| data[:react_class] = name - data[:react_props] = React::Renderer.react_props(args) unless args.empty? + data[:react_props_id] = add_react_props args end html_tag = html_options[:tag] || :div @@ -23,6 +23,26 @@ def react_component(name, args = {}, options = {}, &block) content_tag(html_tag, '', html_options, &block) end + # Add properties for component and return element id. + # + def add_react_props(props={}) + return if props.empty? + props_id = SecureRandom.base64 + content_for :react_props do + content_tag :script, type: 'text/json', id: props_id do + raw React::Renderer.react_props props + end + end + props_id + end + + # Render script tag with JSON props. Should be placed at the end of body + # in order to speedup page rendering. + # + def render_react_props + content_for :react_props + end + end end end diff --git a/react-rails.gemspec b/react-rails.gemspec index aec66efe6..49579562b 100644 --- a/react-rails.gemspec +++ b/react-rails.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'jbuilder' s.add_dependency 'execjs' - s.add_dependency 'coffee-script-source', '~>1.9' + s.add_dependency 'coffee-script-source', '~>1.8' s.add_dependency 'rails', '>= 3.1' s.add_dependency 'react-source', '~> 0.12' s.add_dependency 'connection_pool' From 8b180fa86d58c7b22034595ab9720c89dd047899 Mon Sep 17 00:00:00 2001 From: Alexander Panasyuk Date: Fri, 13 Mar 2015 13:07:42 +0700 Subject: [PATCH 02/10] coffee-script-source back to ~>1.9 --- react-rails.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-rails.gemspec b/react-rails.gemspec index 49579562b..aec66efe6 100644 --- a/react-rails.gemspec +++ b/react-rails.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'jbuilder' s.add_dependency 'execjs' - s.add_dependency 'coffee-script-source', '~>1.8' + s.add_dependency 'coffee-script-source', '~>1.9' s.add_dependency 'rails', '>= 3.1' s.add_dependency 'react-source', '~> 0.12' s.add_dependency 'connection_pool' From bd4da9714c4a93152e9c6b8baff9dc71625db2a1 Mon Sep 17 00:00:00 2001 From: Alexander Panasyuk Date: Fri, 13 Mar 2015 13:10:21 +0700 Subject: [PATCH 03/10] coffee-script-source 1.8 --- react-rails.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-rails.gemspec b/react-rails.gemspec index aec66efe6..49579562b 100644 --- a/react-rails.gemspec +++ b/react-rails.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'jbuilder' s.add_dependency 'execjs' - s.add_dependency 'coffee-script-source', '~>1.9' + s.add_dependency 'coffee-script-source', '~>1.8' s.add_dependency 'rails', '>= 3.1' s.add_dependency 'react-source', '~> 0.12' s.add_dependency 'connection_pool' From 3385588a443250aeafcba0be8047e9523bf9d717 Mon Sep 17 00:00:00 2001 From: Alexander Panasyuk Date: Fri, 13 Mar 2015 14:25:57 +0700 Subject: [PATCH 04/10] Separate options are added as option --- lib/assets/javascripts/react_ujs.js.erb | 16 ++++++++++--- lib/react/rails/railtie.rb | 2 ++ lib/react/rails/view_helper.rb | 31 +++++++++++++++++++------ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/lib/assets/javascripts/react_ujs.js.erb b/lib/assets/javascripts/react_ujs.js.erb index aac45537a..ec206744e 100644 --- a/lib/assets/javascripts/react_ujs.js.erb +++ b/lib/assets/javascripts/react_ujs.js.erb @@ -8,7 +8,9 @@ // create the namespace window.ReactRailsUJS = { CLASS_NAME_ATTR: 'data-react-class', + PROPS_ATTR: 'data-react-props', PROPS_ID_ATTR: 'data-react-props-id', + SEPARATE_PROPS: <%= Rails.configuration.react.separate_props %>, RAILS_ENV_DEVELOPMENT: <%= Rails.env == "development" %>, // helper method for the mount and unmount methods to find the // `data-react-class` DOM elements @@ -27,15 +29,23 @@ var nodes = window.ReactRailsUJS.findDOMNodes(); for (var i = 0; i < nodes.length; ++i) { + var propsId, propsElement, propsJson; var node = nodes[i]; var className = node.getAttribute(window.ReactRailsUJS.CLASS_NAME_ATTR); // Assume className is simple and can be found at top-level (window). // Fallback to eval to handle cases like 'My.React.ComponentName'. var constructor = window[className] || eval.call(window, className); - var propsId = node.getAttribute(window.ReactRailsUJS.PROPS_ID_ATTR); - var propsElement = document.getElementById(propsId); - var propsJson = propsElement && propsElement.text; + + + if (window.ReactRailsUJS.SEPARATE_PROPS) { + propsId = node.getAttribute(window.ReactRailsUJS.PROPS_ID_ATTR); + propsElement = document.getElementById(propsId); + propsJson = propsElement && propsElement.text; + } else { + propsJson = node.getAttribute(window.ReactRailsUJS.PROPS_ATTR); + } + var props = propsJson && JSON.parse(propsJson); React.render(React.createElement(constructor, props), node); diff --git a/lib/react/rails/railtie.rb b/lib/react/rails/railtie.rb index 3ea4ae743..24a5bdadc 100644 --- a/lib/react/rails/railtie.rb +++ b/lib/react/rails/railtie.rb @@ -9,6 +9,8 @@ class Railtie < ::Rails::Railtie config.react.variant = (::Rails.env.production? ? :production : :development) config.react.addons = false config.react.jsx_transform_options = {} + config.react.separate_props = false + # Server-side rendering config.react.max_renderers = 10 config.react.timeout = 20 #seconds diff --git a/lib/react/rails/view_helper.rb b/lib/react/rails/view_helper.rb index e2016e5ef..29f115b3d 100644 --- a/lib/react/rails/view_helper.rb +++ b/lib/react/rails/view_helper.rb @@ -13,22 +13,31 @@ def react_component(name, args = {}, options = {}, &block) html_options = options.reverse_merge(:data => {}) html_options[:data].tap do |data| data[:react_class] = name - data[:react_props_id] = add_react_props args + next if args.empty? + if react_separate_props? + data[:react_props_id] = add_react_props args, options[:inline_props] + else + data[:react_props] = React::Renderer.react_props args + end end html_tag = html_options[:tag] || :div # remove internally used properties so they aren't rendered to DOM html_options.except!(:tag, :prerender) - - content_tag(html_tag, '', html_options, &block) + + result = content_tag(html_tag, '', html_options, &block) + result += render_react_props html_options[:data][:react_props_id] if options[:inline_props] && react_separate_props? + result end # Add properties for component and return element id. # - def add_react_props(props={}) + def add_react_props(props={}, inline=false) return if props.empty? props_id = SecureRandom.base64 - content_for :react_props do + content_key = "react_props" + content_key += "_#{props_id}" if inline + content_for content_key do content_tag :script, type: 'text/json', id: props_id do raw React::Renderer.react_props props end @@ -39,8 +48,16 @@ def add_react_props(props={}) # Render script tag with JSON props. Should be placed at the end of body # in order to speedup page rendering. # - def render_react_props - content_for :react_props + def render_react_props(element_id=nil) + if react_separate_props? + content_for("react_props_#{element_id}") || content_for('react_props') + else + nil + end + end + + def react_separate_props? + ::Rails.configuration.react.separate_props end end From cf15ac6cfed206153902d07afdfc520c3d655474 Mon Sep 17 00:00:00 2001 From: Alexander Panasyuk Date: Fri, 13 Mar 2015 16:31:28 +0700 Subject: [PATCH 05/10] Done with tests --- lib/assets/javascripts/react_ujs.js.erb | 6 ++-- lib/react/rails/railtie.rb | 1 - lib/react/rails/view_helper.rb | 22 +++++------- .../app/controllers/separate_controller.rb | 2 ++ .../app/views/layouts/application.html.erb | 4 +++ test/dummy/app/views/separate/show.html.erb | 1 + test/dummy/config/routes.rb | 1 + test/view_helper_test.rb | 34 ++++++++++++++++--- 8 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 test/dummy/app/controllers/separate_controller.rb create mode 100644 test/dummy/app/views/separate/show.html.erb diff --git a/lib/assets/javascripts/react_ujs.js.erb b/lib/assets/javascripts/react_ujs.js.erb index ec206744e..efe0c4ee6 100644 --- a/lib/assets/javascripts/react_ujs.js.erb +++ b/lib/assets/javascripts/react_ujs.js.erb @@ -10,7 +10,6 @@ CLASS_NAME_ATTR: 'data-react-class', PROPS_ATTR: 'data-react-props', PROPS_ID_ATTR: 'data-react-props-id', - SEPARATE_PROPS: <%= Rails.configuration.react.separate_props %>, RAILS_ENV_DEVELOPMENT: <%= Rails.env == "development" %>, // helper method for the mount and unmount methods to find the // `data-react-class` DOM elements @@ -37,9 +36,8 @@ // Fallback to eval to handle cases like 'My.React.ComponentName'. var constructor = window[className] || eval.call(window, className); - - if (window.ReactRailsUJS.SEPARATE_PROPS) { - propsId = node.getAttribute(window.ReactRailsUJS.PROPS_ID_ATTR); + propsId = node.getAttribute(window.ReactRailsUJS.PROPS_ID_ATTR); + if (propsId != null) { propsElement = document.getElementById(propsId); propsJson = propsElement && propsElement.text; } else { diff --git a/lib/react/rails/railtie.rb b/lib/react/rails/railtie.rb index 24a5bdadc..c8d00916e 100644 --- a/lib/react/rails/railtie.rb +++ b/lib/react/rails/railtie.rb @@ -9,7 +9,6 @@ class Railtie < ::Rails::Railtie config.react.variant = (::Rails.env.production? ? :production : :development) config.react.addons = false config.react.jsx_transform_options = {} - config.react.separate_props = false # Server-side rendering config.react.max_renderers = 10 diff --git a/lib/react/rails/view_helper.rb b/lib/react/rails/view_helper.rb index 29f115b3d..7f8143cf8 100644 --- a/lib/react/rails/view_helper.rb +++ b/lib/react/rails/view_helper.rb @@ -9,13 +9,15 @@ module ViewHelper def react_component(name, args = {}, options = {}, &block) options = {:tag => options} if options.is_a?(Symbol) block = Proc.new{concat React::Renderer.render(name, args)} if options[:prerender] + separate_props = options.delete :separate_props + move_separate_props_out = options.delete :move_separate_props_out html_options = options.reverse_merge(:data => {}) html_options[:data].tap do |data| data[:react_class] = name next if args.empty? - if react_separate_props? - data[:react_props_id] = add_react_props args, options[:inline_props] + if separate_props + data[:react_props_id] = add_react_props args, move_separate_props_out else data[:react_props] = React::Renderer.react_props args end @@ -26,17 +28,17 @@ def react_component(name, args = {}, options = {}, &block) html_options.except!(:tag, :prerender) result = content_tag(html_tag, '', html_options, &block) - result += render_react_props html_options[:data][:react_props_id] if options[:inline_props] && react_separate_props? + result += render_react_props html_options[:data][:react_props_id] if separate_props && !move_separate_props_out result end # Add properties for component and return element id. # - def add_react_props(props={}, inline=false) + def add_react_props(props={}, move_out=false) return if props.empty? props_id = SecureRandom.base64 content_key = "react_props" - content_key += "_#{props_id}" if inline + content_key += "_#{props_id}" unless move_out content_for content_key do content_tag :script, type: 'text/json', id: props_id do raw React::Renderer.react_props props @@ -49,15 +51,7 @@ def add_react_props(props={}, inline=false) # in order to speedup page rendering. # def render_react_props(element_id=nil) - if react_separate_props? - content_for("react_props_#{element_id}") || content_for('react_props') - else - nil - end - end - - def react_separate_props? - ::Rails.configuration.react.separate_props + content_for("react_props_#{element_id}") || content_for('react_props') || nil end end diff --git a/test/dummy/app/controllers/separate_controller.rb b/test/dummy/app/controllers/separate_controller.rb new file mode 100644 index 000000000..819d5ba64 --- /dev/null +++ b/test/dummy/app/controllers/separate_controller.rb @@ -0,0 +1,2 @@ +class SeparateController < ServerController +end diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb index 670d1875b..4a52a7a76 100644 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -10,5 +10,9 @@ <%= yield %> +
+ +<%= render_react_props %> + diff --git a/test/dummy/app/views/separate/show.html.erb b/test/dummy/app/views/separate/show.html.erb new file mode 100644 index 000000000..597ba8d41 --- /dev/null +++ b/test/dummy/app/views/separate/show.html.erb @@ -0,0 +1 @@ +<%= react_component "TodoList", {:todos => @todos}, :prerender => true, :separate_props => true, :move_separate_props_out => params[:move_separate_props_out] %> diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index daf2e2b04..cd8c437fc 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -1,4 +1,5 @@ Dummy::Application.routes.draw do resources :pages, :only => [:show] resources :server, :only => [:show] + resources :separate, :only => [:show] end diff --git a/test/view_helper_test.rb b/test/view_helper_test.rb index 23c061d43..ded8cd777 100644 --- a/test/view_helper_test.rb +++ b/test/view_helper_test.rb @@ -48,7 +48,25 @@ class ViewHelperTest < ActionDispatch::IntegrationTest assert html.include?('class="test"') assert html.include?('data-foo="1"') end - + + test 'react_component can render separate props inline' do + get '/separate/1' + inline_props_id = response.body.scan(/data-react-props-id="([^"]+)/).flatten.first + assert_not inline_props_id.nil? + dummy_index = response.body.index '
' + inline_props_index = response.body.index("