Skip to content

Commit 614f7e1

Browse files
author
Robert Mosolgo
committed
Merge pull request #295 from vipulnsward/292-babel-transformer
New Babel Transformer
2 parents 144f9ae + 3ba4366 commit 614f7e1

File tree

9 files changed

+138
-41
lines changed

9 files changed

+138
-41
lines changed

README.md

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
# react-rails
1010

1111

12-
`react-rails` makes it easy to use [React](http://facebook.github.io/react/) and [JSX](http://facebook.github.io/react/docs/jsx-in-depth.html) in your Ruby on Rails (3.2+) application. `react-rails` can:
12+
`react-rails` makes it easy to use [React](http://facebook.github.io/react/) and [JSX](http://facebook.github.io/react/docs/jsx-in-depth.html)
13+
in your Ruby on Rails (3.2+) application. `react-rails` can:
1314

1415
- Provide [various `react` builds](#reactjs-builds) to your asset bundle
1516
- Transform [`.jsx` in the asset pipeline](#jsx)
@@ -32,7 +33,8 @@ rails g react:install
3233
```
3334

3435
This will:
35-
- create a `components.js` manifest file and a `app/assets/javascripts/components/` directory, where you will put your components
36+
- create a `components.js` manifest file and a `app/assets/javascripts/components/` directory,
37+
where you will put your components
3638
- place the following in your `application.js`:
3739

3840
```js
@@ -45,7 +47,8 @@ This will:
4547

4648
### React.js builds
4749

48-
You can pick which React.js build (development, production, with or without [add-ons]((http://facebook.github.io/react/docs/addons.html))) to serve in each environment by adding a config. Here are the defaults:
50+
You can pick which React.js build (development, production, with or without [add-ons]((http://facebook.github.io/react/docs/addons.html)))
51+
to serve in each environment by adding a config. Here are the defaults:
4952

5053
```ruby
5154
# config/environments/development.rb
@@ -67,14 +70,40 @@ MyApp::Application.configure do
6770
end
6871
```
6972

70-
After restarting your Rails server, `//= require react` will provide the build of React.js which was specified by the configurations.
73+
After restarting your Rails server, `//= require react` will provide the build of React.js which
74+
was specified by the configurations.
7175

72-
`react-rails` offers a few other options for versions & builds of React.js. See [VERSIONS.md](https://github.com/reactjs/react-rails/blob/master/VERSIONS.md) for more info about using the `react-source` gem or dropping in your own copies of React.js.
76+
`react-rails` offers a few other options for versions & builds of React.js.
77+
See [VERSIONS.md](https://github.com/reactjs/react-rails/blob/master/VERSIONS.md) for more info about
78+
using the `react-source` gem or dropping in your own copies of React.js.
7379

7480
### JSX
7581

7682
After installing `react-rails`, restart your server. Now, `.js.jsx` files will be transformed in the asset pipeline.
7783

84+
`react-rails` currently ships with two transformers, to convert jsx code -
85+
86+
* `BabelTransformer` using [Babel](http://babeljs.io), which is the default transformer.
87+
* `JSXTransformer` using `JSXTransformer.js`
88+
89+
#### BabelTransformer options
90+
91+
You can use babel's [transformers](http://babeljs.io/docs/advanced/transformers/) and [custom plugins](http://babeljs.io/docs/advanced/plugins/),
92+
and pass [options](http://babeljs.io/docs/usage/options/) to the babel transpiler adding following configurations:
93+
94+
```ruby
95+
config.react.jsx_transform_options = {
96+
blacklist: ['spec.functionName', 'validation.react'], // default options
97+
optional: ["transformerName"], // pass extra babel options
98+
whitelist: ["useStrict"] // even more options
99+
}
100+
```
101+
Under the hood, `react-rails` users [ruby-babel-transpiler](https://github.com/babel/ruby-babel-transpiler), for transformation.
102+
103+
#### JSXTransformer options
104+
105+
To use old JSXTransformer you can use `React::JSX.transformer_class = React::JSX::JSXTransformer`
106+
78107
You can use JSX `--harmony` or `--strip-types` options by adding a configuration:
79108

80109
```ruby
@@ -85,17 +114,11 @@ config.react.jsx_transform_options = {
85114
}
86115
```
87116

88-
To use CoffeeScript, create `.js.jsx.coffee` files and embed JSX inside backticks, for example:
89-
90-
```coffee
91-
Component = React.createClass
92-
render: ->
93-
`<ExampleComponent videos={this.props.videos} />`
94-
```
95-
96117
### Rendering & mounting
97118

98-
`react-rails` includes a view helper (`react_component`) and an unobtrusive JavaScript driver (`react_ujs`) which work together to put React components on the page. You should require the UJS driver in your manifest after `react` (and after `turbolinks` if you use [Turbolinks](https://github.com/rails/turbolinks)).
119+
`react-rails` includes a view helper (`react_component`) and an unobtrusive JavaScript driver (`react_ujs`)
120+
which work together to put React components on the page. You should require the UJS driver
121+
in your manifest after `react` (and after `turbolinks` if you use [Turbolinks](https://github.com/rails/turbolinks)).
99122

100123
The __view helper__ puts a `div` on the page with the requested component class & props. For example:
101124

@@ -105,9 +128,12 @@ The __view helper__ puts a `div` on the page with the requested component class
105128
<div data-react-class="HelloMessage" data-react-props="{&quot;name&quot;:&quot;John&quot;}"></div>
106129
```
107130

108-
On page load, the __`react_ujs` driver__ will scan the page and mount components using `data-react-class` and `data-react-props`. Before page unload, it will unmount components (if you want to disable this behavior, remove `data-react-class` attribute in `componentDidMount`).
131+
On page load, the __`react_ujs` driver__ will scan the page and mount components using `data-react-class`
132+
and `data-react-props`. Before page unload, it will unmount components (if you want to disable this behavior,
133+
remove `data-react-class` attribute in `componentDidMount`).
109134

110-
`react_ujs` uses Turbolinks events if they're available, otherwise, it uses native events. __Turbolinks >= 2.4.0__ is recommended because it exposes better events.
135+
`react_ujs` uses Turbolinks events if they're available, otherwise, it uses native events.
136+
__Turbolinks >= 2.4.0__ is recommended because it exposes better events.
111137

112138
The view helper's signature is:
113139

@@ -141,16 +167,19 @@ _(It will be also be mounted by the UJS on page load.)_
141167

142168
There are some requirements for this to work:
143169

144-
- `react-rails` must load your code. By convention it uses `components.js`, which was created by the install task. This file must include your components _and_ their dependencies (eg, Underscore.js).
145-
- Your components must be accessible in the global scope. If you are using `.js.jsx.coffee` files then the wrapper function needs to be taken into account:
170+
- `react-rails` must load your code. By convention it uses `components.js`, which was created
171+
by the install task. This file must include your components _and_ their dependencies (eg, Underscore.js).
172+
- Your components must be accessible in the global scope.
173+
If you are using `.js.jsx.coffee` files then the wrapper function needs to be taken into account:
146174

147175
```coffee
148176
# @ is `window`:
149177
@Component = React.createClass
150178
render: ->
151179
`<ExampleComponent videos={this.props.videos} />`
152180
```
153-
- Your code can't reference `document`. Prerender processes don't have access to `document`, so jQuery and some other libs won't work in this environment :(
181+
- Your code can't reference `document`. Prerender processes don't have access to `document`,
182+
so jQuery and some other libs won't work in this environment :(
154183

155184
You can configure your pool of JS virtual machines and specify where it should load code:
156185

@@ -171,7 +200,11 @@ end
171200

172201
### Component generator
173202

174-
`react-rails` ships with a Rails generator to help you get started with a simple component scaffold. You can run it using `rails generate react:component ComponentName`. The generator takes an optional list of arguments for default propTypes, which follow the conventions set in the [Reusable Components](http://facebook.github.io/react/docs/reusable-components.html) section of the React documentation.
203+
`react-rails` ships with a Rails generator to help you get started with a simple component scaffold.
204+
You can run it using `rails generate react:component ComponentName`.
205+
The generator takes an optional list of arguments for default propTypes,
206+
which follow the conventions set in the [Reusable Components](http://facebook.github.io/react/docs/reusable-components.html)
207+
section of the React documentation.
175208

176209
For example:
177210

@@ -222,11 +255,13 @@ The following additional arguments have special behavior:
222255
* `oneOf` behaves like an enum, and takes an optional list of strings in the form of `'name:oneOf{one,two,three}'`.
223256
* `oneOfType` takes an optional list of react and custom types in the form of `'model:oneOfType{string,number,OtherType}'`.
224257

225-
Note that the arguments for `oneOf` and `oneOfType` must be enclosed in single quotes to prevent your terminal from expanding them into an argument list.
258+
Note that the arguments for `oneOf` and `oneOfType` must be enclosed in single quotes
259+
to prevent your terminal from expanding them into an argument list.
226260

227261
### Jbuilder & react-rails
228262

229-
If you use Jbuilder to pass a JSON string to `react_component`, make sure your JSON is a stringified hash, not an array. This is not the Rails default -- you should add the root node yourself. For example:
263+
If you use Jbuilder to pass a JSON string to `react_component`, make sure your JSON is a stringified hash,
264+
not an array. This is not the Rails default -- you should add the root node yourself. For example:
230265

231266
```ruby
232267
# BAD: returns a stringified array
@@ -244,7 +279,9 @@ end
244279

245280
## CoffeeScript
246281

247-
It is possible to use JSX with CoffeeScript. We need to embed JSX inside backticks so CoffeeScript ignores the syntax it doesn't understand. Here's an example:
282+
It is possible to use JSX with CoffeeScript. To use CoffeeScript, create files with an extension `.js.jsx.coffee`.
283+
We also need to embed JSX code inside backticks so that CoffeeScript ignores the syntax it doesn't understand.
284+
Here's an example:
248285

249286
```coffee
250287
Component = React.createClass

lib/react/jsx.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'execjs'
22
require 'react/jsx/template'
3-
require 'react/jsx/transformer'
3+
require 'react/jsx/jsx_transformer'
4+
require 'react/jsx/babel_transformer'
45
require 'rails'
56

67
module React
@@ -11,7 +12,7 @@ module JSX
1112
# to provide your own transformer. It must implement:
1213
# - #initialize(options)
1314
# - #transform(code) => new code
14-
self.transformer_class = Transformer
15+
self.transformer_class = BabelTransformer
1516

1617
def self.transform(code)
1718
transformer.transform(code)

lib/react/jsx/babel_transformer.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require 'babel/transpiler'
2+
module React
3+
module JSX
4+
class BabelTransformer
5+
DEPRECATED_OPTIONS = [:harmony, :strip_types, :asset_path]
6+
DEFAULT_TRANSFORM_OPTIONS = { blacklist: ['spec.functionName', 'validation.react', 'strict'] }
7+
def initialize(options)
8+
if (options.keys & DEPRECATED_OPTIONS).any?
9+
ActiveSupport::Deprecation.warn("Setting config.react.jsx_transform_options for :harmony, :strip_types, and :asset_path keys is now deprecated and has no effect with the default Babel Transformer."+
10+
"Please use new Babel Transformer options :whitelist, :plugin instead.")
11+
end
12+
13+
@transform_options = DEFAULT_TRANSFORM_OPTIONS.merge(options)
14+
end
15+
16+
def transform(code)
17+
Babel::Transpiler.transform(code, @transform_options)['code']
18+
end
19+
end
20+
end
21+
end

lib/react/jsx/transformer.rb renamed to lib/react/jsx/jsx_transformer.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module React
22
module JSX
3-
class Transformer
3+
class JSXTransformer
44
DEFAULT_ASSET_PATH = 'JSXTransformer.js'
55

66
def initialize(options)
@@ -30,4 +30,4 @@ def jsx_transform_code
3030
end
3131
end
3232
end
33-
end
33+
end

react-rails.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
3131
s.add_dependency 'execjs'
3232
s.add_dependency 'rails', '>= 3.2'
3333
s.add_dependency 'tilt'
34+
s.add_dependency 'babel-transpiler', '>=0.7.0'
3435

3536
s.files = Dir[
3637
'lib/**/*',

test/dummy/config/environments/test.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
config.eager_load = false
1818

1919
# Configure static asset server for tests with Cache-Control for performance.
20-
config.serve_static_assets = true
20+
# Disabled since we dont use it and this option is deprecated from Rails 4.2 onwards
21+
# config.serve_static_assets = true
2122
config.static_cache_control = "public, max-age=3600"
2223

2324
# Show full error reports and disable caching.

test/generators/component_generator_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ def filename
4747
end
4848

4949
test "generates working jsx" do
50-
expected_name_div = Regexp.escape('React.createElement("div", null, "Name: ", this.props.name)')
51-
expected_shape_div = Regexp.escape('React.createElement("div", null, "Address: ", this.props.address)')
50+
expected_name_div = /React\.createElement\(\s*"div",\s*null,\s*\"Name:\s*\",\s*this\.props\.name\s*\)/x
51+
expected_shape_div = /React\.createElement\(\s*"div",\s*null,\s*\"Address:\s*\",\s*this\.props\.address\s*\)/x
5252

5353
run_generator %w(GeneratedComponent name:string address:shape)
5454
jsx = React::JSX.transform(File.read(File.join(destination_root, filename)))

test/react/jsx_test.rb

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,24 @@ def transform(code)
2929

3030
class JSXTransformTest < ActionDispatch::IntegrationTest
3131
setup do
32-
clear_sprockets_cache
32+
reset_transformer
3333
end
3434

3535
teardown do
36+
reset_transformer
37+
end
38+
39+
def reset_transformer
3640
clear_sprockets_cache
37-
React::JSX.transformer_class = React::JSX::Transformer
41+
React::JSX.transformer_class = React::JSX::BabelTransformer
3842
React::JSX.transform_options = {}
3943
end
4044

4145
test 'asset pipeline should transform JSX' do
4246
get '/assets/example.js'
4347
assert_response :success
44-
assert_equal EXPECTED_JS, @response.body
48+
49+
assert_equal EXPECTED_JS.gsub(/\s/, ''), @response.body.gsub(/\s/, '')
4550
end
4651

4752
test 'asset pipeline should transform JSX + Coffeescript' do
@@ -54,6 +59,39 @@ class JSXTransformTest < ActionDispatch::IntegrationTest
5459
assert_equal EXPECTED_JS_2.gsub(/\s/, ''), @response.body.gsub(/\s/, '')
5560
end
5661

62+
test 'use a custom transformer' do
63+
React::JSX.transformer_class = NullTransformer
64+
manually_expire_asset('example2.js')
65+
get '/assets/example2.js'
66+
assert_equal "TRANSFORMED CODE!;\n", @response.body
67+
end
68+
69+
def test_babel_transformer_accepts_babel_transformation_options
70+
React::JSX.transform_options = {blacklist: ['spec.functionName', 'validation.react', "strict"]}
71+
get '/assets/example.js'
72+
assert_response :success
73+
74+
assert !@response.body.include?('strict')
75+
end
76+
77+
end
78+
79+
class JSXTransformerTest < ActionDispatch::IntegrationTest
80+
81+
setup do
82+
reset_transformer
83+
end
84+
85+
teardown do
86+
reset_transformer
87+
end
88+
89+
def reset_transformer
90+
clear_sprockets_cache
91+
React::JSX.transformer_class = React::JSX::JSXTransformer
92+
React::JSX.transform_options = {}
93+
end
94+
5795
test 'can use dropped-in version of JSX transformer' do
5896
hidden_path = Rails.root.join("vendor/assets/react/JSXTransformer__.js")
5997
replacing_path = Rails.root.join("vendor/assets/react/JSXTransformer.js")
@@ -99,10 +137,4 @@ class JSXTransformTest < ActionDispatch::IntegrationTest
99137
assert_equal 'test_confirmation_token_jsx_transformed;', @response.body
100138
end
101139

102-
test 'use a custom transformer' do
103-
React::JSX.transformer_class = NullTransformer
104-
manually_expire_asset('example2.js')
105-
get '/assets/example2.js'
106-
assert_equal "TRANSFORMED CODE!;\n", @response.body
107-
end
108140
end

test/test_helper.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
# Configure Rails Environment
77
ENV["RAILS_ENV"] = "test"
88

9-
require File.expand_path("../dummy/config/environment.rb", __FILE__)
9+
require File.expand_path("../dummy/config/environment.rb", __FILE__)
1010
require "rails/test_help"
1111
require "rails/generators"
1212
require "pathname"
1313
require 'minitest/mock'
1414

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

1717
Rails.backtrace_cleaner.remove_silencers!
1818

@@ -40,6 +40,10 @@ def asset.fresh?(env); false; end
4040
ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
4141
end
4242

43+
if ActiveSupport::TestCase.respond_to?(:test_order=)
44+
ActiveSupport::TestCase.test_order = :random
45+
end
46+
4347
def wait_for_turbolinks_to_be_available
4448
sleep(1)
4549
end

0 commit comments

Comments
 (0)