diff --git a/README.md b/README.md index f243dc81..83841cc4 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 @@ -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 @@ -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 @@ -85,17 +114,11 @@ config.react.jsx_transform_options = { } ``` -To use CoffeeScript, create `.js.jsx.coffee` files and embed JSX inside backticks, for example: - -```coffee -Component = React.createClass - render: -> - `` -``` - ### 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: @@ -105,9 +128,12 @@ The __view helper__ puts a `div` on the page with the requested component class
``` -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: @@ -141,8 +167,10 @@ _(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`: @@ -150,7 +178,8 @@ There are some requirements for this to work: render: -> `` ``` -- 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: @@ -171,7 +200,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: @@ -222,11 +255,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 @@ -244,7 +279,9 @@ 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. To use CoffeeScript, create files with an extension `.js.jsx.coffee`. +We also need to embed JSX code inside backticks so that CoffeeScript ignores the syntax it doesn't understand. +Here's an example: ```coffee Component = React.createClass diff --git a/lib/react/jsx.rb b/lib/react/jsx.rb index 72fa6af6..692e4e2d 100644 --- a/lib/react/jsx.rb +++ b/lib/react/jsx.rb @@ -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 @@ -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) diff --git a/lib/react/jsx/babel_transformer.rb b/lib/react/jsx/babel_transformer.rb new file mode 100644 index 00000000..f6fc37dc --- /dev/null +++ b/lib/react/jsx/babel_transformer.rb @@ -0,0 +1,21 @@ +require 'babel/transpiler' +module React + module JSX + class BabelTransformer + DEPRECATED_OPTIONS = [:harmony, :strip_types, :asset_path] + DEFAULT_TRANSFORM_OPTIONS = { blacklist: ['spec.functionName', 'validation.react', 'strict'] } + def initialize(options) + if (options.keys & DEPRECATED_OPTIONS).any? + 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 = DEFAULT_TRANSFORM_OPTIONS.merge(options) + end + + def transform(code) + Babel::Transpiler.transform(code, @transform_options)['code'] + end + end + end +end diff --git a/lib/react/jsx/transformer.rb b/lib/react/jsx/jsx_transformer.rb similarity index 97% rename from lib/react/jsx/transformer.rb rename to lib/react/jsx/jsx_transformer.rb index 49a79e16..c4230332 100644 --- a/lib/react/jsx/transformer.rb +++ b/lib/react/jsx/jsx_transformer.rb @@ -1,6 +1,6 @@ module React module JSX - class Transformer + class JSXTransformer DEFAULT_ASSET_PATH = 'JSXTransformer.js' def initialize(options) @@ -30,4 +30,4 @@ def jsx_transform_code end end end -end \ No newline at end of file +end diff --git a/react-rails.gemspec b/react-rails.gemspec index 4839a978..52da5103 100644 --- a/react-rails.gemspec +++ b/react-rails.gemspec @@ -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/**/*', diff --git a/test/dummy/config/environments/test.rb b/test/dummy/config/environments/test.rb index cff2ea78..f6b42357 100644 --- a/test/dummy/config/environments/test.rb +++ b/test/dummy/config/environments/test.rb @@ -17,7 +17,8 @@ config.eager_load = false # Configure static asset server for tests with Cache-Control for performance. - config.serve_static_assets = true + # Disabled since we dont use it and this option is deprecated from Rails 4.2 onwards + # config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" # Show full error reports and disable caching. diff --git a/test/generators/component_generator_test.rb b/test/generators/component_generator_test.rb index e64bf13e..173ce34d 100644 --- a/test/generators/component_generator_test.rb +++ b/test/generators/component_generator_test.rb @@ -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))) diff --git a/test/react/jsx_test.rb b/test/react/jsx_test.rb index ce71a20c..7032b43e 100644 --- a/test/react/jsx_test.rb +++ b/test/react/jsx_test.rb @@ -29,19 +29,24 @@ def transform(code) class JSXTransformTest < ActionDispatch::IntegrationTest setup do - clear_sprockets_cache + reset_transformer end teardown do + reset_transformer + end + + def reset_transformer clear_sprockets_cache - React::JSX.transformer_class = React::JSX::Transformer + React::JSX.transformer_class = React::JSX::BabelTransformer React::JSX.transform_options = {} end test 'asset pipeline should transform JSX' do get '/assets/example.js' assert_response :success - assert_equal EXPECTED_JS, @response.body + + assert_equal EXPECTED_JS.gsub(/\s/, ''), @response.body.gsub(/\s/, '') end test 'asset pipeline should transform JSX + Coffeescript' do @@ -54,6 +59,39 @@ class JSXTransformTest < ActionDispatch::IntegrationTest assert_equal EXPECTED_JS_2.gsub(/\s/, ''), @response.body.gsub(/\s/, '') end + test 'use a custom transformer' do + React::JSX.transformer_class = NullTransformer + manually_expire_asset('example2.js') + get '/assets/example2.js' + assert_equal "TRANSFORMED CODE!;\n", @response.body + end + + def test_babel_transformer_accepts_babel_transformation_options + React::JSX.transform_options = {blacklist: ['spec.functionName', 'validation.react', "strict"]} + get '/assets/example.js' + assert_response :success + + assert !@response.body.include?('strict') + end + +end + +class JSXTransformerTest < ActionDispatch::IntegrationTest + + setup do + reset_transformer + end + + teardown do + reset_transformer + end + + def reset_transformer + clear_sprockets_cache + React::JSX.transformer_class = React::JSX::JSXTransformer + React::JSX.transform_options = {} + end + test 'can use dropped-in version of JSX transformer' do hidden_path = Rails.root.join("vendor/assets/react/JSXTransformer__.js") replacing_path = Rails.root.join("vendor/assets/react/JSXTransformer.js") @@ -99,10 +137,4 @@ class JSXTransformTest < ActionDispatch::IntegrationTest assert_equal 'test_confirmation_token_jsx_transformed;', @response.body end - test 'use a custom transformer' do - React::JSX.transformer_class = NullTransformer - manually_expire_asset('example2.js') - get '/assets/example2.js' - assert_equal "TRANSFORMED CODE!;\n", @response.body - end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 1e03862e..0c6b4129 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -6,13 +6,13 @@ # Configure Rails Environment ENV["RAILS_ENV"] = "test" -require File.expand_path("../dummy/config/environment.rb", __FILE__) +require File.expand_path("../dummy/config/environment.rb", __FILE__) require "rails/test_help" require "rails/generators" require "pathname" require 'minitest/mock' -CACHE_PATH = Pathname.new File.expand_path("../dummy/tmp/cache", __FILE__) +CACHE_PATH = Pathname.new File.expand_path("../dummy/tmp/cache", __FILE__) Rails.backtrace_cleaner.remove_silencers! @@ -40,6 +40,10 @@ def asset.fresh?(env); false; end ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) end +if ActiveSupport::TestCase.respond_to?(:test_order=) + ActiveSupport::TestCase.test_order = :random +end + def wait_for_turbolinks_to_be_available sleep(1) end