isomorfeus-react 16.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +1 -0
- data/README.md +620 -0
- data/isomorfeus-react.gemspec +23 -0
- data/lib/isomorfeus-react.rb +131 -0
- data/lib/isomorfeus/config.rb +84 -0
- data/lib/isomorfeus/top_level.rb +48 -0
- data/lib/isomorfeus/view_helpers.rb +38 -0
- data/lib/lucid_app/api.rb +22 -0
- data/lib/lucid_app/base.rb +7 -0
- data/lib/lucid_app/context.rb +7 -0
- data/lib/lucid_app/mixin.rb +17 -0
- data/lib/lucid_app/native_component_constructor.rb +70 -0
- data/lib/lucid_component/api.rb +97 -0
- data/lib/lucid_component/base.rb +7 -0
- data/lib/lucid_component/event_handler.rb +17 -0
- data/lib/lucid_component/initializer.rb +12 -0
- data/lib/lucid_component/mixin.rb +17 -0
- data/lib/lucid_component/native_component_constructor.rb +131 -0
- data/lib/react.rb +147 -0
- data/lib/react/active_support_support.rb +13 -0
- data/lib/react/component/api.rb +226 -0
- data/lib/react/component/base.rb +9 -0
- data/lib/react/component/elements.rb +78 -0
- data/lib/react/component/event_handler.rb +19 -0
- data/lib/react/component/features.rb +47 -0
- data/lib/react/component/history.rb +36 -0
- data/lib/react/component/initializer.rb +11 -0
- data/lib/react/component/location.rb +15 -0
- data/lib/react/component/match.rb +31 -0
- data/lib/react/component/mixin.rb +19 -0
- data/lib/react/component/native_component_constructor.rb +76 -0
- data/lib/react/component/native_component_validate_prop.rb +37 -0
- data/lib/react/component/props.rb +49 -0
- data/lib/react/component/resolution.rb +71 -0
- data/lib/react/component/should_component_update.rb +14 -0
- data/lib/react/component/state.rb +52 -0
- data/lib/react/component/unsafe_api.rb +33 -0
- data/lib/react/context_wrapper.rb +47 -0
- data/lib/react/function_component/creator.rb +47 -0
- data/lib/react/function_component/resolution.rb +61 -0
- data/lib/react/function_component/runner.rb +19 -0
- data/lib/react/native_constant_wrapper.rb +34 -0
- data/lib/react/pure_component/base.rb +9 -0
- data/lib/react/pure_component/mixin.rb +17 -0
- data/lib/react/redux_component/api.rb +132 -0
- data/lib/react/redux_component/app_store_defaults.rb +38 -0
- data/lib/react/redux_component/app_store_proxy.rb +46 -0
- data/lib/react/redux_component/base.rb +9 -0
- data/lib/react/redux_component/class_store_proxy.rb +50 -0
- data/lib/react/redux_component/component_class_store_defaults.rb +40 -0
- data/lib/react/redux_component/component_instance_store_defaults.rb +41 -0
- data/lib/react/redux_component/initializer.rb +14 -0
- data/lib/react/redux_component/instance_store_proxy.rb +50 -0
- data/lib/react/redux_component/mixin.rb +18 -0
- data/lib/react/redux_component/native_component_constructor.rb +119 -0
- data/lib/react/redux_component/reducers.rb +53 -0
- data/lib/react/ref.rb +19 -0
- data/lib/react/synthetic_event.rb +53 -0
- data/lib/react/version.rb +3 -0
- data/lib/react_dom.rb +31 -0
- data/lib/react_dom_server.rb +17 -0
- metadata +167 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require_relative 'lib/react/version.rb'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'isomorfeus-react'
|
6
|
+
s.version = React::VERSION
|
7
|
+
|
8
|
+
s.authors = ['Jan Biedermann']
|
9
|
+
s.email = ['jan@kursator.com']
|
10
|
+
s.homepage = 'http://isomorfeus.com'
|
11
|
+
s.summary = 'React for Opal Ruby.'
|
12
|
+
s.license = 'MIT'
|
13
|
+
s.description = 'Write React Components in Ruby.'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(gemfiles|s)/}) }
|
16
|
+
#s.test_files = `git ls-files -- {test,s,features}/*`.split("\n")
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
|
19
|
+
s.add_dependency 'opal', '>= 0.11.0', '< 0.12.0'
|
20
|
+
s.add_dependency 'opal-activesupport', '~> 0.3.1'
|
21
|
+
s.add_dependency 'opal-browser', '~> 0.2.0'
|
22
|
+
s.add_dependency 'isomorfeus-redux', '~> 4.0.0'
|
23
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
if RUBY_ENGINE == 'opal'
|
2
|
+
require 'opal'
|
3
|
+
require 'native'
|
4
|
+
require 'active_support/core_ext/string'
|
5
|
+
require 'react/active_support_support'
|
6
|
+
require 'browser/support'
|
7
|
+
require 'browser/event'
|
8
|
+
require 'browser/event_source'
|
9
|
+
require 'browser/screen'
|
10
|
+
require 'browser/socket'
|
11
|
+
require 'browser/window'
|
12
|
+
require 'browser/dom/node'
|
13
|
+
require 'browser/dom/element'
|
14
|
+
require 'isomorfeus-redux'
|
15
|
+
require 'react/version'
|
16
|
+
require 'react'
|
17
|
+
# require 'react/element' # usually not needed
|
18
|
+
require 'react/synthetic_event'
|
19
|
+
require 'react_dom'
|
20
|
+
# React::Component
|
21
|
+
require 'react/component/api'
|
22
|
+
require 'react/component/unsafe_api'
|
23
|
+
require 'react/component/initializer'
|
24
|
+
require 'react/component/features'
|
25
|
+
require 'react/native_constant_wrapper'
|
26
|
+
require 'react/context_wrapper'
|
27
|
+
require 'react/component/native_component_constructor'
|
28
|
+
require 'react/component/native_component_validate_prop'
|
29
|
+
require 'react/component/props'
|
30
|
+
require 'react/component/state'
|
31
|
+
require 'react/component/match'
|
32
|
+
require 'react/component/location'
|
33
|
+
require 'react/component/history'
|
34
|
+
require 'react/component/elements'
|
35
|
+
require 'react/component/resolution'
|
36
|
+
require 'react/component/should_component_update'
|
37
|
+
require 'react/component/event_handler'
|
38
|
+
require 'react/component/mixin'
|
39
|
+
require 'react/component/base'
|
40
|
+
# React::PureComponent
|
41
|
+
require 'react/pure_component/mixin'
|
42
|
+
require 'react/pure_component/base'
|
43
|
+
# Function Component
|
44
|
+
require 'react/function_component/resolution'
|
45
|
+
require 'react/function_component/runner'
|
46
|
+
require 'react/function_component/creator'
|
47
|
+
# Redux::Component
|
48
|
+
require 'react/redux_component/component_class_store_defaults'
|
49
|
+
require 'react/redux_component/component_instance_store_defaults'
|
50
|
+
require 'react/redux_component/app_store_defaults'
|
51
|
+
require 'react/redux_component/api'
|
52
|
+
require 'react/redux_component/initializer'
|
53
|
+
require 'react/redux_component/app_store_proxy'
|
54
|
+
require 'react/redux_component/class_store_proxy'
|
55
|
+
require 'react/redux_component/instance_store_proxy'
|
56
|
+
require 'react/redux_component/native_component_constructor'
|
57
|
+
require 'react/redux_component/mixin'
|
58
|
+
require 'react/redux_component/base'
|
59
|
+
require 'react/redux_component/reducers'
|
60
|
+
require 'isomorfeus/config'
|
61
|
+
|
62
|
+
# init component reducers
|
63
|
+
React::ReduxComponent::Reducers.add_component_reducers_to_store
|
64
|
+
|
65
|
+
# init LucidApplicationContext (Store Provider and Consumer)
|
66
|
+
require 'lucid_app/context'
|
67
|
+
|
68
|
+
LucidApp::Context.create_application_context
|
69
|
+
|
70
|
+
# LucidComponent
|
71
|
+
require 'lucid_component/api'
|
72
|
+
require 'lucid_component/initializer'
|
73
|
+
require 'lucid_component/native_component_constructor'
|
74
|
+
require 'lucid_component/event_handler'
|
75
|
+
require 'lucid_component/mixin'
|
76
|
+
require 'lucid_component/base'
|
77
|
+
|
78
|
+
# LucidApp
|
79
|
+
require 'lucid_app/api'
|
80
|
+
require 'lucid_app/native_component_constructor'
|
81
|
+
require 'lucid_app/mixin'
|
82
|
+
require 'lucid_app/base'
|
83
|
+
|
84
|
+
# allow mounting of components
|
85
|
+
require 'isomorfeus/top_level'
|
86
|
+
|
87
|
+
# initalize Store, options, etc.
|
88
|
+
Isomorfeus.init
|
89
|
+
else
|
90
|
+
require 'opal'
|
91
|
+
require 'opal-activesupport'
|
92
|
+
require 'opal-browser'
|
93
|
+
require 'isomorfeus-redux'
|
94
|
+
require 'react/version'
|
95
|
+
require 'isomorfeus/config'
|
96
|
+
require 'isomorfeus/view_helpers'
|
97
|
+
|
98
|
+
Opal.append_path(__dir__.untaint)
|
99
|
+
|
100
|
+
if defined?(Rails)
|
101
|
+
module Isomorfeus
|
102
|
+
module Model
|
103
|
+
class Railtie < Rails::Railtie
|
104
|
+
def delete_first(a, e)
|
105
|
+
a.delete_at(a.index(e) || a.length)
|
106
|
+
end
|
107
|
+
|
108
|
+
config.before_configuration do |_|
|
109
|
+
Rails.configuration.tap do |config|
|
110
|
+
# rails will add everything immediately below app to eager and auto load, so we need to remove it
|
111
|
+
delete_first config.eager_load_paths, "#{config.root}/app/isomorfeus"
|
112
|
+
|
113
|
+
unless Rails.env.production?
|
114
|
+
# rails will add everything immediately below app to eager and auto load, so we need to remove it
|
115
|
+
delete_first config.autoload_paths, "#{config.root}/app/isomorfeus"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
if Dir.exist?(File.join('app', 'isomorfeus'))
|
125
|
+
# Opal.append_path(File.expand_path(File.join('app', 'isomorfeus', 'components')))
|
126
|
+
Opal.append_path(File.expand_path(File.join('app', 'isomorfeus'))) unless Opal.paths.include?(File.expand_path(File.join('app', 'isomorfeus')))
|
127
|
+
elsif Dir.exist?('isomorfeus')
|
128
|
+
# Opal.append_path(File.expand_path(File.join('isomorfeus', 'components')))
|
129
|
+
Opal.append_path(File.expand_path('isomorfeus')) unless Opal.paths.include?(File.expand_path('isomorfeus'))
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
if RUBY_ENGINE == 'opal'
|
2
|
+
module Isomorfeus
|
3
|
+
class << self
|
4
|
+
attr_reader :options
|
5
|
+
attr_reader :initialized
|
6
|
+
attr_reader :store
|
7
|
+
|
8
|
+
def init
|
9
|
+
return if initialized
|
10
|
+
@initialized = true
|
11
|
+
# at least one reducer must have been added at this stage
|
12
|
+
# this happened in isomorfeus-react.rb, where the component reducers were added
|
13
|
+
@store = Redux::Store.init!
|
14
|
+
`Opal.Isomorfeus.store = #@store`
|
15
|
+
init_options
|
16
|
+
execute_init_classes
|
17
|
+
end
|
18
|
+
|
19
|
+
def init_options
|
20
|
+
return @options if @options
|
21
|
+
@options = Hash.new(`Opal.IsomorfeusOptions`)
|
22
|
+
@options.keys.each do |option|
|
23
|
+
define_singleton_method(option) do
|
24
|
+
@options[option]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def execute_init_classes
|
30
|
+
if options.has_key?(:client_init_class_names)
|
31
|
+
client_init_class_names.each do |constant|
|
32
|
+
constant.constantize.send(:init)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
else
|
39
|
+
module Isomorfeus
|
40
|
+
class << self
|
41
|
+
attr_accessor :client_init_class_names
|
42
|
+
attr_accessor :prerendering
|
43
|
+
attr_accessor :version
|
44
|
+
|
45
|
+
def add_client_option(option)
|
46
|
+
@options_for_client ||= Set.new
|
47
|
+
@options_for_client << option
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_client_options(options)
|
51
|
+
options.each do |option|
|
52
|
+
add_client_option(option)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_client_init_class_name(class_name)
|
57
|
+
client_init_class_names << class_name
|
58
|
+
end
|
59
|
+
|
60
|
+
def configuration(&block)
|
61
|
+
block.call(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def options_for_client
|
65
|
+
@options_for_client
|
66
|
+
end
|
67
|
+
|
68
|
+
def options_hash_for_client
|
69
|
+
opts = {}
|
70
|
+
Isomorfeus.options_for_client.each do |option|
|
71
|
+
opts[option] = Isomorfeus.send(option)
|
72
|
+
end
|
73
|
+
opts
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
self.add_client_option(:client_init_class_names)
|
78
|
+
self.add_client_option(:version)
|
79
|
+
|
80
|
+
self.client_init_class_names = []
|
81
|
+
self.prerendering = :off
|
82
|
+
# self.version = ::Isomorfeus::Component::VERSION # thats equal to the isomorfeus version
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
class TopLevel
|
3
|
+
def self.search_path
|
4
|
+
@search_path ||= [Object]
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.on_ready_mount(component, params = nil, element_query = nil)
|
8
|
+
# init in case it hasn't been run yet
|
9
|
+
Isomorfeus.init
|
10
|
+
# this looks a bit odd but works across _all_ browsers, and no event handler mess
|
11
|
+
# TODO: server rendering
|
12
|
+
%x{
|
13
|
+
function do_the_mount() { #{mount(component, params, element_query)} };
|
14
|
+
function ready_fun() {
|
15
|
+
/in/.test(document.readyState) ? setTimeout(ready_fun,5) : do_the_mount();
|
16
|
+
};
|
17
|
+
ready_fun();
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.mount(component, params = nil, element_or_query = nil)
|
22
|
+
# TODO: server rendering
|
23
|
+
if element_or_query.nil?
|
24
|
+
if params.nil?
|
25
|
+
element = `document.body.querySelector('div')`
|
26
|
+
elsif params.class == String
|
27
|
+
element = `document.body.querySelector(params)`
|
28
|
+
params = nil
|
29
|
+
elsif params.is_a?(Browser::DOM::Node)
|
30
|
+
element = params.to_n
|
31
|
+
params = nil
|
32
|
+
end
|
33
|
+
else
|
34
|
+
if element_or_query.class == String
|
35
|
+
element = `document.body.querySelector(params)`
|
36
|
+
elsif element_or_query.is_a?(Browser::DOM::Node)
|
37
|
+
element = params.to_n
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
ReactDOM.render(React.create_element(component, params), element)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.ujs_mount
|
45
|
+
# TODO: implement mount using RailsUJS, for turbolinks and things
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
module ViewHelpers
|
3
|
+
def isomorfeus_script_tag(options = {})
|
4
|
+
# client side used options:
|
5
|
+
# current_user_id
|
6
|
+
# session_id
|
7
|
+
# form_authenticity_token
|
8
|
+
options_hash = Isomorfeus.options_hash_for_client
|
9
|
+
options_hash.merge!(options)
|
10
|
+
tag = <<~SCRIPT
|
11
|
+
<script type="text/javascript">
|
12
|
+
Opal.IsomorfeusOptions = #{options_hash.to_json};
|
13
|
+
Opal.Isomorfeus.$init();
|
14
|
+
</script>
|
15
|
+
SCRIPT
|
16
|
+
tag.respond_to?(:html_safe) ? tag.html_safe : tag
|
17
|
+
end
|
18
|
+
|
19
|
+
def react_component(component_name, params)
|
20
|
+
component_name_id = component_id_name(component_name)
|
21
|
+
tag = <<~SCRIPT
|
22
|
+
<div id="#{component_name_id}"></div>
|
23
|
+
<script type="text/javascript">
|
24
|
+
var component = Opal.Object.$const_get("#{component_name}");
|
25
|
+
var json_params = #{Oj.dump(params, mode: :compat)};
|
26
|
+
Opal.Isomorfeus.$const_get('TopLevel').$mount(component, Opal.Hash.$new(json_params), "##{component_name_id}" );
|
27
|
+
</script>
|
28
|
+
SCRIPT
|
29
|
+
tag.respond_to?(:html_safe) ? tag.html_safe : tag
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def component_id_name(component_name)
|
35
|
+
"#{component_name.underscore}_#{Random.rand.to_s[2..-1]}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module LucidApp
|
2
|
+
module API
|
3
|
+
def self.included(base)
|
4
|
+
base.instance_exec do
|
5
|
+
def render(&block)
|
6
|
+
%x{
|
7
|
+
self.react_component.prototype.render = function() {
|
8
|
+
Opal.React.render_buffer.push([]);
|
9
|
+
Opal.React.active_components.push(this);
|
10
|
+
Opal.React.active_redux_components.push(this.__ruby_instance);
|
11
|
+
#{`this.__ruby_instance`.instance_exec(&block)};
|
12
|
+
Opal.React.active_redux_components.pop();
|
13
|
+
Opal.React.active_components.pop();
|
14
|
+
var children = Opal.React.render_buffer.pop();
|
15
|
+
return React.createElement(LucidApplicationContext.Provider, { value: this.state.isomorfeus_store_state }, children);
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module LucidApp
|
2
|
+
module Mixin
|
3
|
+
def self.included(base)
|
4
|
+
base.include(::Native::Wrapper)
|
5
|
+
base.extend(::LucidApp::NativeComponentConstructor)
|
6
|
+
base.extend(::React::Component::NativeComponentValidateProp)
|
7
|
+
base.extend(::React::Component::ShouldComponentUpdate)
|
8
|
+
base.extend(::React::Component::EventHandler)
|
9
|
+
base.include(::React::Component::Elements)
|
10
|
+
base.include(::React::Component::API)
|
11
|
+
base.include(::React::ReduxComponent::API)
|
12
|
+
base.include(::LucidApp::API)
|
13
|
+
base.include(::React::Component::Features)
|
14
|
+
base.include(::React::Component::Resolution)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module LucidApp
|
2
|
+
module NativeComponentConstructor
|
3
|
+
# for should_component_update we apply ruby semantics for comparing props
|
4
|
+
# to do so, we convert the props to ruby hashes and then compare
|
5
|
+
# this makes sure, that for example rubys Nil object gets handled properly
|
6
|
+
def self.extended(base)
|
7
|
+
component_name = base.to_s
|
8
|
+
# language=JS
|
9
|
+
%x{
|
10
|
+
base.react_component = class extends React.Component {
|
11
|
+
constructor(props) {
|
12
|
+
super(props);
|
13
|
+
if (base.$default_state_defined()) {
|
14
|
+
this.state = base.$state().$to_n();
|
15
|
+
} else {
|
16
|
+
this.state = {};
|
17
|
+
};
|
18
|
+
this.state.isomorfeus_store_state = Opal.Isomorfeus.store.native.getState();
|
19
|
+
var current_store_state = this.state.isomorfeus_store_state;
|
20
|
+
if (typeof current_store_state.component_class_state[#{component_name}] !== "undefined") {
|
21
|
+
this.state.component_class_state = {};
|
22
|
+
this.state.component_class_state[#{component_name}] = current_store_state.component_class_state[#{component_name}];
|
23
|
+
} else {
|
24
|
+
this.state.component_class_state = {};
|
25
|
+
this.state.component_class_state[#{component_name}] = {};
|
26
|
+
};
|
27
|
+
this.__ruby_instance = base.$new(this);
|
28
|
+
this.__object_id = this.__ruby_instance.$object_id().$to_s();
|
29
|
+
if (!this.state.component_state) {
|
30
|
+
this.state.component_state = {};
|
31
|
+
this.state.component_state[this.__object_id] = {};
|
32
|
+
};
|
33
|
+
var event_handlers = #{base.event_handlers};
|
34
|
+
for (var i = 0; i < event_handlers.length; i++) {
|
35
|
+
this[event_handlers[i]] = this[event_handlers[i]].bind(this);
|
36
|
+
}
|
37
|
+
var defined_refs = #{base.defined_refs};
|
38
|
+
for (var ref in defined_refs) {
|
39
|
+
if (defined_refs[ref] != null) {
|
40
|
+
this[ref] = function(element) {
|
41
|
+
#{`this.__ruby_instance`.instance_exec(React::Ref.new(`element`), `defined_refs[ref]`)}
|
42
|
+
}
|
43
|
+
this[ref] = this[ref].bind(this);
|
44
|
+
} else {
|
45
|
+
this[ref] = React.createRef();
|
46
|
+
}
|
47
|
+
}
|
48
|
+
this.listener = this.listener.bind(this);
|
49
|
+
this.unsubscriber = Opal.Isomorfeus.store.native.subscribe(this.listener);
|
50
|
+
}
|
51
|
+
static get displayName() {
|
52
|
+
return #{component_name};
|
53
|
+
}
|
54
|
+
listener() {
|
55
|
+
var next_state = Opal.Isomorfeus.store.native.getState();
|
56
|
+
var current_ruby_state = Opal.Hash.$new(this.state.isomorfeus_store_state);
|
57
|
+
var next_ruby_state = Opal.Hash.$new(next_state);
|
58
|
+
if (#{`next_ruby_state` != `current_ruby_state`}) {
|
59
|
+
this.setState({isomorfeus_store_state: next_state});
|
60
|
+
}
|
61
|
+
}
|
62
|
+
componentWillUnmount() {
|
63
|
+
if (typeof this.unsubscriber === "function") { this.unsubscriber(); };
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|