Skip to content

New Babel Transformer #295

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 9 commits into from
Jun 19, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
76 changes: 61 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
# react-rails


`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:
`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:

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

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

```js
Expand All @@ -45,7 +47,8 @@ This will:

### React.js builds

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:
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:

```ruby
# config/environments/development.rb
Expand All @@ -67,14 +70,40 @@ MyApp::Application.configure do
end
```

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

`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.
`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.

### JSX

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

`react-rails` currently ships with two transformers, to convert jsx code -

* `BabelTransformer` using [Babel](http://babeljs.io), which is the default transformer.
* `JSXTransformer` using `JSXTransformer.js`

#### BabelTransformer options

You can use babel's [transformers](http://babeljs.io/docs/advanced/transformers/) and [custom plugins](http://babeljs.io/docs/advanced/plugins/),
and pass [options](http://babeljs.io/docs/usage/options/) to the babel transpiler adding following configurations:

```ruby
config.react.jsx_transform_options = {
blacklist: ['spec.functionName', 'validation.react'], // default options
optional: ["transformerName"], // pass extra babel options
whitelist: ["useStrict"] // even more options
}
```
Under the hood, `react-rails` users [ruby-babel-transpiler](https://github.com/babel/ruby-babel-transpiler), for transformation.

#### JSXTransformer options

To use old JSXTransformer you can use `React::JSX.transformer_class = React::JSX::JSXTransformer`

You can use JSX `--harmony` or `--strip-types` options by adding a configuration:

```ruby
Expand All @@ -85,6 +114,8 @@ config.react.jsx_transform_options = {
}
```

#### CoffeeScript
Copy link
Member

Choose a reason for hiding this comment

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

Should we merge this with the "## Coffeescript" section below?


To use CoffeeScript, create `.js.jsx.coffee` files and embed JSX inside backticks, for example:

```coffee
Expand All @@ -95,7 +126,9 @@ Component = React.createClass

### Rendering & mounting

`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)).
`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)).

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

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

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`).
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`).

`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.
`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.

The view helper's signature is:

Expand Down Expand Up @@ -141,16 +177,19 @@ _(It will be also be mounted by the UJS on page load.)_

There are some requirements for this to work:

- `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).
- 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:
- `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).
- 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:

```coffee
# @ is `window`:
@Component = React.createClass
render: ->
`<ExampleComponent videos={this.props.videos} />`
```
- 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 :(
- 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 :(

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

Expand All @@ -171,7 +210,11 @@ end

### Component generator

`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.
`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.

For example:

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

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.
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.

### Jbuilder & react-rails

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:
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:

```ruby
# BAD: returns a stringified array
Expand All @@ -244,7 +289,8 @@ end

## CoffeeScript

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:
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:

```coffee
Component = React.createClass
Expand Down
5 changes: 3 additions & 2 deletions lib/react/jsx.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'execjs'
require 'react/jsx/template'
require 'react/jsx/transformer'
require 'react/jsx/jsx_transformer'
require 'react/jsx/babel_transformer'
require 'rails'

module React
Expand All @@ -11,7 +12,7 @@ module JSX
# to provide your own transformer. It must implement:
# - #initialize(options)
# - #transform(code) => new code
self.transformer_class = Transformer
self.transformer_class = BabelTransformer

def self.transform(code)
transformer.transform(code)
Expand Down
29 changes: 29 additions & 0 deletions lib/react/jsx/babel_transformer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'babel/transpiler'
module React
module JSX
class BabelTransformer
DEPRECATED_OPTIONS = [:harmony, :strip_types, :asset_path]

def initialize(options)
_options = options.dup
has_old_opts = DEPRECATED_OPTIONS.map do |option|
Copy link
Member

Choose a reason for hiding this comment

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

Is there any downside to passing the deprecated keys to Babel? Are they ignored or do they raise errors?

If they're just ignored, this could be simplified a bit:

if (options.keys & DEPRECATED_OPTIONS).any?
  # deprecation warning ...
end

_options.delete option
end.any?

if has_old_opts
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."+
"Please use new Babel Transformer options :whitelist, :plugin instead.")
end

@transform_options = {
Copy link
Member

Choose a reason for hiding this comment

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

How about moving the defaults to DEFAULT_OPTIONS ?

Then just

@transform_options = DEFAULT_OPTIONS.merge(options)

blacklist: ['spec.functionName', 'validation.react']
}.merge(_options)
end

def transform(code)
puts @transform_options
Copy link
Member

Choose a reason for hiding this comment

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

oops, probably don't need this anymore :)

Babel::Transpiler.transform(code)['code']
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module React
module JSX
class Transformer
class JSXTransformer
DEFAULT_ASSET_PATH = 'JSXTransformer.js'

def initialize(options)
Expand Down Expand Up @@ -30,4 +30,4 @@ def jsx_transform_code
end
end
end
end
end
1 change: 1 addition & 0 deletions react-rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Gem::Specification.new do |s|
s.add_dependency 'execjs'
s.add_dependency 'rails', '>= 3.2'
s.add_dependency 'tilt'
s.add_dependency 'babel-transpiler', '>=0.7.0'

s.files = Dir[
'lib/**/*',
Expand Down
4 changes: 2 additions & 2 deletions test/generators/component_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ def filename
end

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

run_generator %w(GeneratedComponent name:string address:shape)
jsx = React::JSX.transform(File.read(File.join(destination_root, filename)))
Expand Down
3 changes: 2 additions & 1 deletion test/react/jsx_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class JSXTransformTest < ActionDispatch::IntegrationTest

teardown do
clear_sprockets_cache
React::JSX.transformer_class = React::JSX::Transformer
React::JSX.transformer_class = React::JSX::JSXTransformer
React::JSX.transform_options = {}
end

Expand Down Expand Up @@ -89,6 +89,7 @@ class JSXTransformTest < ActionDispatch::IntegrationTest
replacing_path = custom_path.join("CustomTransformer.js")

React::JSX.transform_options = {asset_path: "custom/CustomTransformer.js"}
React::JSX.transformer_class = React::JSX::JSXTransformer

FileUtils.mkdir_p(custom_path)
FileUtils.cp(hidden_path, replacing_path)
Expand Down