Skip to content

Allow users to override react.js and transformer.js for assets and engine #12

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

Merged
merged 11 commits into from
Feb 16, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ As with all gem dependencies, we strongly recommend adding `react-rails` to your
```ruby
# Gemfile

gem 'react-rails', '~> 0.8.0.0'
gem 'react-rails', '~> 1.0.0'
```


## Usage

### react.js

In order to use React client-side in your application, you must make sure the browser requests it. One way to do that is to drop `react.js` into `app/assets/javascript/` and by default your application manifest will pick it up. There are downsides to this approach, so we made it even easier. Once you have `react-rails` installed, you can just add a line into your config file (see Configuring) and require react directly in your manifest:
In order to use React client-side in your application, you must make sure the browser requests it. One way to do that is to drop `react.js` into `vendor/assets/javascript/` and by default your application manifest will pick it up. There are downsides to this approach, so we made it even easier. Once you have `react-rails` installed, you can just add a line into your config file (see Configuring) and require react directly in your manifest:

You can `require` it in your manifest:

Expand Down Expand Up @@ -129,3 +128,20 @@ Component = React.createClass
`<ExampleComponent videos={this.props.videos} />`
```

### Changing react.js and JSXTransformer.js versions

In some cases you may want to have your `react.js` and `JSXTransformer.js` files come from a different release than the one, that is specified in the `react-rails.gemspec`. To achieve that, you have to manually replace them in your app.

#### Instructions

Just put another version of `react.js` or `JSXTransformer.js` under `/vendor/assets/react` directory.
If you need different versions of `react.js` for production and development, then use a subdirectory named
after `config.react.variant`, e.g. you set `config.react.variant = :development` so for this environment
`react.js` is expected to be in `/vendor/assets/react/development`

#### Things to remember

If you replace `JSXTransformer.js` in production environment, you have to restart your rails instance,
because the jsx compiler context is cached.

Name of the `JSXTransformer.js` file *is case-sensitive*.
23 changes: 16 additions & 7 deletions lib/react/jsx.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
require 'execjs'
require 'react/source'
require 'react/jsx/template'
require 'rails'

module React
module JSX
def self.context
# TODO: create React::Source::contents_for
contents =
# If execjs uses therubyracer, there is no 'global'. Make sure
# we have it so JSX script can work properly.
'var global = global || this;' +
File.read(React::Source.bundled_path_for('JSXTransformer.js'))
@context ||= ExecJS.compile(contents)
# lazily loaded during first request and reloaded every time when in dev or test
unless @context && ::Rails.env.production?
contents =
# If execjs uses therubyracer, there is no 'global'. Make sure
# we have it so JSX script can work properly.
'var global = global || this;' +

# search for transformer file using sprockets - allows user to override
# this file in his own application
File.read(::Rails.application.assets.resolve('JSXTransformer.js'))

@context = ExecJS.compile(contents)
end

@context
end

def self.transform(code)
Expand Down
2 changes: 1 addition & 1 deletion lib/react/jsx/template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Template < Tilt::Template
def prepare
end

def evaluate(scopre, locals, &block)
def evaluate(scope, locals, &block)
@output ||= JSX::transform(data)
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/react/rails/engine.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module React
module Rails
class Engine < ::Rails::Engine
initializer "react_rails.setup_engine", :after => "sprockets.environment", :group => :all do |app|
initializer "react_rails.setup_engine", :group => :all do |app|
app.assets.register_engine '.jsx', React::JSX::Template
end
end
Expand Down
18 changes: 13 additions & 5 deletions lib/react/rails/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ module Rails
class Railtie < ::Rails::Railtie
config.react = ActiveSupport::OrderedOptions.new

initializer "react_rails.setup_vendor", :after => "sprockets.environment" do |app|
variant = app.config.react.variant

# run after all initializers to allow sprockets to pick up react.js and
# jsxtransformer.js from end-user to override ours if needed
config.after_initialize do |app|
# Mimic behavior of ember-rails...
# We want to include different files in dev/prod. The unminified builds
# contain console logging for invariants and logging to help catch
Expand All @@ -26,9 +26,17 @@ class Railtie < ::Rails::Railtie
tmp_path.join('react.js'))
FileUtils.cp(::React::Source.bundled_path_for('JSXTransformer.js'),
tmp_path.join('JSXTransformer.js'))
app.assets.prepend_path tmp_path

# Allow overriding react files that are not based on environment
# e.g. /vendor/assets/react/JSXTransformer.js
dropin_path = app.root.join("vendor/assets/react")
app.assets.prepend_path dropin_path if dropin_path.exist?

# Make sure it can be found
app.assets.append_path(tmp_path)
# Allow overriding react files that are based on environment
# e.g. /vendor/assets/react/react.js
dropin_path_env = app.root.join("vendor/assets/react/#{variant}")
app.assets.prepend_path dropin_path_env if dropin_path_env.exist?
end
end
end
Expand Down
3 changes: 1 addition & 2 deletions lib/react/rails/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module React
module Rails
# Version numbers will track react-source, but we'll add another level so
# that we can increment, but have some amount of stability.
VERSION = '0.8.0.0'
VERSION = '1.0.0.beta1'
end
end

2 changes: 2 additions & 0 deletions test/dummy/app/assets/javascripts/example3.js.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** @jsx React.DOM */
<div/>;
8 changes: 7 additions & 1 deletion test/dummy/config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true

# we need this to reload the jsx transformer when different version is dropped in
config.cache_classes = false
config.reload_plugins = true
config.assets.cache_store = :null_store

# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
Expand Down Expand Up @@ -33,4 +37,6 @@

# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr

config.react.variant = :test
end
7 changes: 7 additions & 0 deletions test/dummy/vendor/assets/react/JSXTransformer__.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var JSXTransformer = {
transform: function () {
return {
code: 'test_confirmation_token_jsx_transformed;'
};
}
};
1 change: 1 addition & 0 deletions test/dummy/vendor/assets/react/test/react__.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
'test_confirmation_token_react_content';
17 changes: 16 additions & 1 deletion test/jsxtransform_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'test_helper'
require 'fileutils'

# The transformer is inserting a newline after the docblock for some reason...
# Sprockets is inserting a newline after the docblock for some reason...
EXPECTED_JS = <<eos
/** @jsx React.DOM */

Expand All @@ -26,6 +27,7 @@ class JSXTransformTest < ActionDispatch::IntegrationTest

test 'asset pipeline should transform JSX' do
get 'assets/example.js'
FileUtils.rm_r CACHE_PATH if CACHE_PATH.exist?
assert_response :success
assert_equal EXPECTED_JS, @response.body
end
Expand All @@ -42,4 +44,17 @@ class JSXTransformTest < ActionDispatch::IntegrationTest
assert_equal EXPECTED_JS_2.gsub(/\s/, ''), @response.body.gsub(/\s/, '')
end

test 'can use dropped in version of JSX transformer' do
hidden_path = File.expand_path("../dummy/vendor/assets/react/JSXTransformer__.js", __FILE__)
replacing_path = File.expand_path("../dummy/vendor/assets/react/JSXTransformer.js", __FILE__)

FileUtils.mv hidden_path, replacing_path
get 'assets/example3.js'

FileUtils.mv replacing_path, hidden_path
FileUtils.rm_r CACHE_PATH if CACHE_PATH.exist?

assert_response :success
assert_equal 'test_confirmation_token_jsx_transformed;', @response.body
end
end
36 changes: 36 additions & 0 deletions test/react_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require 'test_helper'
require 'fileutils'

class ReactTest < ActionDispatch::IntegrationTest

test 'asset pipeline should deliver react file in a non-production variant' do
actual_react_file_path = File.expand_path("../dummy/tmp/react-rails/react.js", __FILE__)
actual_react_file_content = File.read actual_react_file_path

react_file_token = "'test_confirmation_token_react_content_non_production';\n";
File.open(actual_react_file_path, 'w') {|f| f.write react_file_token}

get 'assets/react.js'

File.open(actual_react_file_path, 'w') {|f| f.write actual_react_file_content}
FileUtils.rm_r CACHE_PATH if CACHE_PATH.exist?

assert_response :success
assert_equal react_file_token, @response.body
end

test 'asset pipeline should deliver drop-in react file replacement' do
hidden_path = File.expand_path("../dummy/vendor/assets/react/test/react__.js", __FILE__)
replacing_path = File.expand_path("../dummy/vendor/assets/react/test/react.js", __FILE__)

FileUtils.mv hidden_path, replacing_path
get 'assets/react.js'

FileUtils.mv replacing_path, hidden_path
FileUtils.rm_r CACHE_PATH if CACHE_PATH.exist?

assert_response :success
assert_equal "'test_confirmation_token_react_content';\n", @response.body
end

end
3 changes: 3 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

require File.expand_path("../dummy/config/environment.rb", __FILE__)
require "rails/test_help"
require "pathname"

CACHE_PATH = Pathname.new File.expand_path("../dummy/tmp/cache", __FILE__)

Rails.backtrace_cleaner.remove_silencers!

Expand Down