react-rails 0.13.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 83ce3f640e7f27ac175bc2dd031197ab9f7978e4
4
- data.tar.gz: 52408128f938aa4f6a1608fbd0eef1b73213d1d6
3
+ metadata.gz: ecb96344ad4094e2bb7d7ffe7b45455b807fc7a3
4
+ data.tar.gz: 8003608ada04a2d20573621278faa29daf6c838c
5
5
  SHA512:
6
- metadata.gz: a27db732edf425e1e10942ad7768ba5b2bb705c8a3eceae4443dd74541ab9c0871da53cf63d71a57de84af2d393332059c09bb97216d3096d47d124f4b1aa822
7
- data.tar.gz: c7fd4405e8da388da7c2c3f9b12ed5de3526596abac96f94ee2b78310e0b66014da7cda9eae6b1dc53577e7ff4b4c32a7564a3910e20c698537f1929db6ed98a
6
+ metadata.gz: fa90a0d40e3c856840d1b17bc207da5e2bc48f3b1587368eb97c6f4291776253a3b859fca7dbe01da62dd3c1ddd50dc2299bf80c3502bcc513b6e7fb3a9927b7
7
+ data.tar.gz: b126df93a0c17fba3c78e71433fc77d9688a6d3be7fb3ba45f1e968a24a8caaa151b29a5a9c3cccd29a446ddccf17f8409268c54476b7127ef78319c3ad26dfa
data/README.md CHANGED
@@ -1,90 +1,253 @@
1
- # react-rails [![Build Status](https://travis-ci.org/reactjs/react-rails.png)](https://travis-ci.org/reactjs/react-rails) [![Code Climate](https://codeclimate.com/github/reactjs/react-rails.png)](https://codeclimate.com/github/reactjs/react-rails)
1
+ [![Gem](https://img.shields.io/gem/v/react-rails.svg?style=flat-square)](http://rubygems.org/gems/react-rails)
2
+ [![Build Status](https://img.shields.io/travis/reactjs/react-rails/master.svg?style=flat-square)](https://travis-ci.org/reactjs/react-rails)
3
+ [![Gemnasium](https://img.shields.io/gemnasium/reactjs/react-rails.svg?style=flat-square)](https://gemnasium.com/reactjs/react-rails)
4
+ [![Code Climate](https://img.shields.io/codeclimate/github/reactjs/react-rails.svg?style=flat-square)](https://codeclimate.com/github/reactjs/react-rails)
2
5
 
3
- react-rails is a ruby gem which makes it easier 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 application.
6
+ * * *
4
7
 
5
- This is done in 2 ways:
8
+ # react-rails
6
9
 
7
- 1. making it easy to include `react.js` as part of your dependencies in `application.js`.
8
- 2. transforming JSX into regular JS on request, or as part of asset precompilation.
9
10
 
11
+ `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.1+) application. `react-rails` can:
10
12
 
11
- ## Installation
13
+ - Provide [various `react` builds](#reactjs-builds) to your asset bundle
14
+ - Transform [`.jsx` in the asset pipeline](#jsx)
15
+ - [Render components into views and mount them](#rendering--mounting) via view helper & `react_ujs`
16
+ - [Render components server-side](#server-rendering) with `prerender: true`.
17
+ - [Generate components](#component-generator) with a Rails generator
12
18
 
13
- We're specifically targeting versions of Ruby on Rails which make use of the asset pipeline, which means Rails 3.1+.
19
+ ## Installation
14
20
 
15
- As with all gem dependencies, we strongly recommend adding `react-rails` to your `Gemfile` and using `bundler` to manage your application's dependencies.
21
+ Add `react-rails` to your gemfile:
16
22
 
17
23
  ```ruby
18
- # Gemfile
24
+ gem 'react-rails', '~> 1.0'
25
+ ```
19
26
 
20
- gem 'react-rails', '~> 0.12.0.0'
27
+ Next, run the installation script.
28
+
29
+ ```bash
30
+ rails g react:install
21
31
  ```
22
32
 
33
+ This will:
34
+ - create a `components.js` manifest file and a `app/assets/javascripts/components/` directory, where you will put your components
35
+ - place the following in your `application.js`:
36
+
37
+ ```js
38
+ //= require react
39
+ //= require react_ujs
40
+ //= require components
41
+ ```
23
42
 
24
43
  ## Usage
25
44
 
26
- ### react.js
45
+ ### React.js builds
27
46
 
28
- 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:
47
+ 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:
48
+
49
+ ```ruby
50
+ # config/environments/development.rb
51
+ MyApp::Application.configure do
52
+ config.react.variant = :development
53
+ end
29
54
 
30
- You can `require` it in your manifest:
55
+ # config/environments/production.rb
56
+ MyApp::Application.configure do
57
+ config.react.variant = :production
58
+ end
59
+ ```
31
60
 
32
- ```js
33
- // app/assets/application.js
61
+ To include add-ons, use this config:
34
62
 
35
- //= require react
63
+ ```ruby
64
+ MyApp::Application.configure do
65
+ config.react.addons = true # defaults to false
66
+ end
67
+ ```
68
+
69
+ After restarting your Rails server, `//= require react` will provide the build of React.js which was specified by the configurations.
70
+
71
+ `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.
72
+
73
+ ### JSX
74
+
75
+ After installing `react-rails`, restart your server. Now, `.js.jsx` files will be transformed in the asset pipeline.
76
+
77
+ You can use JSX `--harmony` or `--strip-types` options by adding a configuration:
78
+
79
+ ```ruby
80
+ config.react.jsx_transform_options = {
81
+ harmony: true,
82
+ strip_types: true, # for removing Flow type annotations
83
+ }
84
+ ```
85
+
86
+ To use CoffeeScript, create `.js.jsx.coffee` files and embed JSX inside backticks, for example:
87
+
88
+ ```coffee
89
+ Component = React.createClass
90
+ render: ->
91
+ `<ExampleComponent videos={this.props.videos} />`
36
92
  ```
37
93
 
38
- Alternatively, you can include it directly as a separate script tag:
94
+ ### Rendering & mounting
95
+
96
+ `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))
97
+
98
+ The __view helper__ puts a `div` on the page with the requested component class & props. For example:
39
99
 
40
100
  ```erb
41
- # app/views/layouts/application.erb.html
101
+ <%= react_component('HelloMessage', name: 'John') %>
102
+ <!-- becomes: -->
103
+ <div data-react-class="HelloMessage" data-react-props="{&quot;name&quot;:&quot;John&quot;}"></div>
104
+ ```
105
+
106
+ 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`).
107
+
108
+ `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.
42
109
 
43
- <%= javascript_include_tag "react" %>
110
+ The view helper's signature is
111
+
112
+ ```ruby
113
+ react_component(component_class_name, props={}, html_options={})
44
114
  ```
45
115
 
116
+ - `component_class_name` is a string which names a globally-accessible component class. It may have dots (eg, `"MyApp.Header.MenuItem"`).
117
+ - `props` is either an object that responds to `#to_json` or an already-stringified JSON object (eg, made with Jbuilder, see note below)
118
+ - `html_options` may include:
119
+ - `tag:` to use an element other than a `div` to embed `data-react-class` and `-props`.
120
+ - `prerender: true` to render the component on the server.
121
+ - `**other` Any other arguments (eg `class:`, `id:`) are passed through to [`content_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag).
46
122
 
47
- ### JSX
48
123
 
49
- To transform your JSX into JS, simply create `.js.jsx` files. These files will be transformed on request, or precompiled as part of the `assets:precompile` task.
124
+ ### Server rendering
125
+
126
+ To render components on the server, pass `prerender: true` to `react_component`:
50
127
 
128
+ ```erb
129
+ <%= react_component('HelloMessage', {name: 'John'}, {prerender: true}) %>
130
+ <!-- becomes: -->
131
+ <div data-react-class="HelloMessage" data-react-props="{&quot;name&quot;:&quot;John&quot;}">
132
+ <h1>Hello, John!</h1>
133
+ </div>
134
+ ```
135
+
136
+ _(It will be also be mounted by the UJS on page load.)_
51
137
 
52
- ## Configuring
138
+ There are some requirements for this to work:
53
139
 
54
- ### Variants
140
+ - `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).
141
+ - 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:
55
142
 
56
- There are 2 variants available. `:development` gives you the unminified version of React. This provides extra debugging and error prevention. `:production` gives you the minified version of React which strips out comments and helpful warnings, and minifies.
143
+ ```coffee
144
+ # @ is `window`:
145
+ @Component = React.createClass
146
+ render: ->
147
+ `<ExampleComponent videos={this.props.videos} />`
148
+ ```
149
+ - 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 :(
150
+
151
+ You can configure your pool of JS virtual machines and specify where it should load code:
57
152
 
58
153
  ```ruby
59
- # config/environments/development.rb
154
+ # config/environments/application.rb
155
+ # These are the defaults if you dont specify any yourself
60
156
  MyApp::Application.configure do
61
- config.react.variant = :development
157
+ # renderer pool size:
158
+ config.react.max_renderers = 10
159
+ # prerender timeout, in seconds:
160
+ config.react.timeout = 20
161
+ # where to get React.js source:
162
+ config.react.react_js = lambda { File.read(::Rails.application.assets.resolve('react.js')) }
163
+ # array of filenames that will be requested from the asset pipeline
164
+ # and concatenated:
165
+ config.react.component_filenames = ['components.js']
166
+ # server-side console.log, console.warn, and console.error messages will be replayed on the client
167
+ # (you can set this to `true` in config/enviroments/development.rb to replay in development only)
168
+ config.react.replay_console = false
62
169
  end
170
+ ```
63
171
 
64
- # config/environments/production.rb
65
- MyApp::Application.configure do
66
- config.react.variant = :production
67
- end
172
+ ### Component generator
173
+
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.
175
+
176
+ For example:
177
+
178
+ ```shell
179
+ rails generate react:component Post title:string body:string published:bool published_by:instanceOf{Person}
68
180
  ```
69
181
 
70
- ### Add-ons
182
+ would generate the following in `app/assets/javascripts/components/post.js.jsx`:
183
+
184
+ ```jsx
185
+ var Post = React.createClass({
186
+ propTypes: {
187
+ title: React.PropTypes.string,
188
+ body: React.PropTypes.string,
189
+ published: React.PropTypes.bool,
190
+ publishedBy: React.PropTypes.instanceOf(Person)
191
+ },
192
+
193
+ render: function() {
194
+ return (
195
+ <div>
196
+ <div>Title: {this.props.title}</div>
197
+ <div>Body: {this.props.body}</div>
198
+ <div>Published: {this.props.published}</div>
199
+ <div>Published By: {this.props.published_by}</div>
200
+ </div>
201
+ );
202
+ }
203
+ });
204
+ ```
205
+
206
+ The generator can use the following arguments to create basic propTypes:
207
+
208
+ * any
209
+ * array
210
+ * bool
211
+ * element
212
+ * func
213
+ * number
214
+ * object
215
+ * node
216
+ * shape
217
+ * string
218
+
219
+ The following additional arguments have special behavior:
71
220
 
72
- Beginning with React v0.5, there is another type of build. This build ships with some "add-ons" that might be useful - [take a look at the React documentation for details](http://facebook.github.io/react/docs/addons.html). In order to make these available, we've added another configuration (which defaults to `false`).
221
+ * `instanceOf` takes an optional class name in the form of {className}
222
+ * `oneOf` behaves like an enum, and takes an optional list of strings in the form of `'name:oneOf{one,two,three}'`.
223
+ * `oneOfType` takes an optional list of react and custom types in the form of `'model:oneOfType{string,number,OtherType}'`
224
+
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.
226
+
227
+ ### Jbuilder & react-rails
228
+
229
+ If you use Jbuilder to pass 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:
73
230
 
74
231
  ```ruby
75
- MyApp::Application.configure do
76
- config.react.addons = true
232
+ # BAD: returns a stringified array
233
+ json.array!(@messages) do |message|
234
+ json.extract! message, :id, :name
235
+ json.url message_url(message, format: :json)
77
236
  end
78
- ```
79
237
 
238
+ # GOOD: returns a stringified hash
239
+ json.messages(@messages) do |message|
240
+ json.extract! message, :id, :name
241
+ json.url message_url(message, format: :json)
242
+ end
243
+ ```
80
244
 
81
245
  ## CoffeeScript
82
246
 
83
- 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:
247
+ It is possible to use JSX with CoffeeScript. The caveat is that you will still need to include the docblock. Since CoffeeScript doesn't allow `/* */` style comments, we need to do something a little different. We also need to embed JSX inside backticks so CoffeeScript ignores the syntax it doesn't understand. Here's an example:
84
248
 
85
249
  ```coffee
86
250
  Component = React.createClass
87
251
  render: ->
88
252
  `<ExampleComponent videos={this.props.videos} />`
89
253
  ```
90
-
@@ -0,0 +1,104 @@
1
+ /*globals React, Turbolinks*/
2
+
3
+ // Unobtrusive scripting adapter for React
4
+ ;(function(document, window) {
5
+ // jQuery is optional. Use it to support legacy browsers.
6
+ var $ = (typeof window.jQuery !== 'undefined') && window.jQuery;
7
+
8
+ // create the namespace
9
+ window.ReactRailsUJS = {
10
+ CLASS_NAME_ATTR: 'data-react-class',
11
+ PROPS_ATTR: 'data-react-props',
12
+ RAILS_ENV_DEVELOPMENT: <%= Rails.env == "development" %>,
13
+ // helper method for the mount and unmount methods to find the
14
+ // `data-react-class` DOM elements
15
+ findDOMNodes: function(searchSelector) {
16
+ // we will use fully qualified paths as we do not bind the callbacks
17
+ var selector;
18
+ if (typeof searchSelector === 'undefined') {
19
+ var selector = '[' + window.ReactRailsUJS.CLASS_NAME_ATTR + ']';
20
+ } else {
21
+ var selector = searchSelector + ' [' + window.ReactRailsUJS.CLASS_NAME_ATTR + ']';
22
+ }
23
+
24
+ if ($) {
25
+ return $(selector);
26
+ } else {
27
+ return document.querySelectorAll(selector);
28
+ }
29
+ },
30
+
31
+ mountComponents: function(searchSelector) {
32
+ var nodes = window.ReactRailsUJS.findDOMNodes(searchSelector);
33
+
34
+ for (var i = 0; i < nodes.length; ++i) {
35
+ var node = nodes[i];
36
+ var className = node.getAttribute(window.ReactRailsUJS.CLASS_NAME_ATTR);
37
+
38
+ // Assume className is simple and can be found at top-level (window).
39
+ // Fallback to eval to handle cases like 'My.React.ComponentName'.
40
+ var constructor = window[className] || eval.call(window, className);
41
+ var propsJson = node.getAttribute(window.ReactRailsUJS.PROPS_ATTR);
42
+ var props = propsJson && JSON.parse(propsJson);
43
+
44
+ React.render(React.createElement(constructor, props), node);
45
+ }
46
+ },
47
+
48
+ unmountComponents: function(searchSelector) {
49
+ var nodes = window.ReactRailsUJS.findDOMNodes(searchSelector);
50
+
51
+ for (var i = 0; i < nodes.length; ++i) {
52
+ var node = nodes[i];
53
+
54
+ React.unmountComponentAtNode(node);
55
+ }
56
+ }
57
+ };
58
+
59
+ // functions not exposed publicly
60
+ function handleTurbolinksEvents () {
61
+ var handleEvent;
62
+ var unmountEvent;
63
+
64
+ if ($) {
65
+ handleEvent = function(eventName, callback) {
66
+ $(document).on(eventName, callback);
67
+ };
68
+
69
+ } else {
70
+ handleEvent = function(eventName, callback) {
71
+ document.addEventListener(eventName, callback);
72
+ };
73
+ }
74
+
75
+ if (Turbolinks.EVENTS) {
76
+ unmountEvent = Turbolinks.EVENTS.BEFORE_UNLOAD;
77
+ } else {
78
+ unmountEvent = 'page:receive';
79
+ Turbolinks.pagesCached(0);
80
+
81
+ if (window.ReactRailsUJS.RAILS_ENV_DEVELOPMENT) {
82
+ console.warn('The Turbolinks cache has been disabled (Turbolinks >= 2.4.0 is recommended). See https://github.com/reactjs/react-rails/issues/87 for more information.');
83
+ }
84
+ }
85
+ handleEvent('page:change', function() {window.ReactRailsUJS.mountComponents()});
86
+ handleEvent(unmountEvent, function() {window.ReactRailsUJS.unmountComponents()});
87
+ }
88
+
89
+ function handleNativeEvents() {
90
+ if ($) {
91
+ $(function() {window.ReactRailsUJS.mountComponents()});
92
+ $(window).unload(function() {window.ReactRailsUJS.unmountComponents()});
93
+ } else {
94
+ document.addEventListener('DOMContentLoaded', function() {window.ReactRailsUJS.mountComponents()});
95
+ window.addEventListener('unload', function() {window.ReactRailsUJS.unmountComponents()});
96
+ }
97
+ }
98
+
99
+ if (typeof Turbolinks !== 'undefined' && Turbolinks.supported) {
100
+ handleTurbolinksEvents();
101
+ } else {
102
+ handleNativeEvents();
103
+ }
104
+ })(document, window);
@@ -0,0 +1,127 @@
1
+ module React
2
+ module Generators
3
+ class ComponentGenerator < ::Rails::Generators::NamedBase
4
+ source_root File.expand_path '../../templates', __FILE__
5
+ desc <<-DESC.strip_heredoc
6
+ Description:
7
+ Scaffold a react component into app/assets/javascripts/components.
8
+ The generated component will include a basic render function and a PropTypes
9
+ hash to help with development.
10
+
11
+ Available field types:
12
+
13
+ Basic prop types do not take any additional arguments. If you do not specify
14
+ a prop type, the generic node will be used. The basic types available are:
15
+
16
+ any
17
+ array
18
+ bool
19
+ element
20
+ func
21
+ number
22
+ object
23
+ node
24
+ shape
25
+ string
26
+
27
+ Special PropTypes take additional arguments in {}, and must be enclosed in
28
+ single quotes to keep bash from expanding the arguments in {}.
29
+
30
+ instanceOf
31
+ takes an optional class name in the form of {className}
32
+
33
+ oneOf
34
+ behaves like an enum, and takes an optional list of strings that will
35
+ be allowed in the form of 'name:oneOf{one,two,three}'.
36
+
37
+ oneOfType.
38
+ oneOfType takes an optional list of react and custom types in the form of
39
+ 'model:oneOfType{string,number,OtherType}'
40
+
41
+ Examples:
42
+ rails g react:component person name
43
+ rails g react:component restaurant name:string rating:number owner:instanceOf{Person}
44
+ rails g react:component food 'kind:oneOf{meat,cheese,vegetable}'
45
+ rails g react:component events 'location:oneOfType{string,Restaurant}'
46
+ DESC
47
+
48
+ argument :attributes,
49
+ :type => :array,
50
+ :default => [],
51
+ :banner => "field[:type] field[:type] ..."
52
+
53
+ REACT_PROP_TYPES = {
54
+ "node" => 'React.PropTypes.node',
55
+ "bool" => 'React.PropTypes.bool',
56
+ "boolean" => 'React.PropTypes.bool',
57
+ "string" => 'React.PropTypes.string',
58
+ "number" => 'React.PropTypes.number',
59
+ "object" => 'React.PropTypes.object',
60
+ "array" => 'React.PropTypes.array',
61
+ "shape" => 'React.PropTypes.shape({})',
62
+ "element" => 'React.PropTypes.element',
63
+ "func" => 'React.PropTypes.func',
64
+ "function" => 'React.PropTypes.func',
65
+ "any" => 'React.PropTypes.any',
66
+
67
+ "instanceOf" => ->(type) {
68
+ 'React.PropTypes.instanceOf(%s)' % type.to_s.camelize
69
+ },
70
+
71
+ "oneOf" => ->(*options) {
72
+ enums = options.map{|k| "'#{k.to_s}'"}.join(',')
73
+ 'React.PropTypes.oneOf([%s])' % enums
74
+ },
75
+
76
+ "oneOfType" => ->(*options) {
77
+ types = options.map{|k| "#{lookup(k.to_s, k.to_s)}" }.join(',')
78
+ 'React.PropTypes.oneOfType([%s])' % types
79
+ },
80
+ }
81
+
82
+ def create_component_file
83
+ extension = "js.jsx"
84
+ file_path = File.join('app/assets/javascripts/components', "#{file_name}.#{extension}")
85
+ template("component.#{extension}", file_path)
86
+ end
87
+
88
+ private
89
+
90
+ def parse_attributes!
91
+ self.attributes = (attributes || []).map do |attr|
92
+ name, type, options = "", "", ""
93
+ options_regex = /(?<options>{.*})/
94
+
95
+ name, type = attr.split(':')
96
+
97
+ if matchdata = options_regex.match(type)
98
+ options = matchdata[:options]
99
+ type = type.gsub(options_regex, '')
100
+ end
101
+
102
+ { :name => name, :type => lookup(type, options) }
103
+ end
104
+ end
105
+
106
+ def self.lookup(type = "node", options = "")
107
+ react_prop_type = REACT_PROP_TYPES[type]
108
+ if react_prop_type.blank?
109
+ if type =~ /^[[:upper:]]/
110
+ react_prop_type = REACT_PROP_TYPES['instanceOf']
111
+ else
112
+ react_prop_type = REACT_PROP_TYPES['node']
113
+ end
114
+ end
115
+
116
+ options = options.to_s.gsub(/[{}]/, '').split(',')
117
+
118
+ react_prop_type = react_prop_type.call(*options) if react_prop_type.respond_to? :call
119
+ react_prop_type
120
+ end
121
+
122
+ def lookup(type = "node", options = "")
123
+ self.class.lookup(type, options)
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,58 @@
1
+ module React
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ source_root File.expand_path '../../templates', __FILE__
5
+
6
+ desc 'Create default react.js folder layout and prep application.js'
7
+
8
+ class_option :skip_git,
9
+ type: :boolean,
10
+ aliases: '-g',
11
+ default: false,
12
+ desc: 'Skip Git keeps'
13
+
14
+ def create_directory
15
+ empty_directory 'app/assets/javascripts/components'
16
+ create_file 'app/assets/javascripts/components/.gitkeep' unless options[:skip_git]
17
+ end
18
+
19
+ def inject_react
20
+ require_react = "//= require react\n"
21
+
22
+ if manifest.exist?
23
+ manifest_contents = File.read(manifest)
24
+
25
+ if manifest_contents.include? 'require turbolinks'
26
+ inject_into_file manifest, require_react, {after: "//= require turbolinks\n"}
27
+ elsif manifest_contents.include? 'require_tree'
28
+ inject_into_file manifest, require_react, {before: '//= require_tree'}
29
+ else
30
+ append_file manifest, require_react
31
+ end
32
+ else
33
+ create_file manifest, require_react
34
+ end
35
+ end
36
+
37
+ def inject_components
38
+ inject_into_file manifest, "//= require components\n", {after: "//= require react\n"}
39
+ end
40
+
41
+ def inject_react_ujs
42
+ inject_into_file manifest, "//= require react_ujs\n", {after: "//= require react\n"}
43
+ end
44
+
45
+ def create_components
46
+ components_js = "//= require_tree ./components\n"
47
+ components_file = File.join(*%w(app assets javascripts components.js))
48
+ create_file components_file, components_js
49
+ end
50
+
51
+ private
52
+
53
+ def manifest
54
+ Pathname.new(destination_root).join('app/assets/javascripts', 'application.js')
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ var <%= file_name.camelize %> = React.createClass({
2
+ <% if attributes.size > 0 -%>
3
+ propTypes: {
4
+ <% attributes.each_with_index do |attribute, idx| -%>
5
+ <%= attribute[:name].camelize(:lower) %>: <%= attribute[:type] %><% if (idx < attributes.length-1) %>,<% end %>
6
+ <% end -%>
7
+ },
8
+ <% end -%>
9
+
10
+ render: function() {
11
+ <% if attributes.size > 0 -%>
12
+ return (
13
+ <div>
14
+ <% attributes.each do |attribute| -%>
15
+ <div><%= attribute[:name].titleize %>: {this.props.<%= attribute[:name] %>}</div>
16
+ <% end -%>
17
+ </div>
18
+ );
19
+ <% else -%>
20
+ return <div />;
21
+ <% end -%>
22
+ }
23
+ });
@@ -1,3 +1,5 @@
1
1
  require 'react/jsx'
2
+ require 'react/renderer'
2
3
  require 'react/rails'
4
+ require 'react/console'
3
5
 
@@ -0,0 +1,30 @@
1
+ module React
2
+ class Console
3
+ def self.polyfill_js
4
+ # Overwrite global `console` object with something that can capture messages
5
+ # to return to client later for debugging
6
+ <<-JS
7
+ var console = { history: [] };
8
+ ['error', 'log', 'info', 'warn'].forEach(function (fn) {
9
+ console[fn] = function () {
10
+ console.history.push({level: fn, arguments: Array.prototype.slice.call(arguments)});
11
+ };
12
+ });
13
+ JS
14
+ end
15
+
16
+ def self.replay_as_script_js
17
+ <<-JS
18
+ (function (history) {
19
+ if (history && history.length > 0) {
20
+ result += '\\n<scr'+'ipt>';
21
+ history.forEach(function (msg) {
22
+ result += '\\nconsole.' + msg.level + '.apply(console, ' + JSON.stringify(msg.arguments) + ');';
23
+ });
24
+ result += '\\n</scr'+'ipt>';
25
+ }
26
+ })(console.history);
27
+ JS
28
+ end
29
+ end
30
+ end
@@ -1,21 +1,36 @@
1
1
  require 'execjs'
2
2
  require 'react/source'
3
3
  require 'react/jsx/template'
4
+ require 'rails'
4
5
 
5
6
  module React
6
7
  module JSX
8
+ mattr_accessor :transform_options
9
+
7
10
  def self.context
8
- # TODO: create React::Source::contents_for
9
- contents =
10
- # If execjs uses therubyracer, there is no 'global'. Make sure
11
- # we have it so JSX script can work properly.
12
- 'var global = global || this;' +
13
- File.read(React::Source.bundled_path_for('JSXTransformer.js'))
14
- @context ||= ExecJS.compile(contents)
11
+ # lazily loaded during first request and reloaded every time when in dev or test
12
+ unless @context && ::Rails.env.production?
13
+ contents =
14
+ # If execjs uses therubyracer, there is no 'global'. Make sure
15
+ # we have it so JSX script can work properly.
16
+ 'var global = global || this;' +
17
+
18
+ # search for transformer file using sprockets - allows user to override
19
+ # this file in his own application
20
+ File.read(::Rails.application.assets.resolve('JSXTransformer.js'))
21
+
22
+ @context = ExecJS.compile(contents)
23
+ end
24
+
25
+ @context
15
26
  end
16
27
 
17
- def self.transform(code)
18
- result = context.call('JSXTransformer.transform', code)
28
+ def self.transform(code, options={})
29
+ js_options = {
30
+ stripTypes: options[:strip_types],
31
+ harmony: options[:harmony],
32
+ }
33
+ result = context.call('JSXTransformer.transform', code, js_options)
19
34
  return result['code']
20
35
  end
21
36
  end
@@ -9,8 +9,8 @@ module React
9
9
  def prepare
10
10
  end
11
11
 
12
- def evaluate(scopre, locals, &block)
13
- @output ||= JSX::transform(data)
12
+ def evaluate(scope, locals, &block)
13
+ @output ||= JSX::transform(data, JSX.transform_options)
14
14
  end
15
15
  end
16
16
  end
@@ -1,2 +1,3 @@
1
1
  require 'react/rails/railtie'
2
2
  require 'react/rails/engine'
3
+ require 'react/rails/view_helper'
@@ -1,7 +1,7 @@
1
1
  module React
2
2
  module Rails
3
3
  class Engine < ::Rails::Engine
4
- initializer "react_rails.setup_engine", :after => "sprockets.environment", :group => :all do |app|
4
+ initializer "react_rails.setup_engine", :group => :all do |app|
5
5
  app.assets.register_engine '.jsx', React::JSX::Template
6
6
  end
7
7
  end
@@ -5,32 +5,83 @@ module React
5
5
  class Railtie < ::Rails::Railtie
6
6
  config.react = ActiveSupport::OrderedOptions.new
7
7
 
8
- initializer "react_rails.setup_vendor", :after => "sprockets.environment", group: :all do |app|
9
- variant = app.config.react.variant
8
+ # Sensible defaults. Can be overridden in application.rb
9
+ config.react.variant = (::Rails.env.production? ? :production : :development)
10
+ config.react.addons = false
11
+ config.react.jsx_transform_options = {}
12
+ # Server-side rendering
13
+ config.react.max_renderers = 10
14
+ config.react.timeout = 20 #seconds
15
+ config.react.react_js = lambda {File.read(::Rails.application.assets.resolve('react.js'))}
16
+ config.react.component_filenames = ['components.js']
10
17
 
18
+ # Watch .jsx files for changes in dev, so we can reload the JS VMs with the new JS code.
19
+ initializer "react_rails.add_watchable_files", group: :all do |app|
20
+ app.config.watchable_files.concat Dir["#{app.root}/app/assets/javascripts/**/*.jsx*"]
21
+ end
22
+
23
+ # Include the react-rails view helper lazily
24
+ initializer "react_rails.setup_view_helpers", group: :all do |app|
25
+ React::JSX.transform_options = app.config.react.jsx_transform_options
26
+ ActiveSupport.on_load(:action_view) do
27
+ include ::React::Rails::ViewHelper
28
+ end
29
+ end
30
+
31
+ initializer "react_rails.setup_vendor", group: :all do |app|
11
32
  # Mimic behavior of ember-rails...
12
33
  # We want to include different files in dev/prod. The unminified builds
13
34
  # contain console logging for invariants and logging to help catch
14
35
  # common mistakes. These are all stripped out in the minified build.
15
- if variant = app.config.react.variant || ::Rails.env.test?
16
- variant ||= :development
17
- addons = app.config.react.addons || false
18
-
19
- # Copy over the variant into a path that sprockets will pick up.
20
- # We'll always copy to 'react.js' so that no includes need to change.
21
- # We'll also always copy of JSXTransformer.js
22
- tmp_path = app.root.join('tmp/react-rails')
23
- filename = 'react' + (addons ? '-with-addons' : '') + (variant == :production ? '.min.js' : '.js')
24
- FileUtils.mkdir_p(tmp_path)
25
- FileUtils.cp(::React::Source.bundled_path_for(filename),
26
- tmp_path.join('react.js'))
27
- FileUtils.cp(::React::Source.bundled_path_for('JSXTransformer.js'),
28
- tmp_path.join('JSXTransformer.js'))
29
-
30
- # Make sure it can be found
31
- app.assets.append_path(tmp_path)
36
+
37
+ # Copy over the variant into a path that sprockets will pick up.
38
+ # We'll always copy to 'react.js' so that no includes need to change.
39
+ # We'll also always copy of JSXTransformer.js
40
+ tmp_path = app.root.join('tmp/react-rails')
41
+ filename = 'react' +
42
+ (app.config.react.addons ? '-with-addons' : '') +
43
+ (app.config.react.variant == :production ? '.min.js' : '.js')
44
+ FileUtils.mkdir_p(tmp_path)
45
+ FileUtils.cp(::React::Source.bundled_path_for(filename),
46
+ tmp_path.join('react.js'))
47
+ FileUtils.cp(::React::Source.bundled_path_for('JSXTransformer.js'),
48
+ tmp_path.join('JSXTransformer.js'))
49
+ app.assets.prepend_path tmp_path
50
+
51
+ # Allow overriding react files that are not based on environment
52
+ # e.g. /vendor/assets/react/JSXTransformer.js
53
+ dropin_path = app.root.join("vendor/assets/react")
54
+ app.assets.prepend_path dropin_path if dropin_path.exist?
55
+
56
+ # Allow overriding react files that are based on environment
57
+ # e.g. /vendor/assets/react/development/react.js
58
+ dropin_path_env = app.root.join("vendor/assets/react/#{app.config.react.variant}")
59
+ app.assets.prepend_path dropin_path_env if dropin_path_env.exist?
60
+ end
61
+
62
+
63
+ config.after_initialize do |app|
64
+ # Server Rendering
65
+ # Concat component_filenames together for server rendering
66
+ app.config.react.components_js = lambda {
67
+ app.config.react.component_filenames.map do |filename|
68
+ app.assets[filename].to_s
69
+ end.join(";")
70
+ }
71
+
72
+ do_setup = lambda do
73
+ cfg = app.config.react
74
+ React::Renderer.setup!( cfg.react_js, cfg.components_js, cfg.replay_console,
75
+ {:size => cfg.max_renderers, :timeout => cfg.timeout})
32
76
  end
77
+
78
+ do_setup.call
79
+
80
+ # Reload the JS VMs in dev when files change
81
+ ActionDispatch::Reloader.to_prepare(&do_setup)
33
82
  end
83
+
84
+
34
85
  end
35
86
  end
36
87
  end
@@ -1,8 +1,6 @@
1
1
  module React
2
2
  module Rails
3
- # Version numbers will track react-source, but we'll add another level so
4
- # that we can increment, but have some amount of stability.
5
- VERSION = '0.13.0.0'
3
+ # If you change this, make sure to update VERSIONS.md
4
+ VERSION = '1.0.0'
6
5
  end
7
6
  end
8
-
@@ -0,0 +1,28 @@
1
+ module React
2
+ module Rails
3
+ module ViewHelper
4
+
5
+ # Render a UJS-type HTML tag annotated with data attributes, which
6
+ # are used by react_ujs to actually instantiate the React component
7
+ # on the client.
8
+ #
9
+ def react_component(name, args = {}, options = {}, &block)
10
+ options = {:tag => options} if options.is_a?(Symbol)
11
+ block = Proc.new{concat React::Renderer.render(name, args)} if options[:prerender]
12
+
13
+ html_options = options.reverse_merge(:data => {})
14
+ html_options[:data].tap do |data|
15
+ data[:react_class] = name
16
+ data[:react_props] = React::Renderer.react_props(args) unless args.empty?
17
+ end
18
+ html_tag = html_options[:tag] || :div
19
+
20
+ # remove internally used properties so they aren't rendered to DOM
21
+ html_options.except!(:tag, :prerender)
22
+
23
+ content_tag(html_tag, '', html_options, &block)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,83 @@
1
+ require 'connection_pool'
2
+
3
+ module React
4
+ class Renderer
5
+
6
+ class PrerenderError < RuntimeError
7
+ def initialize(component_name, props, js_message)
8
+ message = ["Encountered error \"#{js_message}\" when prerendering #{component_name} with #{props}",
9
+ js_message.backtrace.join("\n")].join("\n")
10
+ super(message)
11
+ end
12
+ end
13
+
14
+ cattr_accessor :pool
15
+
16
+ def self.setup!(react_js, components_js, replay_console, args={})
17
+ args.assert_valid_keys(:size, :timeout)
18
+ @@react_js = react_js
19
+ @@components_js = components_js
20
+ @@replay_console = replay_console
21
+ @@pool.shutdown{} if @@pool
22
+ reset_combined_js!
23
+ default_pool_options = {:size =>10, :timeout => 20}
24
+ @@pool = ConnectionPool.new(default_pool_options.merge(args)) { self.new }
25
+ end
26
+
27
+ def self.render(component, args={})
28
+ @@pool.with do |renderer|
29
+ renderer.render(component, args)
30
+ end
31
+ end
32
+
33
+ def self.react_props(args={})
34
+ if args.is_a? String
35
+ args
36
+ else
37
+ args.to_json
38
+ end
39
+ end
40
+
41
+ def context
42
+ @context ||= ExecJS.compile(self.class.combined_js)
43
+ end
44
+
45
+ def render(component, args={})
46
+ react_props = React::Renderer.react_props(args)
47
+ jscode = <<-JS
48
+ (function () {
49
+ var result = React.renderToString(React.createElement(#{component}, #{react_props}));
50
+ #{@@replay_console ? React::Console.replay_as_script_js : ''}
51
+ return result;
52
+ })()
53
+ JS
54
+ context.eval(jscode).html_safe
55
+ rescue ExecJS::ProgramError => e
56
+ raise PrerenderError.new(component, react_props, e)
57
+ end
58
+
59
+
60
+ private
61
+
62
+ def self.setup_combined_js
63
+ <<-JS
64
+ var global = global || this;
65
+ var self = self || this;
66
+ var window = window || this;
67
+ #{React::Console.polyfill_js}
68
+ #{@@react_js.call};
69
+ React = global.React;
70
+ #{@@components_js.call};
71
+ JS
72
+ end
73
+
74
+ def self.reset_combined_js!
75
+ @@combined_js = setup_combined_js
76
+ end
77
+
78
+ def self.combined_js
79
+ @@combined_js
80
+ end
81
+
82
+ end
83
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: react-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul O’Shannessy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-10 00:00:00.000000000 Z
11
+ date: 2015-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: appraisal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -25,7 +39,7 @@ dependencies:
25
39
  - !ruby/object:Gem::Version
26
40
  version: 1.2.2
27
41
  - !ruby/object:Gem::Dependency
28
- name: appraisal
42
+ name: coffee-rails
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
@@ -39,14 +53,98 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
- name: coffee-rails
56
+ name: es5-shim-rails
43
57
  requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.5
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.5
69
+ - !ruby/object:Gem::Dependency
70
+ name: jbuilder
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
44
79
  requirements:
45
80
  - - ">="
46
81
  - !ruby/object:Gem::Version
47
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: poltergeist
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 0.3.3
48
90
  type: :development
49
91
  prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 0.3.3
97
+ - !ruby/object:Gem::Dependency
98
+ name: test-unit
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: turbolinks
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 2.0.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 2.0.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: coffee-script-source
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.8'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.8'
139
+ - !ruby/object:Gem::Dependency
140
+ name: connection_pool
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
50
148
  version_requirements: !ruby/object:Gem::Requirement
51
149
  requirements:
52
150
  - - ">="
@@ -84,16 +182,16 @@ dependencies:
84
182
  name: react-source
85
183
  requirement: !ruby/object:Gem::Requirement
86
184
  requirements:
87
- - - '='
185
+ - - "~>"
88
186
  - !ruby/object:Gem::Version
89
- version: 0.13.0
187
+ version: '0.13'
90
188
  type: :runtime
91
189
  prerelease: false
92
190
  version_requirements: !ruby/object:Gem::Requirement
93
191
  requirements:
94
- - - '='
192
+ - - "~>"
95
193
  - !ruby/object:Gem::Version
96
- version: 0.13.0
194
+ version: '0.13'
97
195
  description: Compile your JSX on demand or precompile for production.
98
196
  email:
99
197
  - paul@oshannessy.com
@@ -103,14 +201,21 @@ extra_rdoc_files: []
103
201
  files:
104
202
  - LICENSE
105
203
  - README.md
204
+ - lib/assets/javascripts/react_ujs.js.erb
205
+ - lib/generators/react/component_generator.rb
206
+ - lib/generators/react/install_generator.rb
207
+ - lib/generators/templates/component.js.jsx
106
208
  - lib/react-rails.rb
209
+ - lib/react/console.rb
107
210
  - lib/react/jsx.rb
108
211
  - lib/react/jsx/template.rb
109
212
  - lib/react/rails.rb
110
213
  - lib/react/rails/engine.rb
111
214
  - lib/react/rails/railtie.rb
112
215
  - lib/react/rails/version.rb
113
- homepage: https://github.com/facebook/react-rails
216
+ - lib/react/rails/view_helper.rb
217
+ - lib/react/renderer.rb
218
+ homepage: https://github.com/reactjs/react-rails
114
219
  licenses:
115
220
  - APL 2.0
116
221
  metadata: {}
@@ -130,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
235
  version: '0'
131
236
  requirements: []
132
237
  rubyforge_project:
133
- rubygems_version: 2.2.2
238
+ rubygems_version: 2.4.5
134
239
  signing_key:
135
240
  specification_version: 4
136
241
  summary: React/JSX adapter for the Ruby on Rails asset pipeline.