react_on_rails 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.eslintignore +1 -0
- data/.eslintrc +16 -0
- data/.jscsrc +5 -0
- data/.rubocop.yml +26 -0
- data/.scss-lint.yml +205 -0
- data/.travis.yml +10 -2
- data/Dockerfile +14 -0
- data/README.md +187 -50
- data/Rakefile +89 -4
- data/app/helpers/react_on_rails_helper.rb +148 -82
- data/docker-compose.yml +5 -0
- data/lib/react_on_rails/configuration.rb +12 -5
- data/lib/react_on_rails/react_renderer.rb +115 -22
- data/lib/react_on_rails/version.rb +1 -1
- data/react_on_rails.gemspec +1 -0
- data/ruby-lint.yml +22 -0
- metadata +25 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ff4186feb1c1939c00dd049ef22d868aea660f6
|
4
|
+
data.tar.gz: 0b5808c38aeb2a3092ec374e6fa8ff2871849f57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c3262b0bb0634626438299e5330e4bd4268a743ddb83f795adfbb2a8e29ed3dc9daf4c987dfca29113c68d66d1daf9e2d5b5277bf59b02cb391dfa28ab6f59e
|
7
|
+
data.tar.gz: 0885111f10b06d283bbc39058492a1f5d6e9ff958cc61ba1a4b67ece33a7c78af6006232a1ef380be349d6d7ea8fa2021a022e749574ad9f649fea0107902536
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.eslintignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
node_modules
|
data/.eslintrc
ADDED
data/.jscsrc
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# This is the configuration used to check the rubocop source code.
|
2
|
+
# Check out: https://github.com/bbatsov/rubocop
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
Include:
|
6
|
+
- '**/Rakefile'
|
7
|
+
- '**/config.ru'
|
8
|
+
Exclude:
|
9
|
+
- 'vendor/**/*'
|
10
|
+
- 'spec/fixtures/**/*'
|
11
|
+
- 'node_modules/**/*'
|
12
|
+
- 'db/**/*'
|
13
|
+
- 'db/schema.rb'
|
14
|
+
- 'db/seeds.rb'
|
15
|
+
- 'client/node_modules/**/*'
|
16
|
+
- 'bin/**/*'
|
17
|
+
- !ruby/regexp /old_and_unused\.rb$/
|
18
|
+
|
19
|
+
Metrics/LineLength:
|
20
|
+
Max: 120
|
21
|
+
|
22
|
+
Style/StringLiterals:
|
23
|
+
EnforcedStyle: double_quotes
|
24
|
+
|
25
|
+
Style/Documentation:
|
26
|
+
Enabled: false
|
data/.scss-lint.yml
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
# See http://sass-guidelin.es/#zeros
|
2
|
+
|
3
|
+
scss_files:
|
4
|
+
- 'app/assets/stylesheets/**/*.scss'
|
5
|
+
- 'client/assets/stylesheets/**/*.scss'
|
6
|
+
|
7
|
+
linters:
|
8
|
+
# BangFormat:
|
9
|
+
# enabled: true
|
10
|
+
# space_before_bang: true
|
11
|
+
# space_after_bang: false
|
12
|
+
#
|
13
|
+
# BorderZero:
|
14
|
+
# enabled: true
|
15
|
+
# convention: zero # or `none`
|
16
|
+
#
|
17
|
+
ColorKeyword:
|
18
|
+
enabled: false
|
19
|
+
ColorVariable:
|
20
|
+
enabled: false
|
21
|
+
#
|
22
|
+
# Comment:
|
23
|
+
# enabled: true
|
24
|
+
#
|
25
|
+
# DebugStatement:
|
26
|
+
# enabled: true
|
27
|
+
#
|
28
|
+
# DeclarationOrder:
|
29
|
+
# enabled: true
|
30
|
+
#
|
31
|
+
# DuplicateProperty:
|
32
|
+
# enabled: true
|
33
|
+
#
|
34
|
+
# ElsePlacement:
|
35
|
+
# enabled: true
|
36
|
+
# style: same_line # or 'new_line'
|
37
|
+
#
|
38
|
+
# EmptyLineBetweenBlocks:
|
39
|
+
# enabled: true
|
40
|
+
# ignore_single_line_blocks: true
|
41
|
+
#
|
42
|
+
# EmptyRule:
|
43
|
+
# enabled: true
|
44
|
+
#
|
45
|
+
# FinalNewline:
|
46
|
+
# enabled: true
|
47
|
+
# present: true
|
48
|
+
#
|
49
|
+
HexLength:
|
50
|
+
enabled: true
|
51
|
+
style: long
|
52
|
+
|
53
|
+
HexNotation:
|
54
|
+
enabled: true
|
55
|
+
style: uppercase
|
56
|
+
#
|
57
|
+
# HexValidation:
|
58
|
+
# enabled: true
|
59
|
+
#
|
60
|
+
IdSelector:
|
61
|
+
enabled: true
|
62
|
+
#
|
63
|
+
# ImportantRule:
|
64
|
+
# enabled: true
|
65
|
+
#
|
66
|
+
# ImportPath:
|
67
|
+
# enabled: true
|
68
|
+
# leading_underscore: false
|
69
|
+
# filename_extension: false
|
70
|
+
#
|
71
|
+
# Indentation:
|
72
|
+
# enabled: true
|
73
|
+
# allow_non_nested_indentation: false
|
74
|
+
# character: space # or 'tab'
|
75
|
+
# width: 2
|
76
|
+
#
|
77
|
+
LeadingZero:
|
78
|
+
enabled: true
|
79
|
+
style: include_zero
|
80
|
+
#
|
81
|
+
# MergeableSelector:
|
82
|
+
# enabled: true
|
83
|
+
# force_nesting: true
|
84
|
+
#
|
85
|
+
# NameFormat:
|
86
|
+
# enabled: true
|
87
|
+
# allow_leading_underscore: true
|
88
|
+
# convention: hyphenated_lowercase # or 'camel_case', or 'snake_case', or a regex pattern
|
89
|
+
#
|
90
|
+
# NestingDepth:
|
91
|
+
# enabled: true
|
92
|
+
# max_depth: 3
|
93
|
+
#
|
94
|
+
# PlaceholderInExtend:
|
95
|
+
# enabled: true
|
96
|
+
#
|
97
|
+
# PropertyCount:
|
98
|
+
# enabled: false
|
99
|
+
# include_nested: false
|
100
|
+
# max_properties: 10
|
101
|
+
#
|
102
|
+
# PropertyUnits:
|
103
|
+
# enabled: true
|
104
|
+
# global: [
|
105
|
+
# 'ch', 'em', 'ex', 'rem', # Font-relative lengths
|
106
|
+
# 'cm', 'in', 'mm', 'pc', 'pt', 'px', 'q', # Absolute lengths
|
107
|
+
# 'vh', 'vw', 'vmin', 'vmax', # Viewport-percentage lengths
|
108
|
+
# 'deg', 'grad', 'rad', 'turn', # Angle
|
109
|
+
# 'ms', 's', # Duration
|
110
|
+
# 'Hz', 'kHz', # Frequency
|
111
|
+
# 'dpi', 'dpcm', 'dppx', # Resolution
|
112
|
+
# '%', # Other
|
113
|
+
# ]
|
114
|
+
# properties: {}
|
115
|
+
#
|
116
|
+
# PropertySortOrder:
|
117
|
+
# enabled: true
|
118
|
+
# ignore_unspecified: false
|
119
|
+
# separate_groups: false
|
120
|
+
#
|
121
|
+
# PropertySpelling:
|
122
|
+
# enabled: true
|
123
|
+
# extra_properties: []
|
124
|
+
#
|
125
|
+
# QualifyingElement:
|
126
|
+
# enabled: true
|
127
|
+
# allow_element_with_attribute: false
|
128
|
+
# allow_element_with_class: false
|
129
|
+
# allow_element_with_id: false
|
130
|
+
#
|
131
|
+
# SelectorDepth:
|
132
|
+
# enabled: true
|
133
|
+
# max_depth: 3
|
134
|
+
#
|
135
|
+
# SelectorFormat:
|
136
|
+
# enabled: true
|
137
|
+
# convention: hyphenated_lowercase # or 'strict_BEM', or 'hyphenated_BEM', or 'snake_case', or 'camel_case', or a regex pattern
|
138
|
+
#
|
139
|
+
# Shorthand:
|
140
|
+
# enabled: true
|
141
|
+
# allowed_shorthands: [1, 2, 3]
|
142
|
+
#
|
143
|
+
# SingleLinePerProperty:
|
144
|
+
# enabled: true
|
145
|
+
# allow_single_line_rule_sets: true
|
146
|
+
#
|
147
|
+
# SingleLinePerSelector:
|
148
|
+
# enabled: true
|
149
|
+
#
|
150
|
+
# SpaceAfterComma:
|
151
|
+
# enabled: true
|
152
|
+
#
|
153
|
+
# SpaceAfterPropertyColon:
|
154
|
+
# enabled: true
|
155
|
+
# style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
|
156
|
+
#
|
157
|
+
# SpaceAfterPropertyName:
|
158
|
+
# enabled: true
|
159
|
+
#
|
160
|
+
# SpaceBeforeBrace:
|
161
|
+
# enabled: true
|
162
|
+
# style: space # or 'new_line'
|
163
|
+
# allow_single_line_padding: false
|
164
|
+
#
|
165
|
+
# SpaceBetweenParens:
|
166
|
+
# enabled: true
|
167
|
+
# spaces: 0
|
168
|
+
#
|
169
|
+
# StringQuotes:
|
170
|
+
# enabled: true
|
171
|
+
# style: single_quotes # or double_quotes
|
172
|
+
#
|
173
|
+
# TrailingSemicolon:
|
174
|
+
# enabled: true
|
175
|
+
#
|
176
|
+
# TrailingZero:
|
177
|
+
# enabled: false
|
178
|
+
#
|
179
|
+
# UnnecessaryMantissa:
|
180
|
+
# enabled: true
|
181
|
+
#
|
182
|
+
# UnnecessaryParentReference:
|
183
|
+
# enabled: true
|
184
|
+
#
|
185
|
+
# UrlFormat:
|
186
|
+
# enabled: true
|
187
|
+
#
|
188
|
+
# UrlQuotes:
|
189
|
+
# enabled: true
|
190
|
+
#
|
191
|
+
# VariableForProperty:
|
192
|
+
# enabled: false
|
193
|
+
# properties: []
|
194
|
+
#
|
195
|
+
# VendorPrefix:
|
196
|
+
# enabled: true
|
197
|
+
# identifier_list: base
|
198
|
+
# additional_identifiers: []
|
199
|
+
# excluded_identifiers: []
|
200
|
+
#
|
201
|
+
# ZeroUnit:
|
202
|
+
# enabled: true
|
203
|
+
#
|
204
|
+
# Compass::*:
|
205
|
+
# enabled: false
|
data/.travis.yml
CHANGED
@@ -1,4 +1,12 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
|
4
|
-
|
3
|
+
- 2.2.2
|
4
|
+
gemfile:
|
5
|
+
- spec/dummy/Gemfile
|
6
|
+
install: bundle install
|
7
|
+
script:
|
8
|
+
- rake run_rspec:gem
|
9
|
+
- rake run_rspec:dummy
|
10
|
+
notifications:
|
11
|
+
slack:
|
12
|
+
secure: LfcUk4AJ4vAxWwRIyw4tFh8QNbYefMwfG/oLfsN3CdRMWMOtCOHR1GGsRhAOlfVVJ/FvHqVqWj5gK7z7CaO5Uvl7rD3/zJ8QzExKx/iH9yWj55iIPuKLzwFNnBwRpFW/cqyU2lFPPRxGD50BUn3c+qybkuSqtKZ6qtTowwqlxLa5iyM3N95aZp7MEIKCP7cPcnHfLbJyP8wBpotp/rtw62eXM2HIRJJwgjcp+n+My7VFR9DnBXNFf6R91aZHM4U4cHHDbu15HFtH8honVrzK1JQdyqMNHga+j04dFuaS7z9Q369/hsELMOBp/227+Pz7ZRfWZFK4UASguOvyeX7RmGTRpTuWLm1XJeUzfsPZVROecaSVQBve+U7F12yKqilt97QlvRXn2EGyBILqvxtFNNR4S9kgAf72/6EFgiM1TKq7i9zy6lVOnagU2+7amq7UeopX1uoFsUfNKMR7YbgV1WjF0IK95UP0b0/7ZOJlPYgi5zzkQi129qAFWSMmxGk+ZpsttHh/tjJtvAh0A3mHq/zb5w4ub/MbSyZqeDUNgGj72QArOWUFSAStQT1ybsVLeDoKPgOvVq7OV1D64rpcHjBXcqOCit8tDZ+TqkFhcYJo2cITSaqE4zJXn+4F5s7So5O8CyfKYQq+kFJCooYGmfgTUckJpGl7eIvKmL4TN9Q=
|
data/Dockerfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
FROM ruby:2.2.3
|
2
|
+
MAINTAINER Dylan Grafmyre <dylan@shakacode.com>
|
3
|
+
|
4
|
+
RUN mkdir -p /app
|
5
|
+
WORKDIR /app/
|
6
|
+
# Setup container for linting with ruby and npm
|
7
|
+
RUN gem install rubocop ruby-lint scss_lint
|
8
|
+
RUN curl --silent --location https://deb.nodesource.com/setup_0.12 | bash -
|
9
|
+
RUN apt-get install -y nodejs
|
10
|
+
RUN npm install -g eslint \
|
11
|
+
eslint-config-airbnb \
|
12
|
+
eslint-plugin-react \
|
13
|
+
babel-eslint \
|
14
|
+
jscs
|
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/shakacode/react_on_rails.svg?branch=master)](https://travis-ci.org/shakacode/react_on_rails)
|
2
|
+
[![Coverage Status](https://coveralls.io/repos/shakacode/react_on_rails/badge.svg?branch=master&service=github)](https://coveralls.io/github/shakacode/react_on_rails?branch=master)
|
3
|
+
[![Dependency Status](https://gemnasium.com/shakacode/react_on_rails.svg)](https://gemnasium.com/shakacode/react_on_rails)
|
1
4
|
# React On Rails
|
2
5
|
|
3
|
-
Published: https://rubygems.org/gems/react_on_rails
|
6
|
+
Gem Published: https://rubygems.org/gems/react_on_rails
|
4
7
|
|
5
8
|
See [Action Plan for v1.0](https://github.com/shakacode/react_on_rails/issues/1)
|
6
9
|
|
@@ -16,108 +19,242 @@ Supports:
|
|
16
19
|
6. Server side rendering with fragment caching
|
17
20
|
7. react-router for client side rendering (and maybe server side eventually)
|
18
21
|
|
19
|
-
#
|
20
|
-
|
22
|
+
# Links
|
23
|
+
1. https://github.com/justin808/react-webpack-rails-tutorial/ See https://github.com/shakacode/react-webpack-rails-tutorial/pull/84 for how we integrated it!
|
24
|
+
2. http://www.railsonmaui.com/blog/2014/10/03/integrating-webpack-and-the-es6-transpiler-into-an-existing-rails-project/
|
25
|
+
3. http://forum.railsonmaui.com
|
26
|
+
5. If this project is interesting to you, email me at justin@shakacode.com. We're looking for great
|
27
|
+
developers that want to work with Rails + React with a distributed, worldwide team, for our own
|
28
|
+
products, client work, and open source.
|
21
29
|
|
22
|
-
|
23
|
-
2. [Samnang Chhun](https://github.com/samnang)
|
24
|
-
3. [Alex Fedoseev](https://github.com/alexfedoseev)
|
30
|
+
## Application Installation
|
25
31
|
|
26
|
-
|
32
|
+
Add these lines to your application's Gemfile, sustituting your preferable JavaScript engine.
|
27
33
|
|
28
|
-
|
29
|
-
|
34
|
+
```ruby
|
35
|
+
gem "react_on_rails"
|
36
|
+
gem "therubyracer"
|
37
|
+
```
|
30
38
|
|
31
|
-
|
32
|
-
2. http://www.railsonmaui.com/blog/2014/10/03/integrating-webpack-and-the-es6-transpiler-into-an-existing-rails-project/
|
33
|
-
3. http://forum.railsonmaui.com
|
34
|
-
4. Lots of work to do in terms of docs, tests
|
35
|
-
5. If this project is interesting to you, email me at justin@shakacode.com. We're looking for great
|
36
|
-
developers that want to work with Rails + React with a distributed, worldwide team.
|
39
|
+
And then execute:
|
37
40
|
|
41
|
+
$ bundle
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
*See section below titled "Try it out"*
|
46
|
+
|
47
|
+
### Helper Method
|
48
|
+
The main API is a helper:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
react_component(component_name, props = {}, options = {})
|
52
|
+
```
|
53
|
+
|
54
|
+
Params are:
|
55
|
+
|
56
|
+
* **react_component_name**: can be a React component, created using a ES6 class, or React.createClass,
|
57
|
+
or a `generator function` that returns a React component
|
58
|
+
|
59
|
+
using ES6
|
60
|
+
```javascript
|
61
|
+
let MyReactComponentApp = (props) => <MyReactComponent {...props}/>;
|
62
|
+
```
|
63
|
+
|
64
|
+
or using ES5
|
65
|
+
```javascript
|
66
|
+
var MyReactComponentApp = function(props) { return <YourReactComponent {...props}/>; }
|
67
|
+
```
|
68
|
+
Exposing the react_component_name is necessary to both a plain ReactComponent as well as
|
69
|
+
a generator:
|
70
|
+
For client rendering, expose the react_component_name on window:
|
71
|
+
|
72
|
+
```javascript
|
73
|
+
window.MyReactComponentApp = MyReactComponentApp;
|
74
|
+
```
|
75
|
+
For server rendering, export the react_component_name on global:
|
76
|
+
```javascript
|
77
|
+
global.MyReactComponentApp = MyReactComponentApp;
|
78
|
+
```
|
79
|
+
See `spec/dummy/client/app/startup/serverGlobals.jsx` and
|
80
|
+
`spec/dummy/client/app/startup/ClientApp.jsx` for examples of this.
|
81
|
+
|
82
|
+
* **props**: Ruby Hash which contains the properties to pass to the react object
|
83
|
+
|
84
|
+
* **options:**
|
85
|
+
* **generator_function**: <true/false> default is false, set to true if you want to use a generator function rather than a React Component.
|
86
|
+
* **prerender**: <true/false> set to false when debugging!
|
87
|
+
* **trace**: <true/false> set to true to print additional debugging information in the browser default is true for development, off otherwise
|
88
|
+
* **replay_console**: <true/false> Default is true. False will disable echoing server rendering logs, which can make troubleshooting server rendering difficult.
|
89
|
+
|
90
|
+
## JavaScript
|
91
|
+
|
92
|
+
1. Configure your webpack configuration to create the file used for server rendering if you plan to
|
93
|
+
do server rendering.
|
94
|
+
2. Follow the examples in `spec/dummy/client/app/startup/clientGlobals.jsx` to expose your react components
|
95
|
+
for client side rendering.
|
96
|
+
```ruby
|
97
|
+
import HelloWorld from '../components/HelloWorld';
|
98
|
+
window.HelloWorld = HelloWorld;
|
99
|
+
```
|
100
|
+
3. Follow the examples in `spec/dummy/client/app/startup/serverGlobals.jsx` to expose your react components
|
101
|
+
for client side rendering.
|
102
|
+
```ruby
|
103
|
+
import HelloWorld from '../components/HelloWorld';
|
104
|
+
global.HelloWorld = HelloWorld;
|
105
|
+
```
|
106
|
+
|
107
|
+
## Optional Configuration
|
108
|
+
|
109
|
+
Create a file `config/react_on_rails.rb` to override any defaults. If you don't specify this file,
|
110
|
+
the default options are below.
|
111
|
+
|
112
|
+
The `server_bundle_js_file` must correspond to the bundle you want to use for server rendering.
|
38
113
|
|
39
|
-
|
114
|
+
```ruby
|
115
|
+
# Shown below are the defaults for configuration
|
116
|
+
ReactOnRails.configure do |config|
|
117
|
+
# Client bundles are configured in application.js
|
118
|
+
# Server bundle is a single file for all server rendering of components.
|
119
|
+
config.server_bundle_js_file = "app/assets/javascripts/generated/server.js" # This is the default
|
120
|
+
|
121
|
+
# Below options can be overriden by passing to the helper method.
|
122
|
+
config.prerender = false # default is false
|
123
|
+
config.generator_function = false # default is false, meaning that you expose ReactComponents directly
|
124
|
+
config.trace = Rails.env.development? # default is true for development, off otherwise
|
125
|
+
|
126
|
+
# For server rendering. This can be set to false so that server side messages are discarded.
|
127
|
+
config.replay_console = true # Default is true. Be cautious about turning this off.
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
# Try it out in the simple sample app
|
40
132
|
Contributions and pull requests welcome!
|
41
133
|
|
42
|
-
1. Setup and run the test app
|
134
|
+
1. Setup and run the test app in `spec/dummy`. Note, there's no database.
|
43
135
|
```bash
|
44
136
|
cd spec/dummy
|
45
137
|
bundle
|
46
138
|
npm i
|
47
|
-
foreman start
|
48
|
-
```
|
139
|
+
foreman start
|
140
|
+
```
|
49
141
|
2. Caching is turned for development mode. Open the console and run `Rails.cache.clear` to clear
|
50
142
|
the cache. Note, even if you stop the server, you'll still have the cache entries around.
|
51
143
|
3. Visit http://localhost:3000
|
52
144
|
4. Notice that the first time you hit the page, you'll see a message that server is rendering.
|
53
145
|
See `spec/dummy/app/views/pages/index.html.erb:17` for the generation of that message.
|
146
|
+
5. Look at the layouts in `spec/dummy/app/views/pages` for samples of usage.
|
54
147
|
5. Open up the browser console and see some tracing.
|
55
148
|
6. Open up the source for the page and see the server rendered code.
|
56
|
-
7. If you want to turn off server caching, run the server like:
|
149
|
+
7. If you want to turn off server caching, run the server like:
|
57
150
|
`export RAILS_USE_CACHE=N && foreman start`
|
58
151
|
8. If you click back and forth between the about and react page links, you can see the rails console
|
59
152
|
log as well as the browser console to see what's going on with regards to server rendering and
|
60
153
|
caching.
|
61
154
|
|
62
155
|
# Key Tips
|
63
|
-
1. See sample app in `spec/dummy` for how to set this up.
|
156
|
+
1. See sample app in `spec/dummy` for how to set this up. See note below on ensuring you
|
157
|
+
**DO NOT RUN `rails s` and instead run `foreman start`.
|
158
|
+
2. Test out the different options and study the JSX samples in `spec/dummy/client/app/startup`.
|
159
|
+
3. Experiment with changing the settings on the `render_component` helper calls in the ERB files.
|
64
160
|
2. The file used for server rendering is hard coded as `generated/server.js`
|
65
161
|
(assets/javascripts/generated/server.js).
|
66
|
-
3.
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
162
|
+
3. The default for rendering right now is `prerender: false`. **NOTE:** Server side rendering does
|
163
|
+
not work for some components, namely react-router, that use an async setup for server rendering.
|
164
|
+
You can configure the default for prerender in your config.
|
165
|
+
4. You can expose either a React component or a function that returns a React component. If you
|
166
|
+
wish to create a React component via a function, rather than simply props, then you need to set
|
167
|
+
the property "generator" on that function to true. When that is done, the function is invoked
|
168
|
+
with a single parameter of "props", and that function should return a React element.
|
169
|
+
5. Be sure you can first render your react component client only before you try to debug server
|
170
|
+
rendering!
|
171
|
+
4. Open up the HTML source and take a look at the generated HTML and the JavaScript to see what's
|
172
|
+
going on under the covers. Not that when server rendering is turned on, then you'll see the
|
173
|
+
server rendered react components. When server rendering is turned off, then you'll only see
|
174
|
+
the `div` element where the inline JavaScript will render the component. You might also notice
|
175
|
+
how the props you pass (a Ruby Hash) becomes inline JavaScript on the HTML page.
|
176
|
+
|
177
|
+
TODO: Check if this is true still: If you're only doing client rendering, you still *MUST* create an empty version of this file. This
|
178
|
+
will soon change so that this is not necessary.
|
179
|
+
|
180
|
+
## JavaScript Runtime Configuration
|
181
|
+
See this [discussion on JavaScript performance](https://github.com/shakacode/react_on_rails/issues/21).
|
182
|
+
The net result is that you want to add this line to your Gemfile to get therubyracer as your default
|
183
|
+
JavaScript engine.
|
184
|
+
|
75
185
|
```ruby
|
76
|
-
|
77
|
-
config.bundle_js_file = "app/assets/javascripts/generated/server.js" # This is the default
|
78
|
-
config.prerender = true # default is false
|
79
|
-
end
|
186
|
+
gem "therubyracer"
|
80
187
|
```
|
81
188
|
|
82
|
-
|
83
189
|
## References
|
84
190
|
* [Making the helper for server side rendering work with JS created by Webpack] (https://github.com/reactjs/react-rails/issues/301#issuecomment-133098974)
|
85
191
|
* [Add Demonstration of Server Side Rendering](https://github.com/justin808/react-webpack-rails-tutorial/issues/2)
|
86
192
|
* [Charlie Marsh's article "Rendering React Components on the Server"](http://www.crmarsh.com/react-ssr/)
|
87
193
|
* [Node globals](https://nodejs.org/api/globals.html#globals_global)
|
88
194
|
|
89
|
-
## Installation
|
90
195
|
|
91
|
-
|
196
|
+
## Development Setup for Gem Contributors
|
92
197
|
|
93
|
-
|
94
|
-
|
95
|
-
|
198
|
+
### Initial Setup
|
199
|
+
After checking out the repo, making sure you have rvm and nvm setup (setup ruby and node),
|
200
|
+
cd to `spec/dummy` and run `bin/setup` to install dependencies.
|
201
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
96
202
|
|
97
|
-
|
203
|
+
### Starting the Dummy App
|
204
|
+
To run the test app, it's **CRITICAL** to not just run `rails s`. You have to run `foreman start`.
|
205
|
+
If you don't do this, then `webpack` will not generate a new bundle,
|
206
|
+
and you will be seriously confused when you change JavaScript and the app does not change.
|
98
207
|
|
99
|
-
|
208
|
+
### Install and Release
|
209
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version,
|
210
|
+
update the version number in `version.rb`, and then run `bundle exec rake release`,
|
211
|
+
which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
100
212
|
|
101
|
-
|
213
|
+
### RSpec Testing
|
214
|
+
Run `rake` for testing the gem and `spec/dummy`. Otherwise, the `rspec` command only works for testing within `spec/dummy`.
|
102
215
|
|
103
|
-
|
216
|
+
If you run `rspec` at the top level, you'll see this message: `require': cannot load such file -- rails_helper (LoadError)`
|
104
217
|
|
105
|
-
|
218
|
+
### Debugging
|
219
|
+
Start the sample app like this for some debug printing:
|
220
|
+
```bash
|
221
|
+
TRACE_REACT_ON_RAILS=true && foreman start
|
222
|
+
```
|
106
223
|
|
107
|
-
|
224
|
+
### Linting
|
225
|
+
All linting is performed from the docker container. You will need docker and docker-compose installed
|
226
|
+
locally to lint code changes via the lint container.
|
108
227
|
|
109
|
-
|
228
|
+
* [Install Docker Toolbox for Mac](https://www.docker.com/toolbox)
|
229
|
+
* [Install Docker Compose for Linux](https://docs.docker.com/compose/install/)
|
110
230
|
|
111
|
-
|
231
|
+
Once you have docker and docker-compose running locally, run `docker-compose build lint`. This will build
|
232
|
+
the `reactonrails_lint` docker image and docker-compose `lint` container. The inital build is slow,
|
233
|
+
but after the install, startup is very quick.
|
112
234
|
|
113
|
-
|
235
|
+
### Linting Commands
|
236
|
+
Run `rake -D docker` to see all docker linting commands for rake. `rake docker` will run all linters.
|
237
|
+
For individual rake linting commands please refer to `rake -D docker` for the list.
|
238
|
+
You can run specfic linting for directories or files by using `docker-compose run lint rubocop (file path or directory)`, etc.
|
239
|
+
`docker-compose run lint /bin/bash` sets you up to run from the container command line.
|
114
240
|
|
115
241
|
## Contributing
|
116
242
|
|
117
243
|
Bug reports and pull requests are welcome on GitHub at https://github.com/shakacode/react_on_rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
118
244
|
|
119
|
-
|
120
245
|
## License
|
121
246
|
|
122
247
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
123
248
|
|
249
|
+
## Updating New Versions of the Gem
|
250
|
+
|
251
|
+
See https://github.com/svenfuchs/gem-release
|
252
|
+
|
253
|
+
# Authors
|
254
|
+
The Shaka Code team!
|
255
|
+
|
256
|
+
1. [Justin Gordon](https://github.com/justin808/)
|
257
|
+
2. [Samnang Chhun](https://github.com/samnang)
|
258
|
+
3. [Alex Fedoseev](https://github.com/alexfedoseev)
|
259
|
+
|
260
|
+
And based on the work of the [react-rails gem](https://github.com/reactjs/react-rails)
|
data/Rakefile
CHANGED
@@ -1,6 +1,91 @@
|
|
1
|
-
require "
|
2
|
-
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
namespace :run_rspec do
|
4
|
+
desc "Run RSpec for top level only"
|
5
|
+
task :gem do
|
6
|
+
sh %{ rspec --exclude-pattern "spec/dummy/**/*_spec.rb" spec }
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Run RSpec for spec/dummy only"
|
10
|
+
task :dummy do
|
11
|
+
sh %{ cd spec/dummy && rspec }
|
12
|
+
end
|
13
|
+
|
14
|
+
task run_rspec: [:gem, :dummy] do
|
15
|
+
puts "Completed all RSpec tests"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
desc "Runs all tests. Run `rake -D run_rspec` to see all available test options"
|
19
|
+
task run_rspec: ["run_rspec:run_rspec"]
|
20
|
+
|
21
|
+
task default: :run_rspec
|
22
|
+
|
23
|
+
namespace :lint do
|
24
|
+
|
25
|
+
desc "Run Rubocop as shell"
|
26
|
+
task :rubocop do
|
27
|
+
sh "rubocop ."
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Run ruby-lint as shell"
|
31
|
+
task :ruby do
|
32
|
+
sh "ruby-lint app spec lib"
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Run scss-lint as shell"
|
36
|
+
task :scss do
|
37
|
+
sh "scss-lint ."
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Run eslint as shell"
|
41
|
+
task :eslint do
|
42
|
+
sh "eslint . --ext .jsx and .js"
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Run jscs from shell"
|
46
|
+
task :jscs do
|
47
|
+
sh "jscs ."
|
48
|
+
end
|
49
|
+
|
50
|
+
task lint: [:eslint, :rubocop, :ruby, :jscs, :scss] do
|
51
|
+
puts "Completed all linting"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
desc "Runs all linters. Run `rake -D lint` to see all available lint options"
|
55
|
+
task lint: ["lint:lint"]
|
56
|
+
|
57
|
+
namespace :docker do
|
58
|
+
desc "Run Rubocop linter from docker"
|
59
|
+
task :rubocop do
|
60
|
+
sh "docker-compose run lint rake lint:rubocop"
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "Run ruby-lint linter from docker"
|
64
|
+
task :ruby do
|
65
|
+
sh "docker-compose run lint rake lint:ruby"
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Run scss-lint linter from docker"
|
69
|
+
task :scss do
|
70
|
+
sh"docker-compose run lint rake lint:scss"
|
71
|
+
end
|
72
|
+
|
73
|
+
desc "Run eslint linter from docker"
|
74
|
+
task :eslint do
|
75
|
+
sh "docker-compose run lint rake lint:eslint"
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "Run jscs linter from docker"
|
79
|
+
task :jscs do
|
80
|
+
sh "docker-compose run lint rake lint:jscs"
|
81
|
+
end
|
82
|
+
desc "Run all linting from docker"
|
83
|
+
task :lint do
|
84
|
+
sh "docker-compose run lint rake lint"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "Runs all linters from docker. Run `rake -D docker` to see all available lint options"
|
89
|
+
task docker: ["docker:lint"]
|
3
90
|
|
4
|
-
RSpec::Core::RakeTask.new(:spec)
|
5
91
|
|
6
|
-
task :default => :spec
|
@@ -2,132 +2,198 @@ require 'react_on_rails/react_renderer'
|
|
2
2
|
|
3
3
|
module ReactOnRailsHelper
|
4
4
|
|
5
|
-
#
|
5
|
+
# react_component_name: can be a React component, created using a ES6 class, or
|
6
|
+
# React.createClass, or a
|
7
|
+
# `generator function` that returns a React component
|
8
|
+
# using ES6
|
9
|
+
# let MyReactComponentApp = (props) => <MyReactComponent {...props}/>;
|
10
|
+
# or using ES5
|
11
|
+
# var MyReactComponentApp = function(props) { return <YourReactComponent {...props}/>; }
|
12
|
+
# Exposing the react_component_name is necessary to both a plain ReactComponent as well as
|
13
|
+
# a generator:
|
14
|
+
# For client rendering, expose the react_component_name on window:
|
15
|
+
# window.MyReactComponentApp = MyReactComponentApp;
|
16
|
+
# For server rendering, export the react_component_name on global:
|
17
|
+
# global.MyReactComponentApp = MyReactComponentApp;
|
18
|
+
# See spec/dummy/client/app/startup/serverGlobals.jsx and
|
19
|
+
# spec/dummy/client/app/startup/ClientApp.jsx for examples of this
|
6
20
|
# props: Ruby Hash which contains the properties to pass to the react object
|
7
21
|
#
|
8
|
-
# Naming Conventions:
|
9
|
-
# Suppose your component is named "App" (can be anything)
|
10
|
-
# 1. Inside app/startup/ServerApp.jsx, setup the component for server rendering. This is used by
|
11
|
-
# the global exports in step 2
|
12
|
-
# 2. Component_name is changed to CamelizedUpper for the exposed component,
|
13
|
-
# so we have App as the exposed global (see app/startup/serverGlobals.jsx)
|
14
|
-
# 3. Inside app/startup/ClientApp.jsx, you want to export a function that generates
|
15
|
-
# your component, and when you generate the component, you must use this naming convention
|
16
|
-
# to get the data.
|
17
|
-
# App(__appData__)
|
18
|
-
# That way it gets the data you pass from the Rails helper.
|
19
|
-
#
|
20
22
|
# options:
|
21
|
-
#
|
22
|
-
#
|
23
|
+
# generator_function: <true/false> default is false, set to true if you want to use a
|
24
|
+
# generator function rather than a React Component.
|
25
|
+
# prerender: <true/false> set to false when debugging!
|
26
|
+
# trace: <true/false> set to true to print additional debugging information in the browser
|
27
|
+
# default is true for development, off otherwise
|
28
|
+
# replay_console: <true/false> Default is true. False will disable echoing server rendering
|
29
|
+
# logs, which can make troubleshooting server rendering difficult.
|
23
30
|
def react_component(component_name, props = {}, options = {})
|
31
|
+
# Create the JavaScript and HTML to allow either client or server rendering of the
|
32
|
+
# react_component.
|
33
|
+
#
|
24
34
|
# Create the JavaScript setup of the global to initialize the client rendering
|
25
35
|
# (re-hydrate the data). This enables react rendered on the client to see that the
|
26
36
|
# server has already rendered the HTML.
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
37
|
+
# We use this react_component_index in case we have the same component multiple times on the page.
|
38
|
+
react_component_index = next_react_component_index
|
39
|
+
react_component_name = component_name.camelize # Not sure if we should be doing this (JG)
|
40
|
+
dom_id = "#{component_name}-react-component-#{react_component_index}"
|
41
|
+
|
42
|
+
# Setup the page_loaded_js, which is the same regardless of prerendering or not!
|
43
|
+
# The reason is that React is smart about not doing extra work if the server rendering did its job.
|
44
|
+
data_variable_name = "__#{component_name.camelize(:lower)}Data#{react_component_index}__"
|
36
45
|
turbolinks_loaded = Object.const_defined?(:Turbolinks)
|
37
|
-
install_render_events = turbolinks_loaded ? turbolinks_bootstrap(
|
38
|
-
|
46
|
+
install_render_events = turbolinks_loaded ? turbolinks_bootstrap(dom_id) : non_turbolinks_bootstrap
|
39
47
|
page_loaded_js = <<-JS
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
48
|
+
(function() {
|
49
|
+
window.#{data_variable_name} = #{props.to_json};
|
50
|
+
#{define_render_if_dom_node_present(react_component_name, data_variable_name, dom_id, trace(options), generator_function(options))}
|
51
|
+
#{install_render_events}
|
52
|
+
})();
|
45
53
|
JS
|
46
54
|
|
47
55
|
data_from_server_script_tag = javascript_tag(page_loaded_js)
|
48
56
|
|
49
57
|
# Create the HTML rendering part
|
50
|
-
|
58
|
+
server_rendered_html =
|
59
|
+
server_rendered_react_component_html(options, props, react_component_name)
|
60
|
+
|
61
|
+
rendered_output = content_tag(:div,
|
62
|
+
server_rendered_html,
|
63
|
+
id: dom_id)
|
64
|
+
|
65
|
+
# IMPORTANT: Ensure that we mark string as html_safe to avoid escaping.
|
66
|
+
<<-HTML.html_safe
|
67
|
+
#{data_from_server_script_tag}
|
68
|
+
#{rendered_output}
|
69
|
+
HTML
|
70
|
+
end
|
71
|
+
|
72
|
+
def next_react_component_index
|
73
|
+
@react_component_index ||= -1
|
74
|
+
@react_component_index += 1
|
75
|
+
end
|
76
|
+
|
77
|
+
def server_rendered_react_component_html(options, props, react_component_name)
|
78
|
+
if prerender(options)
|
51
79
|
render_js_expression = <<-JS
|
52
|
-
|
80
|
+
(function(React) {
|
81
|
+
var reactElement = #{render_js_react_element(react_component_name, props.to_json, generator_function(options))};
|
82
|
+
return React.renderToString(reactElement);
|
83
|
+
})(this.React);
|
53
84
|
JS
|
54
|
-
|
85
|
+
# create the server generated html of the react component with props
|
86
|
+
options[:react_component_name] = react_component_name
|
87
|
+
server_rendered_react_component_html =
|
88
|
+
render_js(render_js_expression, options)
|
55
89
|
else
|
56
90
|
server_rendered_react_component_html = ""
|
57
91
|
end
|
58
|
-
|
59
|
-
rendered_output = content_tag(:div,
|
60
|
-
server_rendered_react_component_html,
|
61
|
-
id: domId)
|
62
|
-
|
63
|
-
<<-HTML.strip_heredoc.html_safe
|
64
|
-
#{data_from_server_script_tag}
|
65
|
-
#{rendered_output}
|
66
|
-
HTML
|
92
|
+
server_rendered_react_component_html
|
67
93
|
end
|
68
94
|
|
69
95
|
# Takes javascript code and returns the output from it. This is called by react_component, which
|
70
96
|
# sets up the JS code for rendering a react component.
|
71
97
|
# This method could be used by itself to render the output of any javascript that returns a
|
72
98
|
# string of proper HTML.
|
73
|
-
def render_js(js_expression)
|
74
|
-
ReactOnRails::ReactRenderer.new.render_js(js_expression
|
99
|
+
def render_js(js_expression, options = {})
|
100
|
+
ReactOnRails::ReactRenderer.new(options).render_js(js_expression,
|
101
|
+
options).html_safe
|
75
102
|
end
|
76
103
|
|
77
104
|
private
|
78
105
|
|
79
|
-
def
|
106
|
+
def trace(options)
|
107
|
+
options.fetch(:trace) { ReactOnRails.configuration.trace }
|
108
|
+
end
|
109
|
+
|
110
|
+
def generator_function(options)
|
111
|
+
options.fetch(:generator_function) { ReactOnRails.configuration.generator_function }
|
112
|
+
end
|
113
|
+
|
114
|
+
def prerender(options)
|
115
|
+
options.fetch(:prerender) { ReactOnRails.configuration.prerender }
|
116
|
+
end
|
117
|
+
|
118
|
+
def debug_js(react_component_name, data_variable, dom_id, trace)
|
80
119
|
if trace
|
81
|
-
<<-JS
|
82
|
-
|
120
|
+
<<-JS
|
121
|
+
console.log("CLIENT SIDE RENDERED #{react_component_name} with data_variable #{data_variable} to dom node with id: #{dom_id}");
|
83
122
|
JS
|
84
123
|
else
|
85
124
|
""
|
86
125
|
end
|
87
126
|
end
|
88
127
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
128
|
+
# react_component_name: See app/helpers/react_on_rails_helper.rb:5
|
129
|
+
# props_string: is either the variable name used to hold the props (client side) or the
|
130
|
+
# stringified hash of props from the Ruby server side. In terms of the view helper, one is
|
131
|
+
# simply passing in the Ruby Hash of props.
|
132
|
+
#
|
133
|
+
# Returns the JavaScript code to generate a React element.
|
134
|
+
def render_js_react_element(react_component_name, props_string, generator_function)
|
135
|
+
# "this" is defined by the calling context which is "global" in the execJs
|
136
|
+
# environment or window in the client side context.
|
137
|
+
js_create_element = if generator_function
|
138
|
+
"#{react_component_name}(props)"
|
139
|
+
else
|
140
|
+
"React.createElement(#{react_component_name}, props)"
|
141
|
+
end
|
142
|
+
|
143
|
+
<<-JS
|
144
|
+
(function(React) {
|
145
|
+
var props = #{props_string};
|
146
|
+
return #{js_create_element};
|
147
|
+
})(this.React);
|
148
|
+
JS
|
149
|
+
end
|
150
|
+
|
151
|
+
def define_render_if_dom_node_present(react_component_name, data_variable, dom_id, trace, generator_function)
|
152
|
+
inner_js_code = <<-JS_CODE
|
153
|
+
var domNode = document.getElementById('#{dom_id}');
|
154
|
+
if (domNode) {
|
155
|
+
#{debug_js(react_component_name, data_variable, dom_id, trace)}
|
156
|
+
var reactElement = #{render_js_react_element(react_component_name, data_variable, generator_function)};
|
157
|
+
React.render(reactElement, domNode);
|
158
|
+
}
|
159
|
+
JS_CODE
|
160
|
+
|
161
|
+
<<-JS
|
162
|
+
var renderIfDomNodePresent = function() {
|
163
|
+
#{ReactOnRails::ReactRenderer.wrap_code_with_exception_handler(inner_js_code, react_component_name)}
|
164
|
+
}
|
99
165
|
JS
|
100
166
|
end
|
101
167
|
|
102
168
|
def non_turbolinks_bootstrap
|
103
|
-
<<-JS
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
169
|
+
<<-JS
|
170
|
+
document.addEventListener("DOMContentLoaded", function(event) {
|
171
|
+
console.log("DOMContentLoaded event fired");
|
172
|
+
renderIfDomNodePresent();
|
173
|
+
});
|
108
174
|
JS
|
109
175
|
end
|
110
176
|
|
111
177
|
def turbolinks_bootstrap(dom_id)
|
112
|
-
<<-JS
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
178
|
+
<<-JS
|
179
|
+
var turbolinksInstalled = typeof(Turbolinks) !== 'undefined';
|
180
|
+
if (!turbolinksInstalled) {
|
181
|
+
console.warn("WARNING: NO TurboLinks detected in JS, but it's in your Gemfile");
|
182
|
+
#{non_turbolinks_bootstrap}
|
183
|
+
} else {
|
184
|
+
function onPageChange(event) {
|
185
|
+
var removePageChangeListener = function() {
|
186
|
+
document.removeEventListener("page:change", onPageChange);
|
187
|
+
document.removeEventListener("page:before-unload", removePageChangeListener);
|
188
|
+
var domNode = document.getElementById('#{dom_id}');
|
189
|
+
React.unmountComponentAtNode(domNode);
|
190
|
+
};
|
191
|
+
document.addEventListener("page:before-unload", removePageChangeListener);
|
192
|
+
|
193
|
+
renderIfDomNodePresent();
|
194
|
+
}
|
195
|
+
document.addEventListener("page:change", onPageChange);
|
196
|
+
}
|
131
197
|
JS
|
132
198
|
end
|
133
199
|
end
|
data/docker-compose.yml
ADDED
@@ -5,17 +5,24 @@ module ReactOnRails
|
|
5
5
|
|
6
6
|
def self.configuration
|
7
7
|
@configuration ||= Configuration.new(
|
8
|
-
|
9
|
-
prerender: false
|
8
|
+
server_bundle_js_file: "app/assets/javascripts/generated/server.js",
|
9
|
+
prerender: false,
|
10
|
+
replay_console: true,
|
11
|
+
generator_function: false,
|
12
|
+
trace: Rails.env.development?
|
10
13
|
)
|
11
14
|
end
|
12
15
|
|
13
16
|
class Configuration
|
14
|
-
attr_accessor :
|
17
|
+
attr_accessor :server_bundle_js_file, :prerender, :replay_console, :generator_function, :trace
|
15
18
|
|
16
|
-
def initialize(
|
17
|
-
|
19
|
+
def initialize(server_bundle_js_file: nil, prerender: nil, replay_console: nil,
|
20
|
+
generator_function: nil, trace: nil)
|
21
|
+
self.server_bundle_js_file = server_bundle_js_file
|
18
22
|
self.prerender = prerender
|
23
|
+
self.replay_console = replay_console
|
24
|
+
self.generator_function = generator_function
|
25
|
+
self.trace = Rails.env.development?
|
19
26
|
end
|
20
27
|
end
|
21
28
|
end
|
@@ -1,41 +1,134 @@
|
|
1
|
+
# Kudos to react-rails for how to do the polyfill of the console!
|
2
|
+
# https://github.com/reactjs/react-rails/blob/master/lib/react/server_rendering/sprockets_renderer.rb
|
3
|
+
|
1
4
|
module ReactOnRails
|
2
5
|
class ReactRenderer
|
6
|
+
# Reimplement console methods for replaying on the client
|
7
|
+
CONSOLE_POLYFILL = <<-JS
|
8
|
+
var console = { history: [] };
|
9
|
+
['error', 'log', 'info', 'warn'].forEach(function (level) {
|
10
|
+
console[level] = function () {
|
11
|
+
console.history.push({level: level, arguments: Array.prototype.slice.call(arguments)});
|
12
|
+
};
|
13
|
+
});
|
14
|
+
JS
|
3
15
|
|
4
|
-
#
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
16
|
+
# Script to write to the browser console.
|
17
|
+
# NOTE: result comes from enclosing closure and is the server generated HTML
|
18
|
+
# that we intend to write to the browser. Thus, the script tag will get executed right after
|
19
|
+
# the HTML is rendered.
|
20
|
+
CONSOLE_REPLAY = <<-JS
|
21
|
+
var history = console.history;
|
22
|
+
if (history && history.length > 0) {
|
23
|
+
result += '\\n<script>';
|
24
|
+
history.forEach(function (msg) {
|
25
|
+
result += '\\nconsole.' + msg.level + '.apply(console, ' + JSON.stringify(msg.arguments) + ');';
|
26
|
+
});
|
27
|
+
result += '\\n</script>';
|
28
|
+
}
|
29
|
+
JS
|
30
|
+
|
31
|
+
DEBUGGER = <<-JS
|
32
|
+
if (typeof window !== 'undefined') { debugger; }
|
33
|
+
JS
|
34
|
+
|
35
|
+
def base_js_code
|
36
|
+
<<-JS
|
37
|
+
#{CONSOLE_POLYFILL}
|
38
|
+
#{bundle_js_code};
|
13
39
|
JS
|
14
40
|
end
|
15
41
|
|
16
|
-
def initialize
|
17
|
-
|
18
|
-
@
|
42
|
+
def initialize(options)
|
43
|
+
@context = ExecJS.compile(base_js_code)
|
44
|
+
@replay_console = options.fetch(:replay_console) { ReactOnRails.configuration.replay_console }
|
19
45
|
end
|
20
46
|
|
21
|
-
#
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
47
|
+
# js_code: JavaScript expression that returns a string.
|
48
|
+
# Returns a string of HTML for direct insertion on the page by evaluating js_code.
|
49
|
+
# Note, js_code does not have to be based on React.
|
50
|
+
# Calling code will probably call 'html_safe' on return value before rendering to the view.
|
51
|
+
def render_js(js_code, options = {})
|
52
|
+
component_name = options.fetch(:react_component_name, "")
|
53
|
+
|
54
|
+
result_js_code = "result = #{js_code}"
|
55
|
+
|
56
|
+
js_code_wrapper = <<-JS
|
57
|
+
(function () {
|
58
|
+
var result = '';
|
59
|
+
#{ReactOnRails::ReactRenderer.wrap_code_with_exception_handler(result_js_code, component_name)}
|
60
|
+
#{after_render};
|
61
|
+
return result;
|
62
|
+
})()
|
28
63
|
JS
|
29
64
|
|
30
|
-
|
31
|
-
|
65
|
+
trace_rails_on_maui = ENV["TRACE_REACT_ON_RAILS"].present? # Set to anything to print generated code.
|
66
|
+
if trace_rails_on_maui
|
67
|
+
puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
68
|
+
puts "react_renderer.rb: 92"
|
69
|
+
puts "js_code_wrapper = #{js_code_wrapper.ai}"
|
70
|
+
puts "wrote file tmp/server-generated.js"
|
71
|
+
File.write('tmp/server-generated.js', js_code_wrapper)
|
72
|
+
puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
73
|
+
end
|
74
|
+
|
32
75
|
@context.eval(js_code_wrapper)
|
33
76
|
end
|
34
77
|
|
78
|
+
def self.wrap_code_with_exception_handler(js_code, component_name)
|
79
|
+
<<-JS
|
80
|
+
try {
|
81
|
+
#{js_code}
|
82
|
+
}
|
83
|
+
catch(e) {
|
84
|
+
var lineOne =
|
85
|
+
'ERROR: You specifed the option generator_function (could be in your defaults) to be\\n';
|
86
|
+
var lastLine =
|
87
|
+
'A generator function takes a single arg of props and returns a ReactElement.';
|
88
|
+
|
89
|
+
var msg = '';
|
90
|
+
var shouldBeGeneratorError = lineOne +
|
91
|
+
'false, but the react component \\'#{component_name}\\' seems to be a generator function.\\n' +
|
92
|
+
lastLine;
|
93
|
+
var reMatchShouldBeGeneratorError = /Can't add property context, object is not extensible/;
|
94
|
+
if (reMatchShouldBeGeneratorError.test(e.message)) {
|
95
|
+
msg += shouldBeGeneratorError + '\\n\\n';
|
96
|
+
console.error(shouldBeGeneratorError);
|
97
|
+
}
|
98
|
+
|
99
|
+
var shouldBeGeneratorError = lineOne +
|
100
|
+
'true, but the react component \\'#{component_name}\\' is not a generator function.\\n' +
|
101
|
+
lastLine;
|
102
|
+
var reMatchShouldNotBeGeneratorError = /Cannot call a class as a function/;
|
103
|
+
if (reMatchShouldNotBeGeneratorError.test(e.message)) {
|
104
|
+
msg += shouldBeGeneratorError + '\\n\\n';
|
105
|
+
console.error(shouldBeGeneratorError);
|
106
|
+
}
|
107
|
+
|
108
|
+
console.error('SERVER SIDE: Exception in server side rendering!');
|
109
|
+
if (e.fileName) {
|
110
|
+
console.error('SERVER SIDE: location: ' + e.fileName + ':' + e.lineNumber);
|
111
|
+
}
|
112
|
+
console.error('SERVER SIDE: message: ' + e.message);
|
113
|
+
console.error('SERVER SIDE: stack: ' + e.stack);
|
114
|
+
msg += 'SERVER SIDE Exception in rendering!\\n' +
|
115
|
+
(e.fileName ? '\\nlocation: ' + e.fileName + ':' + e.lineNumber : '') +
|
116
|
+
'\\nMessage: ' + e.message + '\\n\\n' + e.stack;
|
117
|
+
|
118
|
+
var reactElement = React.createElement('pre', null, msg);
|
119
|
+
result = React.renderToString(reactElement);
|
120
|
+
}
|
121
|
+
JS
|
122
|
+
end
|
123
|
+
|
35
124
|
private
|
36
125
|
|
126
|
+
def after_render
|
127
|
+
@replay_console ? CONSOLE_REPLAY : ""
|
128
|
+
end
|
129
|
+
|
37
130
|
def bundle_js_code
|
38
|
-
js_file = Rails.root.join(ReactOnRails.configuration.
|
131
|
+
js_file = Rails.root.join(ReactOnRails.configuration.server_bundle_js_file)
|
39
132
|
File.read(js_file)
|
40
133
|
end
|
41
134
|
end
|
data/react_on_rails.gemspec
CHANGED
data/ruby-lint.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Not currently using ruby-lint
|
2
|
+
---
|
3
|
+
presenter: syntastic
|
4
|
+
|
5
|
+
directories:
|
6
|
+
- app
|
7
|
+
- lib
|
8
|
+
- spec
|
9
|
+
|
10
|
+
ignore_paths:
|
11
|
+
- client
|
12
|
+
- node_modules
|
13
|
+
|
14
|
+
analysis_classes:
|
15
|
+
# - argument_amount
|
16
|
+
- pedantics
|
17
|
+
- shadowing_variables
|
18
|
+
# Too many false positives for these
|
19
|
+
# - undefined_methods
|
20
|
+
# - undefined_variables
|
21
|
+
# - unused_variables
|
22
|
+
- useless_equality_checks
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: react_on_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Gordon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-09-
|
11
|
+
date: 2015-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: coveralls
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
description: See README.md
|
84
98
|
email:
|
85
99
|
- justin@shakacode.com
|
@@ -87,20 +101,29 @@ executables: []
|
|
87
101
|
extensions: []
|
88
102
|
extra_rdoc_files: []
|
89
103
|
files:
|
104
|
+
- ".coveralls.yml"
|
105
|
+
- ".eslintignore"
|
106
|
+
- ".eslintrc"
|
90
107
|
- ".gitignore"
|
108
|
+
- ".jscsrc"
|
91
109
|
- ".rspec"
|
110
|
+
- ".rubocop.yml"
|
111
|
+
- ".scss-lint.yml"
|
92
112
|
- ".travis.yml"
|
93
113
|
- CODE_OF_CONDUCT.md
|
114
|
+
- Dockerfile
|
94
115
|
- Gemfile
|
95
116
|
- LICENSE.txt
|
96
117
|
- README.md
|
97
118
|
- Rakefile
|
98
119
|
- app/helpers/react_on_rails_helper.rb
|
120
|
+
- docker-compose.yml
|
99
121
|
- lib/react_on_rails.rb
|
100
122
|
- lib/react_on_rails/configuration.rb
|
101
123
|
- lib/react_on_rails/react_renderer.rb
|
102
124
|
- lib/react_on_rails/version.rb
|
103
125
|
- react_on_rails.gemspec
|
126
|
+
- ruby-lint.yml
|
104
127
|
homepage: https://github.com/shakacode/react_on_rails
|
105
128
|
licenses:
|
106
129
|
- MIT
|