isomorfeus-react 16.5.1
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 +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
|
+
|