isomorfeus-react 16.6.6 → 16.6.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +439 -0
- data/lib/isomorfeus-react.rb +36 -38
- data/lib/isomorfeus/config.rb +66 -5
- data/lib/isomorfeus/execution_environment.rb +31 -0
- data/lib/isomorfeus/react_view_helper.rb +26 -0
- data/lib/isomorfeus/top_level_browser.rb +51 -0
- data/lib/isomorfeus/top_level_ssr.rb +18 -0
- data/lib/lucid_app/api.rb +1 -1
- data/lib/lucid_app/native_component_constructor.rb +2 -2
- data/lib/lucid_component/native_component_constructor.rb +9 -6
- data/lib/lucid_router.rb +18 -0
- data/lib/react.rb +14 -14
- data/lib/react/component/features.rb +12 -12
- data/lib/react/component/native_component_constructor.rb +2 -2
- data/lib/react/component/resolution.rb +6 -3
- data/lib/react/context_wrapper.rb +1 -1
- data/lib/react/function_component/creator.rb +1 -1
- data/lib/react/function_component/resolution.rb +7 -5
- data/lib/react/native_constant_wrapper.rb +3 -1
- data/lib/react/redux_component/app_store_proxy.rb +0 -3
- data/lib/react/redux_component/class_store_proxy.rb +0 -4
- data/lib/react/redux_component/native_component_constructor.rb +7 -4
- data/lib/react/version.rb +1 -1
- data/lib/react_dom.rb +38 -28
- data/lib/react_dom_server.rb +13 -11
- metadata +40 -9
- data/lib/isomorfeus/top_level.rb +0 -48
- data/lib/isomorfeus/view_helpers.rb +0 -22
- data/readme.md +0 -661
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7734c66d2190367c11ef62d2a95658b144eea4fc35d9840e23d0b684297857bf
|
4
|
+
data.tar.gz: c7adfe966a2e6b1b5921981d72266673877dd559fd7d95ab4a49870aece9552f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97c9f1935d81da9b7debd7fce453d32e5b1810230d40ffcd0f9af98050e69e3c48a5fdc864e60c3c2e9098de1ee6c647aa3957ed9f0c35af8bfc736ddae40dd7
|
7
|
+
data.tar.gz: ab9c75bf388786ef5e4d5ac046bb5b4a8bf94aba2fd99f12426529b8ff0e14b6dcd44936c4f2b8750ba032b884906a7070fb0fd996fe50c49d257b83c46612be
|
data/README.md
ADDED
@@ -0,0 +1,439 @@
|
|
1
|
+
# isomorfeus-react
|
2
|
+
|
3
|
+
Develop React components for Opal Ruby along with very easy to use and advanced React-Redux Components.
|
4
|
+
|
5
|
+
### Community and Support
|
6
|
+
At the [Isomorfeus Framework Project](http://isomorfeus.com)
|
7
|
+
|
8
|
+
## Versioning
|
9
|
+
isomorfeus-react version follows the React version which features and API it implements.
|
10
|
+
Isomorfeus-react 16.5.x implements features and the API of React 16.6 and should be used with React 16.6
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
To install React with the matching version:
|
14
|
+
```
|
15
|
+
yarn add react@16.6
|
16
|
+
```
|
17
|
+
then add to the Gemfile:
|
18
|
+
```ruby
|
19
|
+
gem 'isomorfeus-react' # this will also include isomorfeus-redux
|
20
|
+
```
|
21
|
+
then `bundle install`
|
22
|
+
and to your client code add:
|
23
|
+
```ruby
|
24
|
+
require 'isomorfeus-react' # this will also require isomorfeus-redux
|
25
|
+
```
|
26
|
+
|
27
|
+
### Dependencies
|
28
|
+
|
29
|
+
For full functionality the following are required:
|
30
|
+
Ruby Gems:
|
31
|
+
- [Opal with ES6 modules](https://github.com/opal/opal/pull/1976)
|
32
|
+
- [Opal Webpack Loader](https://github.com/isomorfeus/opal-webpack-loader)
|
33
|
+
- [Opal Autoloader](https://github.com/janbiedermann/opal-autoloader)
|
34
|
+
- [Isomorfeus-Speednode](https://github.com/isomorfeus/isomorfeus-speednode)
|
35
|
+
- [Isomorfeus-Redux](https://github.com/isomorfeus/isomorfeus-redux)
|
36
|
+
|
37
|
+
For the Gemfile:
|
38
|
+
```ruby
|
39
|
+
gem 'opal', github: 'janbiedermann/opal', branch: 'es6_modules_1_1'
|
40
|
+
gem 'opal-webpack-loader', '~> 0.8.4'
|
41
|
+
gem 'opal-autoloader', '~> 0.0.3'
|
42
|
+
gem 'isomorfeus-redux', '~> 4.0.4'
|
43
|
+
gem 'isomorfeus-speednode', '~> 0.2.3'
|
44
|
+
```
|
45
|
+
Javascript Npms:
|
46
|
+
- opal-webpack-laoder
|
47
|
+
- react
|
48
|
+
- react-router
|
49
|
+
- redux
|
50
|
+
|
51
|
+
for package.json:
|
52
|
+
```json
|
53
|
+
"opal-webpack-loader": "^0.8.4",
|
54
|
+
"react": "16.8",
|
55
|
+
"react-dom": "16.8",
|
56
|
+
"react-router": "5.0.0",
|
57
|
+
"react-router-dom": "5.0.0",
|
58
|
+
"redux": "^4.0.1"
|
59
|
+
```
|
60
|
+
## Usage
|
61
|
+
Because isomorfeus-react follows closely the React principles/implementation/API and Documentation, most things of the official React documentation
|
62
|
+
apply, but in the Ruby way, see:
|
63
|
+
- https://reactjs.org/docs/getting-started.html
|
64
|
+
|
65
|
+
React, Redux and accompanying libraries must be imported and made available in the global namespace in the application javascript entry file,
|
66
|
+
with webpack this can be ensured by assigning them to the global namespace:
|
67
|
+
```javascript
|
68
|
+
import * as Redux from 'redux';
|
69
|
+
import React from 'react';
|
70
|
+
import ReactDOM from 'react-dom';
|
71
|
+
global.Redux = Redux;
|
72
|
+
global.React = React;
|
73
|
+
global.ReactDOM = ReactDOM;
|
74
|
+
|
75
|
+
// for routing support
|
76
|
+
import { BrowserRouter, Link, NavLink, Route, Switch } from 'react-router-dom';
|
77
|
+
global.BrowserRouter = BrowserRouter;
|
78
|
+
global.Link = Link;
|
79
|
+
global.NavLink = NavLink;
|
80
|
+
global.Route = Route;
|
81
|
+
global.Switch = Switch;
|
82
|
+
```
|
83
|
+
|
84
|
+
Following features are presented with its differences to the Javascript React implementation, along with enhancements and the advanced components.
|
85
|
+
|
86
|
+
### Component Types
|
87
|
+
- [Class Component](https://github.com/isomorfeus/isomorfeus-react/blob/master/ruby/docs/class_component.md)
|
88
|
+
- [Pure Component](https://github.com/isomorfeus/isomorfeus-react/blob/master/ruby/docs/pure_component.md)
|
89
|
+
- [Function Component](https://github.com/isomorfeus/isomorfeus-react/blob/master/ruby/docs/function_component.md)
|
90
|
+
- [Redux Component](https://github.com/isomorfeus/isomorfeus-react/blob/master/ruby/docs/redux_component.md)
|
91
|
+
- [Lucid App, Lucid Router and Lucid Component](https://github.com/isomorfeus/isomorfeus-react/blob/master/ruby/docs/lucid_component.md)
|
92
|
+
- [React Javascript Component](https://github.com/isomorfeus/isomorfeus-react/blob/master/ruby/docs/javascript_component.md)
|
93
|
+
|
94
|
+
Which component to use?
|
95
|
+
- Usually LucidApp, LucidRouter and LucidComponent along with some javascript components.
|
96
|
+
- For improved performance small Function Components may help at critical spots.
|
97
|
+
|
98
|
+
### Props
|
99
|
+
[Props](https://github.com/isomorfeus/isomorfeus-react/blob/master/ruby/docs/props.md)
|
100
|
+
|
101
|
+
### State
|
102
|
+
[State](https://github.com/isomorfeus/isomorfeus-react/blob/master/ruby/docs/state.md)
|
103
|
+
|
104
|
+
### Lifecycle Callbacks
|
105
|
+
All lifecycle callbacks that are available in the matching React version are available as DSL. Callback names are underscored.
|
106
|
+
Callback names prefixed with UNSAFE_ in React are prefixed with unsafe_ in ruby.
|
107
|
+
Example:
|
108
|
+
```ruby
|
109
|
+
class MyComponent < React::Component::Base
|
110
|
+
render do
|
111
|
+
SPAN { 'some more text' }
|
112
|
+
end
|
113
|
+
|
114
|
+
component_did_mount do
|
115
|
+
`console.log("MyComponent mounted!")`
|
116
|
+
end
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
### Events
|
121
|
+
Event names are underscored in ruby: `onClick` becomes `on_click`. The conversion for React is done automatically.
|
122
|
+
|
123
|
+
Event handlers must be declared using the `event_handler` DSL. This is to make sure, that they are not recreated during render and can be properly
|
124
|
+
compared by reference by shouldComponentUpdate(). Use the DSL like so:
|
125
|
+
```ruby
|
126
|
+
class MyComponent < React::Component::Base
|
127
|
+
event_handler :handle_click do |event|
|
128
|
+
state.toggler = !state.toggler
|
129
|
+
end
|
130
|
+
|
131
|
+
render do
|
132
|
+
SPAN(on_click: :handle_click) { 'some more text' }
|
133
|
+
SPAN(on_click: :handle_click) { 'a lot more text' } # event handlers can be reused
|
134
|
+
end
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
To the event handler the event is passed as argument. The event is a ruby object `React::SyntheticEvent` and supports all the methods, properties
|
139
|
+
and events as the React.Synthetic event. Methods are underscored. Example:
|
140
|
+
```ruby
|
141
|
+
class MyComponent < React::Component::Base
|
142
|
+
event_handler :handle_click do |event|
|
143
|
+
event.prevent_default
|
144
|
+
event.current_target
|
145
|
+
end
|
146
|
+
|
147
|
+
render do
|
148
|
+
SPAN(on_click: :handle_click) { 'some more text' }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
```
|
152
|
+
Targets of the event, like current_target, are wrapped Elements as supplied by opal-browser.
|
153
|
+
|
154
|
+
#### Events and Function Components
|
155
|
+
The event_handler DSL can be used within the React::FunctionComponent::Creator. However, function component dont react by themselves to events,
|
156
|
+
the event handler must be applied to a element.
|
157
|
+
```ruby
|
158
|
+
class React::FunctionComponent::Creator
|
159
|
+
event_handler :show_red_alert do |event|
|
160
|
+
`alert("RED ALERT!")`
|
161
|
+
end
|
162
|
+
|
163
|
+
event_handler :show_orange_alert do |event|
|
164
|
+
`alert("ORANGE ALERT!")`
|
165
|
+
end
|
166
|
+
|
167
|
+
function_component 'AFunComponent' do
|
168
|
+
SPAN(on_click: props.on_click) { 'Click for orange alert! ' } # event handler passed in props, applied to a element
|
169
|
+
SPAN(on_click: :show_red_alert) { 'Click for red alert! ' } # event handler directly applied to a element
|
170
|
+
end
|
171
|
+
|
172
|
+
function_component 'AnotherFunComponent' do
|
173
|
+
AFunComponent(on_click: :show_orange_alert, text: 'Fun') # event handler passed as prop, but must be applied to element, see above
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
### Render blocks
|
178
|
+
render or element or component blocks work like ruby blocks, the result of the last expression in a block is returned and then rendered,
|
179
|
+
but only if it is a string or a React Element.
|
180
|
+
HTML Elements and Components at any place in the blocks are rendered too.
|
181
|
+
Examples:
|
182
|
+
```ruby
|
183
|
+
class MyComponent < React::Component::Base
|
184
|
+
render do
|
185
|
+
SPAN { "string" } # this string is rendered in a SPAN HTML Element
|
186
|
+
SPAN { "another string" } # this string is rendered in a SPAN too
|
187
|
+
end
|
188
|
+
end
|
189
|
+
```
|
190
|
+
```ruby
|
191
|
+
class MyComponent < React::Component::Base
|
192
|
+
render do
|
193
|
+
"string" # this string is NOT rendered, its not returned from the block and its not wrapped in a Element,
|
194
|
+
# to render it, wrap it in a element or fragment
|
195
|
+
"another string" # this string is returned from the block, so its rendered
|
196
|
+
end
|
197
|
+
end
|
198
|
+
```
|
199
|
+
```ruby
|
200
|
+
class MyComponent < React::Component::Base
|
201
|
+
render do
|
202
|
+
Fragment { "string" } # this string is rendered without surrounding element
|
203
|
+
100 # this is not a string, so its NOT rendered, to render it, simply convert it to a string: "#{100}" or 100.to_s
|
204
|
+
end
|
205
|
+
end
|
206
|
+
```
|
207
|
+
There is a shorthand "string param syntax". Its advantages are:
|
208
|
+
- reduced asset size, because it reduces the amount of compiled blocks, strings are passed as param instead
|
209
|
+
- improved performance, because it reduces the amount of executed blocks, strings are passed as param instead
|
210
|
+
|
211
|
+
Its disadvantages are:
|
212
|
+
- it looks a bit odd when other params are passed
|
213
|
+
|
214
|
+
The first render block example in "string param syntax":
|
215
|
+
```ruby
|
216
|
+
class MyComponent < React::Component::Base
|
217
|
+
render do
|
218
|
+
SPAN "string" # this string is rendered in a SPAN HTML Element, short and handy
|
219
|
+
SPAN "another string" # this string is rendered in a SPAN too, short and handy
|
220
|
+
|
221
|
+
# "string param syntax" with additional params:
|
222
|
+
SPAN({class: 'design'}, "yet another string") # <- not recommended
|
223
|
+
# for comparison the "string block syntax" with additonal params
|
224
|
+
SPAN(class: 'design') { 'even a string' } # <- recommended, looks better
|
225
|
+
end
|
226
|
+
end
|
227
|
+
```
|
228
|
+
### Rendering HTML or SVG Elements
|
229
|
+
Elements are rendered using a DSL which provides all Elements supported by React following these specs:
|
230
|
+
- https://www.w3.org/TR/html52/fullindex.html#index-elements
|
231
|
+
- https://www.w3.org/TR/SVG11/eltindex.html
|
232
|
+
|
233
|
+
The DSL can be used like so:
|
234
|
+
```ruby
|
235
|
+
class MyComponent < React::Component::Base
|
236
|
+
render do
|
237
|
+
SPAN { 'some more text' } # upper case
|
238
|
+
span { 'so much text' } # lower case
|
239
|
+
end
|
240
|
+
end
|
241
|
+
```
|
242
|
+
Use whichever you prefer. There are some clashes with opal ruby kernel methods, like `p 'text'`, that may have to be considered.
|
243
|
+
|
244
|
+
### Accessibility
|
245
|
+
Props like `aria-label` must be written underscored `aria_label`. They are automatically converted for React. Example:
|
246
|
+
```ruby
|
247
|
+
class MyComponent < React::Component::Base
|
248
|
+
render do
|
249
|
+
SPAN(aria_label: 'label text') { 'some more text' }
|
250
|
+
end
|
251
|
+
end
|
252
|
+
```
|
253
|
+
|
254
|
+
### Fragments
|
255
|
+
Fragments can be created like so:
|
256
|
+
```ruby
|
257
|
+
class MyComponent < React::Component::Base
|
258
|
+
render do
|
259
|
+
Fragment do
|
260
|
+
SPAN { 'useful text' }
|
261
|
+
SPAN { 'extremely useful text' }
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
```
|
266
|
+
|
267
|
+
### Portals
|
268
|
+
Portals can be created like so:
|
269
|
+
```ruby
|
270
|
+
class MyComponent < React::Component::Base
|
271
|
+
render do
|
272
|
+
Portal(`document.querySelector('div')`) do
|
273
|
+
SPAN { 'useful text' }
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
```
|
278
|
+
Portals currently require a native DOM node as argument. (This may change to something conveniently provided by opal-browser.)
|
279
|
+
|
280
|
+
### StrictMode
|
281
|
+
React.StrictMode can be used like so:
|
282
|
+
```ruby
|
283
|
+
class MyComponent < React::Component::Base
|
284
|
+
render do
|
285
|
+
StrictMode do
|
286
|
+
SPAN { 'useful text' }
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
```
|
291
|
+
|
292
|
+
### Ref
|
293
|
+
Refs must be declared using the `ref` DSL. This is to make sure, that they are not recreated during render and can be properly
|
294
|
+
compared by reference by shouldComponentUpdate(). Use the DSL like so:
|
295
|
+
```ruby
|
296
|
+
class MyComponent < React::Component::Base
|
297
|
+
ref :my_ref # a simple ref
|
298
|
+
ref :my_other_ref do |ref| # a ref with block
|
299
|
+
ref.current
|
300
|
+
end
|
301
|
+
|
302
|
+
render do
|
303
|
+
SPAN(ref: :my_ref) { 'useful text' } # refs can then be passed as prop
|
304
|
+
end
|
305
|
+
end
|
306
|
+
```
|
307
|
+
If the ref declaration supplies a block, the block receives a `React::Ref` ruby instance as argument. `ref.current`may then be the ruby component or
|
308
|
+
native DOM node. ()The latter may change to something conveniently provided by opal-browser.)
|
309
|
+
|
310
|
+
### Context
|
311
|
+
A context can be created using `React.create_context(constant_name, default_value)`. Constant_name must be a string like `"MyContext"`.
|
312
|
+
The context withs its Provider and Consumer can then be used like a component:
|
313
|
+
```ruby
|
314
|
+
React.create_context("MyContext", 'div')
|
315
|
+
|
316
|
+
class MyComponent < React::Component::Base
|
317
|
+
render do
|
318
|
+
MyContext.Provider(value="span") do
|
319
|
+
MyOtherComponent()
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
```
|
324
|
+
or the consumer:
|
325
|
+
```ruby
|
326
|
+
class MyOtherComponent < React::Component::Base
|
327
|
+
render do
|
328
|
+
MyContext.Consumer do |value|
|
329
|
+
Sem.Button(as: value) { 'useful text' }
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
```
|
334
|
+
|
335
|
+
### Using React Router
|
336
|
+
First the Components of React Router must be imported and made available in the global context:
|
337
|
+
```javascript
|
338
|
+
import * as ReactRouter from 'react-router';
|
339
|
+
import * as ReactRouterDOM from 'react-router-dom';
|
340
|
+
import { BrowserRouter, Link, NavLink, Route, Switch } from 'react-router-dom';
|
341
|
+
|
342
|
+
global.ReactRouter = ReactRouter;
|
343
|
+
global.ReactRouterDOM = ReactRouterDOM;
|
344
|
+
global.BrowserRouter = BrowserRouter;
|
345
|
+
global.Link = Link;
|
346
|
+
global.NavLink = NavLink;
|
347
|
+
global.Route = Route;
|
348
|
+
global.Switch = Switch;
|
349
|
+
```
|
350
|
+
Only import whats needed, or import HashRouter instead of BrowserRouter.
|
351
|
+
Then the Router components can be used as an other component:
|
352
|
+
```ruby
|
353
|
+
class RouterComponent < React::Component::Base
|
354
|
+
render do
|
355
|
+
DIV do
|
356
|
+
BrowserRouter do
|
357
|
+
Switch do
|
358
|
+
Route(path: '/my_path/:id', exact: true, component: MyOtherComponent.JS[:react_component])
|
359
|
+
Route(path: '/', strict: true, component: MyCompnent.JS[:react_component])
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
```
|
366
|
+
The Javascript React components of the ruby class must be passed as shown above. The child components then get the Router props
|
367
|
+
(match, history, location) passed in their props. They can be accessed like this:
|
368
|
+
```ruby
|
369
|
+
class MyOtherComponent < React::Component::Base
|
370
|
+
|
371
|
+
render do
|
372
|
+
Sem.Container(text_align: 'left', text: true) do
|
373
|
+
DIV do
|
374
|
+
SPAN { 'match :id is: ' }
|
375
|
+
SPAN { props.match.id }
|
376
|
+
end
|
377
|
+
DIV do
|
378
|
+
SPAN { 'location pathname is: ' }
|
379
|
+
SPAN { props.location.pathname }
|
380
|
+
end
|
381
|
+
DIV do
|
382
|
+
SPAN { 'number of history entries: ' }
|
383
|
+
SPAN { props.history.length }
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
```
|
389
|
+
Otherwise the React Router documentation applies: https://reacttraining.com/react-router/
|
390
|
+
|
391
|
+
### Code Splitting with Suspense (doc is wip)
|
392
|
+
|
393
|
+
React.lazy is availalable and so is the Suspense Component, in a render block:
|
394
|
+
```ruby
|
395
|
+
render do
|
396
|
+
Suspense do
|
397
|
+
MyComponent()
|
398
|
+
end
|
399
|
+
end
|
400
|
+
```
|
401
|
+
### Development Tools
|
402
|
+
The React Developer Tools allow for analyzing, debugging and profiling components. A very helpful toolset and working very nice with isomorfeus-react:
|
403
|
+
https://github.com/facebook/react-devtools
|
404
|
+
|
405
|
+
### Execution Environment
|
406
|
+
Code can run in 3 different environments:
|
407
|
+
- On the Browser, for normal execution
|
408
|
+
- On nodejs, for server side rendering
|
409
|
+
- On the server, for normal execution of server side code
|
410
|
+
|
411
|
+
The following helpers are available to determine the execution environment:
|
412
|
+
- `Isomorfeus.on_browser?` - true if running on the browser, otherwise false
|
413
|
+
- `Isomorfeus.on_ssr?` - true if running on node for server side rendering, otherwise false
|
414
|
+
- `Isomorfeus.on_server?` - true if running on the server, otherwise false
|
415
|
+
|
416
|
+
### Server Side Rendering
|
417
|
+
SSR is turned on by default in production and turned of in development. SSR is done in node using isomorfeus-speednode.
|
418
|
+
Components that depend on a browser can be shielded from rendering in node by using the above execution environment helper methods.
|
419
|
+
Example:
|
420
|
+
```ruby
|
421
|
+
class MyOtherComponent < React::Component::Base
|
422
|
+
|
423
|
+
render do
|
424
|
+
if Isomorfeus.on_browser?
|
425
|
+
SomeComponentDependingOnWindow()
|
426
|
+
else
|
427
|
+
DIV()
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
```
|
432
|
+
|
433
|
+
### Hot Module Reloading
|
434
|
+
|
435
|
+
HMR is supported when using LucidApp as top level component.
|
436
|
+
A render after a code update can be triggered using:
|
437
|
+
```ruby
|
438
|
+
Isomorfeus.force_render
|
439
|
+
```
|