Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/react/rails.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'react/rails/railtie'
require 'react/rails/engine'
require 'react/rails/reloader'
require 'react/rails/view_helper'
33 changes: 22 additions & 11 deletions lib/react/rails/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ class Railtie < ::Rails::Railtie
# Server-side rendering
config.react.max_renderers = 10
config.react.timeout = 20 #seconds
config.react.react_js = lambda {File.read(::Rails.application.assets.resolve('react.js'))}
config.react.component_filenames = ['components.js']

# Watch .jsx files for changes in dev, so we can reload the JS VMs with the new JS code.
initializer "react_rails.add_watchable_files" do |app|
app.config.watchable_files.concat Dir["#{app.root}/app/assets/javascripts/**/*.jsx*"]
# Add a custom file reloader middleware
initializer "react_rails.configure_reloader" do |app|
app.middleware.use React::Rails::Reloader
end

# Include the react-rails view helper lazily
Expand Down Expand Up @@ -62,23 +61,35 @@ class Railtie < ::Rails::Railtie

config.after_initialize do |app|
# Server Rendering

# Concat component_filenames together for server rendering
app.config.react.components_js = lambda {
component_source = lambda {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about component_source_files or component_sources, since we are catching multiple files here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are catching multiple files, but it ends up as a single large source block. So I have renamed to combined_components_source to better document its purpose.

app.config.react.component_filenames.map do |filename|
app.assets[filename].to_s
end.join(";")
}

do_setup = lambda do
watchable_files = lambda {
app.config.react.component_filenames.flat_map { |filename|
asset = app.assets[filename]
[asset.pathname.to_s] + asset.dependencies.map(&:pathname).map(&:to_s)
}.uniq
}

react_source = lambda { File.read(app.assets.resolve('react.js')) }

setup_renderer = lambda do
cfg = app.config.react
React::Renderer.setup!( cfg.react_js, cfg.components_js,
React::Renderer.setup!( react_source, component_source,
{:size => cfg.size, :timeout => cfg.timeout})
end

do_setup.call
# Update the watch file list with the latest set of dependencies
if app.config.reload_classes_only_on_change
React::Rails::Reloader.on_change(watchable_files.call, &setup_renderer)
end
end

# Reload the JS VMs in dev when files change
ActionDispatch::Reloader.to_prepare(&do_setup)
setup_renderer.call
end


Expand Down
32 changes: 32 additions & 0 deletions lib/react/rails/reloader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'rails'

module React
module Rails

class Reloader
def self.on_change(watch_files, &block)
unless block_given?
warn "Reloader.on_change requires a callback block"
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActiveSupport::FileUpdateChecker won't work without a block. Below code would just blow up after warning.
Also we are already sending this block from within the app ourselves. I don't think this is required.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I have removed the extra check.


@@file_checker = ActiveSupport::FileUpdateChecker.new(watch_files, &block)
end

def initialize(app)
@app = app
end

def call(env)
execute_if_updated!
@app.call(env)
end

private

def execute_if_updated!
@@file_checker.execute_if_updated if @@file_checker
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too, extra if condition isn't required, we are defining this ourselves.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is actually required, because on_change won't be called in production environment, which means that we have no @@file_checker object to work with.

end
end

end
end
3 changes: 3 additions & 0 deletions test/dummy/app/assets/javascripts/components/TodoList.js.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ TodoList = React.createClass({
this.setState({mounted: 'yep'});
},
render: function() {
var header = window.todoHeader ? window.todoHeader() : null;

return (
<ul>
{header}
<li id='status'>{this.state.mounted}</li>
{this.props.todos.map(function(todo, i) {
return (<Todo key={i} todo={todo} />)
Expand Down
3 changes: 3 additions & 0 deletions test/helper_files/components_with_updates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//= require_self
//= require todo-helper
//= require_tree ./components
3 changes: 3 additions & 0 deletions test/helper_files/todo-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
window.todoHeader = function() {
return "<li>Injected Header</li>";
};
35 changes: 35 additions & 0 deletions test/server_rendered_html_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,39 @@ def wait_to_ensure_asset_pipeline_detects_changes
FileUtils.touch app_file
end
end

test 'react server rendering picks up new files to reload' do
helper_update_file = File.expand_path('../helper_files/todo-helper.js', __FILE__)
helper_app_file = File.expand_path('../dummy/app/assets/javascripts/todo-helper.js', __FILE__)

components_with_updates = File.expand_path('../helper_files/components_with_updates.js', __FILE__)
components_without_updates = File.expand_path('../helper_files/components_without_updates.js', __FILE__)
components_app_file = File.expand_path('../dummy/app/assets/javascripts/components.js', __FILE__)

FileUtils.cp components_app_file, components_without_updates
FileUtils.touch components_app_file

begin
get '/server/1'
refute_match 'Updated', response.body

wait_to_ensure_asset_pipeline_detects_changes

FileUtils.cp components_with_updates, components_app_file
FileUtils.cp helper_update_file, helper_app_file

FileUtils.touch components_app_file

get '/server/1'
assert_match 'Injected Header', response.body
ensure
# if we have a test failure, we want to make sure that we revert the dummy file
wait_to_ensure_asset_pipeline_detects_changes

FileUtils.mv components_without_updates, components_app_file
FileUtils.touch components_app_file

FileUtils.rm helper_app_file
end
end
end