hyper-component 0.12.3 → 0.99.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +27 -0
  3. data/.gitignore +42 -41
  4. data/.travis.yml +29 -0
  5. data/CHANGELOG.md +143 -0
  6. data/DOCS.md +1515 -0
  7. data/Gemfile +5 -2
  8. data/Gemfile.lock +244 -193
  9. data/LICENSE +5 -7
  10. data/README.md +49 -0
  11. data/Rakefile +40 -0
  12. data/hyper-component.gemspec +41 -31
  13. data/lib/hyper-component.rb +44 -9
  14. data/lib/rails-helpers/top_level_rails_component.rb +79 -0
  15. data/lib/react/api.rb +270 -0
  16. data/lib/react/callbacks.rb +42 -0
  17. data/lib/react/children.rb +38 -0
  18. data/lib/react/component.rb +189 -0
  19. data/lib/react/component/api.rb +70 -0
  20. data/lib/react/component/base.rb +13 -0
  21. data/lib/react/component/class_methods.rb +175 -0
  22. data/lib/react/component/dsl_instance_methods.rb +23 -0
  23. data/lib/react/component/params.rb +6 -0
  24. data/lib/react/component/props_wrapper.rb +90 -0
  25. data/lib/react/component/should_component_update.rb +99 -0
  26. data/lib/react/component/tags.rb +116 -0
  27. data/lib/react/config.rb +5 -0
  28. data/lib/react/element.rb +159 -0
  29. data/lib/react/event.rb +76 -0
  30. data/lib/react/ext/hash.rb +9 -0
  31. data/lib/react/ext/opal-jquery/element.rb +37 -0
  32. data/lib/react/ext/string.rb +8 -0
  33. data/lib/react/native_library.rb +87 -0
  34. data/lib/react/object.rb +15 -0
  35. data/lib/react/react-source-server.rb +3 -0
  36. data/lib/react/react-source.rb +17 -0
  37. data/lib/react/ref_callback.rb +31 -0
  38. data/lib/react/rendering_context.rb +149 -0
  39. data/lib/react/server.rb +19 -0
  40. data/lib/react/state_wrapper.rb +23 -0
  41. data/lib/react/test.rb +16 -0
  42. data/lib/react/test/dsl.rb +17 -0
  43. data/lib/react/test/matchers/render_html_matcher.rb +56 -0
  44. data/lib/react/test/rspec.rb +15 -0
  45. data/lib/react/test/session.rb +37 -0
  46. data/lib/react/test/utils.rb +71 -0
  47. data/lib/react/to_key.rb +26 -0
  48. data/lib/react/top_level.rb +110 -0
  49. data/lib/react/top_level_render.rb +28 -0
  50. data/lib/react/validator.rb +132 -0
  51. data/lib/reactive-ruby/component_loader.rb +43 -0
  52. data/lib/reactive-ruby/isomorphic_helpers.rb +233 -0
  53. data/lib/reactive-ruby/rails.rb +8 -0
  54. data/lib/reactive-ruby/rails/component_mount.rb +48 -0
  55. data/lib/reactive-ruby/rails/controller_helper.rb +14 -0
  56. data/lib/reactive-ruby/rails/railtie.rb +20 -0
  57. data/lib/reactive-ruby/serializers.rb +23 -0
  58. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +46 -0
  59. data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +46 -0
  60. data/lib/{hyperloop/component → reactive-ruby}/version.rb +1 -1
  61. data/lib/reactrb/auto-import.rb +27 -0
  62. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
  63. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +5 -0
  64. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
  65. data/misc/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
  66. data/misc/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  67. data/misc/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
  68. data/misc/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  69. data/misc/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  70. data/misc/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  71. data/misc/generators/reactive_ruby/test_app/test_app_generator.rb +121 -0
  72. data/misc/how-component-name-lookup-works.md +145 -0
  73. data/misc/hyperloop-logo-small-pink.png +0 -0
  74. data/misc/logo1.png +0 -0
  75. data/misc/logo2.png +0 -0
  76. data/misc/logo3.png +0 -0
  77. data/path_release_steps.md +9 -0
  78. metadata +260 -37
  79. data/CODE_OF_CONDUCT.md +0 -49
@@ -0,0 +1,42 @@
1
+ require 'hyperloop-config'
2
+
3
+ module React
4
+ module Callbacks
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ def run_callback(name, *args)
10
+ self.class.callbacks_for(name).each do |callback|
11
+ if callback.is_a?(Proc)
12
+ instance_exec(*args, &callback)
13
+ else
14
+ send(callback, *args)
15
+ end
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ def define_callback(callback_name, &after_define_hook)
21
+ wrapper_name = "_#{callback_name}_callbacks"
22
+ define_singleton_method(wrapper_name) do
23
+ Hyperloop::Context.set_var(self, "@#{wrapper_name}", force: true) { [] }
24
+ end
25
+ define_singleton_method(callback_name) do |*args, &block|
26
+ send(wrapper_name).concat(args)
27
+ send(wrapper_name).push(block) if block_given?
28
+ after_define_hook.call(*args, &block) if after_define_hook
29
+ end
30
+ end
31
+
32
+ def callbacks_for(callback_name)
33
+ wrapper_name = "_#{callback_name}_callbacks"
34
+ if superclass.respond_to? :callbacks_for
35
+ superclass.callbacks_for(callback_name)
36
+ else
37
+ []
38
+ end + send(wrapper_name)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ module React
2
+ class Children
3
+ include Enumerable
4
+
5
+ def initialize(children)
6
+ @children = children
7
+ end
8
+
9
+ def render
10
+ each(&:render)
11
+ end
12
+
13
+ def to_proc
14
+ -> () { render }
15
+ end
16
+
17
+ def each(&block)
18
+ return to_enum(__callee__) { length } unless block_given?
19
+ return [] unless length > 0
20
+ collection = []
21
+ %x{
22
+ React.Children.forEach(#{@children}, function(context){
23
+ #{
24
+ element = React::Element.new(`context`)
25
+ block.call(element)
26
+ collection << element
27
+ }
28
+ })
29
+ }
30
+ collection
31
+ end
32
+
33
+ def length
34
+ @length ||= `React.Children.count(#{@children})`
35
+ end
36
+ alias_method :size, :length
37
+ end
38
+ end
@@ -0,0 +1,189 @@
1
+ require 'react/ext/string'
2
+ require 'react/ext/hash'
3
+ require 'active_support/core_ext/class/attribute'
4
+ require 'react/callbacks'
5
+ require 'react/rendering_context'
6
+ require 'hyper-store'
7
+ require 'react/state_wrapper'
8
+ require 'react/component/api'
9
+ require 'react/component/class_methods'
10
+ require 'react/component/props_wrapper'
11
+
12
+ module Hyperloop
13
+ class Component
14
+ class << self
15
+ def mounted_components
16
+ @mounted_components ||= Set.new
17
+ end
18
+
19
+ def force_update!
20
+ components = mounted_components.to_a
21
+ components.each do |comp|
22
+ next unless mounted_components.include? comp
23
+ comp.force_update!
24
+ end
25
+ end
26
+ end
27
+
28
+ module Mixin
29
+ def self.included(base)
30
+ base.include(Hyperloop::Store::Mixin)
31
+ base.include(React::Component::API)
32
+ base.include(React::Callbacks)
33
+ base.include(React::Component::Tags)
34
+ base.include(React::Component::DslInstanceMethods)
35
+ base.include(React::Component::ShouldComponentUpdate)
36
+ base.class_eval do
37
+ class_attribute :initial_state
38
+ define_callback :before_mount
39
+ define_callback :after_mount
40
+ define_callback :before_receive_props
41
+ define_callback :before_update
42
+ define_callback :after_update
43
+ define_callback :before_unmount
44
+ define_callback(:after_error) { React::API.add_after_error_hook(base) }
45
+ end
46
+ base.extend(React::Component::ClassMethods)
47
+ end
48
+
49
+ def self.deprecation_warning(message)
50
+ React::Component.deprecation_warning(name, message)
51
+ end
52
+
53
+ def deprecation_warning(message)
54
+ React::Component.deprecation_warning(self.class.name, message)
55
+ end
56
+
57
+ def initialize(native_element)
58
+ @native = native_element
59
+ init_store
60
+ end
61
+
62
+ def emit(event_name, *args)
63
+ if React::Event::BUILT_IN_EVENTS.include?(built_in_event_name = "on#{event_name.to_s.event_camelize}")
64
+ params[built_in_event_name].call(*args)
65
+ else
66
+ params["on_#{event_name}"].call(*args)
67
+ end
68
+ end
69
+
70
+ def component_will_mount
71
+ React::IsomorphicHelpers.load_context(true) if React::IsomorphicHelpers.on_opal_client?
72
+ React::State.set_state_context_to(self) do
73
+ Hyperloop::Component.mounted_components << self
74
+ run_callback(:before_mount)
75
+ end
76
+ end
77
+
78
+ def component_did_mount
79
+ React::State.set_state_context_to(self) do
80
+ run_callback(:after_mount)
81
+ React::State.update_states_to_observe
82
+ end
83
+ end
84
+
85
+ def component_will_receive_props(next_props)
86
+ # need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
87
+ # for now we are just using it to clear processed_params
88
+ React::State.set_state_context_to(self) { run_callback(:before_receive_props, next_props) }
89
+ @_receiving_props = true
90
+ end
91
+
92
+ def component_will_update(next_props, next_state)
93
+ React::State.set_state_context_to(self) { run_callback(:before_update, next_props, next_state) }
94
+ params._reset_all_others_cache if @_receiving_props
95
+ @_receiving_props = false
96
+ end
97
+
98
+ def component_did_update(prev_props, prev_state)
99
+ React::State.set_state_context_to(self) do
100
+ run_callback(:after_update, prev_props, prev_state)
101
+ React::State.update_states_to_observe
102
+ end
103
+ end
104
+
105
+ def component_will_unmount
106
+ React::State.set_state_context_to(self) do
107
+ run_callback(:before_unmount)
108
+ React::State.remove
109
+ Hyperloop::Component.mounted_components.delete self
110
+ end
111
+ end
112
+
113
+ def component_did_catch(error, info)
114
+ React::State.set_state_context_to(self) do
115
+ run_callback(:after_error, error, info)
116
+ end
117
+ end
118
+
119
+ attr_reader :waiting_on_resources
120
+
121
+ def update_react_js_state(object, name, value)
122
+ if object
123
+ name = "#{object.class}.#{name}" unless object == self
124
+ # Date.now() has only millisecond precision, if several notifications of
125
+ # observer happen within a millisecond, updates may get lost.
126
+ # to mitigate this the Math.random() appends some random number
127
+ # this way notifactions will happen as expected by the rest of hyperloop
128
+ set_state(
129
+ '***_state_updated_at-***' => `Date.now() + Math.random()`,
130
+ name => value
131
+ )
132
+ else
133
+ set_state name => value
134
+ end
135
+ end
136
+
137
+ def set_state_synchronously?
138
+ @native.JS[:__opalInstanceSyncSetState]
139
+ end
140
+
141
+ def render
142
+ raise 'no render defined'
143
+ end unless method_defined?(:render)
144
+
145
+ def _render_wrapper
146
+ React::State.set_state_context_to(self, true) do
147
+ element = React::RenderingContext.render(nil) { render || '' }
148
+ @waiting_on_resources =
149
+ element.waiting_on_resources if element.respond_to? :waiting_on_resources
150
+ element
151
+ end
152
+ end
153
+
154
+ def watch(value, &on_change)
155
+ Observable.new(value, on_change)
156
+ end
157
+
158
+ def define_state(*args, &block)
159
+ React::State.initialize_states(self, self.class.define_state(*args, &block))
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ module React
166
+ module Component
167
+ def self.included(base)
168
+ # note this is turned off during old style testing: See the spec_helper
169
+ deprecation_warning base, "The module name React::Component has been deprecated. Use Hyperloop::Component::Mixin instead."
170
+ base.include Hyperloop::Component::Mixin
171
+ end
172
+ def self.deprecation_warning(name, message)
173
+ @deprecation_messages ||= []
174
+ message = "Warning: Deprecated feature used in #{name}. #{message}"
175
+ unless @deprecation_messages.include? message
176
+ @deprecation_messages << message
177
+ React::IsomorphicHelpers.log message, :warning
178
+ end
179
+ end
180
+ end
181
+ module ComponentNoNotice
182
+ def self.included(base)
183
+ base.include Hyperloop::Component::Mixin
184
+ end
185
+ end
186
+ end
187
+
188
+ module React
189
+ end
@@ -0,0 +1,70 @@
1
+ module React
2
+ module Component
3
+ module API
4
+ def dom_node
5
+ `ReactDOM.findDOMNode(#{self}.native)` # react >= v0.15.0
6
+ end
7
+
8
+ def mounted?
9
+ `(#{self}.is_mounted === undefined) ? false : #{self}.is_mounted`
10
+ end
11
+
12
+ def force_update!
13
+ `#{self}.native.forceUpdate()`
14
+ self
15
+ end
16
+
17
+ def set_props(prop, &block)
18
+ raise "set_props: setProps() is no longer supported by react"
19
+ end
20
+ alias :set_props! :set_props
21
+
22
+ def set_state(state, &block)
23
+ set_or_replace_state_or_prop(state, 'setState', &block)
24
+ end
25
+
26
+ def set_state!(state, &block)
27
+ set_or_replace_state_or_prop(state, 'setState', &block)
28
+ `#{self}.native.forceUpdate()`
29
+ end
30
+
31
+ private
32
+
33
+ def set_or_replace_state_or_prop(state_or_prop, method, &block)
34
+ raise "No native ReactComponent associated" unless @native
35
+ `var state_prop_n = #{state_or_prop.shallow_to_n}`
36
+ # the state object is initalized when the ruby component is instantiated
37
+ # this is detected by self.native.__opalInstanceInitializedState
38
+ # which is set in the native component constructor in react/api.rb
39
+ # the setState update callback is not called when initalizing initial state
40
+ if block
41
+ %x{
42
+ if (#{@native}.__opalInstanceInitializedState === true) {
43
+ #{@native}[method](state_prop_n, function(){
44
+ block.$call();
45
+ });
46
+ } else {
47
+ for (var sp in state_prop_n) {
48
+ if (state_prop_n.hasOwnProperty(sp)) {
49
+ #{@native}.state[sp] = state_prop_n[sp];
50
+ }
51
+ }
52
+ }
53
+ }
54
+ else
55
+ %x{
56
+ if (#{@native}.__opalInstanceInitializedState === true) {
57
+ #{@native}[method](state_prop_n);
58
+ } else {
59
+ for (var sp in state_prop_n) {
60
+ if (state_prop_n.hasOwnProperty(sp)) {
61
+ #{@native}.state[sp] = state_prop_n[sp];
62
+ }
63
+ }
64
+ }
65
+ }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,13 @@
1
+ module React
2
+ module Component
3
+ class Base
4
+ def self.inherited(child)
5
+ # note this is turned off during old style testing: See the spec_helper
6
+ unless child.to_s == "React::Component::HyperTestDummy"
7
+ React::Component.deprecation_warning child, "The class name React::Component::Base has been deprecated. Use Hyperloop::Component instead."
8
+ end
9
+ child.include(ComponentNoNotice)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,175 @@
1
+ module React
2
+ module Component
3
+ # class level methods (macros) for components
4
+ module ClassMethods
5
+
6
+ def deprecation_warning(message)
7
+ React::Component.deprecation_warning(self, message)
8
+ end
9
+
10
+ def reactrb_component?
11
+ true
12
+ end
13
+
14
+ def backtrace(*args)
15
+ @dont_catch_exceptions = (args[0] == :none)
16
+ @backtrace_off = @dont_catch_exceptions || (args[0] == :off)
17
+ end
18
+
19
+ def append_backtrace(message_array, backtrace)
20
+ message_array << " #{backtrace[0]}"
21
+ backtrace[1..-1].each { |line| message_array << line }
22
+ end
23
+
24
+ def render(container = nil, params = {}, &block)
25
+ if container
26
+ container = container.type if container.is_a? React::Element
27
+ define_method :render do
28
+ React::RenderingContext.render(container, params) { instance_eval(&block) if block }
29
+ end
30
+ else
31
+ define_method(:render) { instance_eval(&block) }
32
+ end
33
+ end
34
+
35
+ # method missing will assume the method is a class name, and will treat this a render of
36
+ # of the component, i.e. Foo::Bar.baz === Foo::Bar().baz
37
+
38
+ def method_missing(name, *args, &children)
39
+ Object.method_missing(name, *args, &children) unless args.empty?
40
+ React::RenderingContext.render(
41
+ self, class: React::Element.haml_class_name(name), &children
42
+ )
43
+ end
44
+
45
+ def validator
46
+ @validator ||= Validator.new(props_wrapper)
47
+ end
48
+
49
+ def prop_types
50
+ if self.validator
51
+ {
52
+ _componentValidator: %x{
53
+ function(props, propName, componentName) {
54
+ var errors = #{validator.validate(Hash.new(`props`))};
55
+ return #{`errors`.count > 0 ? `new Error(#{"In component `#{name}`\n" + `errors`.join("\n")})` : `undefined`};
56
+ }
57
+ }
58
+ }
59
+ else
60
+ {}
61
+ end
62
+ end
63
+
64
+ def default_props
65
+ validator.default_props
66
+ end
67
+
68
+ def params(&block)
69
+ validator.build(&block)
70
+ end
71
+
72
+ def props_wrapper
73
+ @props_wrapper ||= Class.new(PropsWrapper)
74
+ end
75
+
76
+ def param(*args)
77
+ if args[0].is_a? Hash
78
+ options = args[0]
79
+ name = options.first[0]
80
+ default = options.first[1]
81
+ options.delete(name)
82
+ options.merge!({default: default})
83
+ else
84
+ name = args[0]
85
+ options = args[1] || {}
86
+ end
87
+ if options[:default]
88
+ validator.optional(name, options)
89
+ else
90
+ validator.requires(name, options)
91
+ end
92
+ end
93
+
94
+ def collect_other_params_as(name)
95
+ validator.all_other_params(name) { props }
96
+ end
97
+
98
+ alias other_params collect_other_params_as
99
+ alias others collect_other_params_as
100
+
101
+ def define_state(*states, &block)
102
+ deprecation_warning "'define_state' is deprecated. Use the 'state' macro to declare states."
103
+ default_initial_value = (block && block.arity == 0) ? yield : nil
104
+ states_hash = (states.last.is_a?(Hash)) ? states.pop : {}
105
+ states.each { |name| state(name => default_initial_value) } # was states_hash[name] = default_initial_value
106
+ states_hash.each { |name, value| state(name => value) }
107
+ end
108
+
109
+ def export_state(*states, &block)
110
+ deprecation_warning "'export_state' is deprecated. Use the 'state' macro to declare states."
111
+ default_initial_value = (block && block.arity == 0) ? yield : nil
112
+ states_hash = (states.last.is_a?(Hash)) ? states.pop : {}
113
+ states.each { |name| states_hash[name] = default_initial_value }
114
+ states_hash.each do |name, value|
115
+ state(name => value, scope: :class, reader: true)
116
+ singleton_class.define_method("#{name}!") do |*args|
117
+ mutate.__send__(name, *args)
118
+ end
119
+ end
120
+ end
121
+
122
+ def native_mixin(item)
123
+ native_mixins << item
124
+ end
125
+
126
+ def native_mixins
127
+ @native_mixins ||= []
128
+ end
129
+
130
+ def static_call_back(name, &block)
131
+ static_call_backs[name] = block
132
+ end
133
+
134
+ def static_call_backs
135
+ @static_call_backs ||= {}
136
+ end
137
+
138
+ def export_component(opts = {})
139
+ export_name = (opts[:as] || name).split('::')
140
+ first_name = export_name.first
141
+ Native(`Opal.global`)[first_name] = add_item_to_tree(
142
+ Native(`Opal.global`)[first_name],
143
+ [React::API.create_native_react_class(self)] + export_name[1..-1].reverse
144
+ ).to_n
145
+ end
146
+
147
+ def imports(component_name)
148
+ React::API.import_native_component(
149
+ self, React::API.eval_native_react_component(component_name)
150
+ )
151
+ define_method(:render) {} # define a dummy render method - will never be called...
152
+ rescue Exception => e # rubocop:disable Lint/RescueException : we need to catch everything!
153
+ raise "#{self} cannot import '#{component_name}': #{e.message}."
154
+ # rubocop:enable Lint/RescueException
155
+ ensure
156
+ self
157
+ end
158
+
159
+ def add_item_to_tree(current_tree, new_item)
160
+ if Native(current_tree).class != Native::Object || new_item.length == 1
161
+ new_item.inject { |a, e| { e => a } }
162
+ else
163
+ Native(current_tree)[new_item.last] = add_item_to_tree(
164
+ Native(current_tree)[new_item.last], new_item[0..-2]
165
+ )
166
+ current_tree
167
+ end
168
+ end
169
+
170
+ def to_n
171
+ React::API.class_eval('@@component_classes')[self]
172
+ end
173
+ end
174
+ end
175
+ end