hyper-component 0.99.6 → 1.0.alpha1

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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -3
  3. data/Gemfile +4 -3
  4. data/Gemfile.lock +51 -36
  5. data/{misc/how-component-name-lookup-works.md → how-component-name-lookup-works.md} +1 -1
  6. data/hyper-component.gemspec +9 -8
  7. data/lib/hyper-component.rb +31 -43
  8. data/lib/hyperstack/component.rb +145 -0
  9. data/lib/hyperstack/component/auto-import.rb +44 -0
  10. data/lib/hyperstack/component/children.rb +40 -0
  11. data/lib/hyperstack/component/element.rb +129 -0
  12. data/lib/hyperstack/component/event.rb +78 -0
  13. data/lib/hyperstack/component/haml.rb +18 -0
  14. data/lib/hyperstack/component/isomorphic_helpers.rb +235 -0
  15. data/lib/hyperstack/component/jquery.rb +2 -0
  16. data/lib/hyperstack/component/native_library.rb +92 -0
  17. data/lib/hyperstack/component/react_api.rb +142 -0
  18. data/lib/hyperstack/component/server.rb +21 -0
  19. data/lib/hyperstack/component/version.rb +5 -0
  20. data/lib/hyperstack/ext/component/boolean.rb +14 -0
  21. data/lib/{react/ext/opal-jquery → hyperstack/ext/component}/element.rb +17 -12
  22. data/lib/{react/ext → hyperstack/ext/component}/hash.rb +0 -0
  23. data/lib/{react/to_key.rb → hyperstack/ext/component/number.rb} +0 -12
  24. data/lib/hyperstack/ext/component/object.rb +32 -0
  25. data/lib/{reactive-ruby → hyperstack/ext/component}/serializers.rb +0 -0
  26. data/lib/{react/ext → hyperstack/ext/component}/string.rb +0 -0
  27. data/lib/hyperstack/internal/component.rb +16 -0
  28. data/lib/hyperstack/internal/component/class_methods.rb +212 -0
  29. data/lib/hyperstack/internal/component/haml.rb +56 -0
  30. data/lib/hyperstack/internal/component/instance_methods.rb +92 -0
  31. data/lib/hyperstack/internal/component/props_wrapper.rb +125 -0
  32. data/lib/hyperstack/internal/component/rails.rb +11 -0
  33. data/lib/hyperstack/internal/component/rails/component_loader.rb +49 -0
  34. data/lib/hyperstack/internal/component/rails/component_mount.rb +52 -0
  35. data/lib/{reactive-ruby → hyperstack/internal/component}/rails/controller_helper.rb +0 -0
  36. data/lib/hyperstack/internal/component/rails/railtie.rb +24 -0
  37. data/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb +52 -0
  38. data/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb +52 -0
  39. data/lib/hyperstack/internal/component/react_wrapper.rb +308 -0
  40. data/lib/hyperstack/internal/component/rendering_context.rb +165 -0
  41. data/lib/hyperstack/internal/component/should_component_update.rb +101 -0
  42. data/lib/hyperstack/internal/component/tags.rb +109 -0
  43. data/lib/hyperstack/internal/component/top_level_rails_component.rb +83 -0
  44. data/lib/hyperstack/internal/component/validator.rb +149 -0
  45. data/lib/react/react-source.rb +2 -2
  46. data/unmounting-objects.md +78 -0
  47. metadata +73 -85
  48. data/DOCS.md +0 -1515
  49. data/LICENSE +0 -19
  50. data/README.md +0 -49
  51. data/lib/hyper-component/jquery.rb +0 -2
  52. data/lib/rails-helpers/top_level_rails_component.rb +0 -79
  53. data/lib/react/api.rb +0 -272
  54. data/lib/react/callbacks.rb +0 -42
  55. data/lib/react/children.rb +0 -38
  56. data/lib/react/component.rb +0 -189
  57. data/lib/react/component/api.rb +0 -70
  58. data/lib/react/component/base.rb +0 -13
  59. data/lib/react/component/class_methods.rb +0 -175
  60. data/lib/react/component/dsl_instance_methods.rb +0 -23
  61. data/lib/react/component/params.rb +0 -6
  62. data/lib/react/component/props_wrapper.rb +0 -90
  63. data/lib/react/component/should_component_update.rb +0 -99
  64. data/lib/react/component/tags.rb +0 -116
  65. data/lib/react/config.rb +0 -5
  66. data/lib/react/element.rb +0 -167
  67. data/lib/react/event.rb +0 -76
  68. data/lib/react/native_library.rb +0 -87
  69. data/lib/react/object.rb +0 -15
  70. data/lib/react/ref_callback.rb +0 -31
  71. data/lib/react/rendering_context.rb +0 -149
  72. data/lib/react/server.rb +0 -19
  73. data/lib/react/state_wrapper.rb +0 -23
  74. data/lib/react/test.rb +0 -16
  75. data/lib/react/test/dsl.rb +0 -17
  76. data/lib/react/test/matchers/render_html_matcher.rb +0 -56
  77. data/lib/react/test/rspec.rb +0 -15
  78. data/lib/react/test/session.rb +0 -37
  79. data/lib/react/test/utils.rb +0 -71
  80. data/lib/react/top_level.rb +0 -110
  81. data/lib/react/top_level_render.rb +0 -30
  82. data/lib/react/validator.rb +0 -132
  83. data/lib/reactive-ruby/component_loader.rb +0 -43
  84. data/lib/reactive-ruby/isomorphic_helpers.rb +0 -233
  85. data/lib/reactive-ruby/rails.rb +0 -8
  86. data/lib/reactive-ruby/rails/component_mount.rb +0 -48
  87. data/lib/reactive-ruby/rails/railtie.rb +0 -20
  88. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +0 -46
  89. data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +0 -46
  90. data/lib/reactive-ruby/version.rb +0 -5
  91. data/lib/reactrb/auto-import.rb +0 -27
  92. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +0 -3
  93. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +0 -5
  94. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +0 -2
  95. data/misc/generators/reactive_ruby/test_app/templates/boot.rb.erb +0 -6
  96. data/misc/generators/reactive_ruby/test_app/templates/script/rails +0 -5
  97. data/misc/generators/reactive_ruby/test_app/templates/test_application.rb.erb +0 -13
  98. data/misc/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +0 -11
  99. data/misc/generators/reactive_ruby/test_app/templates/views/components/todo.rb +0 -14
  100. data/misc/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  101. data/misc/generators/reactive_ruby/test_app/test_app_generator.rb +0 -121
  102. data/misc/hyperloop-logo-small-pink.png +0 -0
  103. data/misc/logo1.png +0 -0
  104. data/misc/logo2.png +0 -0
  105. data/misc/logo3.png +0 -0
  106. data/path_release_steps.md +0 -9
@@ -0,0 +1,101 @@
1
+ module Hyperstack
2
+ module Internal
3
+ module Component
4
+ #
5
+ # React assumes all components should update, unless a component explicitly overrides
6
+ # the shouldComponentUpdate method. Reactrb does an explicit check doing a shallow
7
+ # compare of params, and using a timestamp to determine if state has changed.
8
+
9
+ # If needed components can provide their own #needs_update? method which will be
10
+ # passed the next params and state opal hashes.
11
+
12
+ # Attached to these hashes is a #changed? method that returns whether the hash contains
13
+ # changes as calculated by the base mechanism. This way implementations of #needs_update?
14
+ # can use the base comparison mechanism as needed.
15
+
16
+ # For example
17
+ # def needs_update?(next_params, next_state)
18
+ # # use a special comparison method
19
+ # return false if next_state.changed? || next_params.changed?
20
+ # # do some other special checks
21
+ # end
22
+
23
+ # Note that beginning in 0.9 we will use standard ruby compare on all params further reducing
24
+ # the need for needs_update?
25
+ #
26
+ module ShouldComponentUpdate
27
+ def should_component_update?(next_props, next_state)
28
+ observing do
29
+ # rubocop:disable Style/DoubleNegation # we must return true/false to js land
30
+ if respond_to?(:needs_update?)
31
+ !!call_needs_update(next_props, next_state)
32
+ else
33
+ (props_changed?(next_props) || native_state_changed?(next_state))
34
+ end
35
+ # rubocop:enable Style/DoubleNegation
36
+ end
37
+ end
38
+
39
+ # create opal hashes for next params and state, and attach
40
+ # the changed? method to each hash
41
+
42
+ def call_needs_update(next_params, next_state)
43
+ component = self
44
+ next_params.define_singleton_method(:changed?) do
45
+ component.props_changed?(self)
46
+ end
47
+ next_state.define_singleton_method(:changed?) do
48
+ component.native_state_changed?(next_state)
49
+ end
50
+ needs_update?(next_params, next_state)
51
+ end
52
+
53
+ # Whenever state changes, reactrb updates a timestamp on the state object.
54
+ # We can rapidly check for state changes comparing the incoming state time_stamp
55
+ # with the current time stamp.
56
+
57
+ # we receive a Opal Ruby Hash here, always, so the Hash is either empty or filled
58
+ # Hash is converted to native object
59
+ # if the Hash was empty, the Object has no keys
60
+
61
+ # Different versions of react treat empty state differently, so we first
62
+ # convert anything that looks like an empty state to "false" for consistency.
63
+
64
+ # Then we test if one state is empty and the other is not, then we return false.
65
+ # Then we test if both states are empty we return true.
66
+ # If either state does not have a time stamp then we have to assume a change.
67
+ # Otherwise we check time stamps
68
+
69
+ # rubocop:disable Metrics/MethodLength # for effeciency we want this to be one method
70
+ def native_state_changed?(next_state_hash)
71
+ # next_state = next_state_hash.to_n
72
+ # %x{
73
+ # var current_state = #{@__hyperstack_component_native}.state
74
+ # var normalized_next_state =
75
+ # !next_state || Object.keys(next_state).length === 0 ? false : next_state
76
+ # var normalized_current_state =
77
+ # !current_state || Object.keys(current_state).length === 0 ? false : current_state
78
+ # if (!normalized_current_state != !normalized_next_state) return(true)
79
+ # if (!normalized_current_state && !normalized_next_state) return(false)
80
+ # if (!normalized_current_state['***_state_updated_at-***'] &&
81
+ # !normalized_next_state['***_state_updated_at-***']) return(false)
82
+ # if (!normalized_current_state['***_state_updated_at-***'] ||
83
+ # !normalized_next_state['***_state_updated_at-***']) return(true)
84
+ # return (normalized_current_state['***_state_updated_at-***'] !=
85
+ # normalized_next_state['***_state_updated_at-***'])
86
+ # }
87
+ state_hash = Hash.new(`#{@__hyperstack_component_native}.state`)
88
+ next_state_hash != state_hash
89
+ end
90
+ # rubocop:enable Metrics/MethodLength
91
+
92
+ # Do a shallow compare on the two hashes. Starting in 0.9 we will do a deep compare. ???
93
+
94
+ def props_changed?(next_props)
95
+ props = Hash.new(`#{@__hyperstack_component_native}.props`)
96
+ next_props != props
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,109 @@
1
+ module Hyperstack
2
+ module Internal
3
+ module Component # contains the name of all HTML tags, and the mechanism to register a component
4
+ # class as a new tag
5
+ module Tags
6
+ HTML_TAGS = %w(a abbr address area article aside audio b base bdi bdo big blockquote body br
7
+ button canvas caption cite code col colgroup data datalist dd del details dfn
8
+ dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5
9
+ h6 head header hr html i iframe img input ins kbd keygen label legend li link
10
+ main map mark menu menuitem meta meter nav noscript object ol optgroup option
11
+ output p param picture pre progress q rp rt ruby s samp script section select
12
+ small source span strong style sub summary sup table tbody td textarea tfoot th
13
+ thead time title tr track u ul var video wbr) +
14
+ # The SVG Tags
15
+ %w(circle clipPath defs ellipse g line linearGradient mask path pattern polygon polyline
16
+ radialGradient rect stop svg text tspan)
17
+
18
+ # the present method is retained as a legacy behavior
19
+ # def present(component, *params, &children)
20
+ # RenderingContext.render(component, *params, &children)
21
+ # end
22
+
23
+ # define each predefined tag (upcase) as an instance method and a constant
24
+ # deprecated: define each predefined tag (downcase) as the alias of the instance method
25
+
26
+ HTML_TAGS.each do |tag|
27
+
28
+ define_method(tag.upcase) do |*params, &children|
29
+ RenderingContext.render(tag, *params, &children)
30
+ end
31
+
32
+ const_set tag.upcase, tag
33
+ end
34
+
35
+ # this is used for haml style (i.e. DIV.foo.bar) class tags which is deprecated
36
+ def self.html_tag_class_for(tag)
37
+ downcased_tag = tag.downcase
38
+ if tag =~ /^[A-Z]+$/ && HTML_TAGS.include?(downcased_tag)
39
+ Object.const_set tag, ReactWrapper.create_element(downcased_tag)
40
+ end
41
+ end
42
+
43
+ # use method_missing to look up component names in the form of "Foo(..)"
44
+ # where there is no preceeding scope.
45
+
46
+ def method_missing(name, *params, &children)
47
+ component = find_component(name)
48
+ return RenderingContext.render(component, *params, &children) if component
49
+ super
50
+ end
51
+
52
+ # install methods with the same name as the component in the parent class/module
53
+ # thus component names in the form Foo::Bar(...) will work
54
+
55
+ class << self
56
+ def included(component)
57
+ name, parent = find_name_and_parent(component)
58
+ tag_names_module = Module.new do
59
+ define_method name do |*params, &children|
60
+ RenderingContext.render(component, *params, &children)
61
+ end
62
+ # handle deprecated _as_node style
63
+ define_method "#{name}_as_node" do |*params, &children|
64
+ RenderingContext.build_only(component, *params, &children)
65
+ end
66
+ end
67
+ parent.extend(tag_names_module)
68
+ end
69
+
70
+ private
71
+
72
+ def find_name_and_parent(component)
73
+ split_name = component.name && component.name.split('::')
74
+ if split_name && split_name.length > 1
75
+ [split_name.last, split_name.inject([Module]) { |a, e| a + [a.last.const_get(e)] }[-2]]
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def find_component(name)
83
+ component = lookup_const(name)
84
+ if component && !component.method_defined?(:render)
85
+ raise "#{name} does not appear to be a react component."
86
+ end
87
+ component || Object._reactrb_import_component_class(name)
88
+ end
89
+
90
+ def lookup_const(name)
91
+ return nil unless name =~ /^[A-Z]/
92
+ scopes = self.class.name.to_s.split('::').inject([Module]) do |nesting, next_const|
93
+ nesting + [nesting.last.const_get(next_const)]
94
+ end.reverse
95
+ scope = scopes.detect { |s| s.const_defined?(name) }
96
+ scope.const_get(name) if scope
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ unless Object.respond_to? :_reactrb_import_component_class
104
+ class Object
105
+ def self._reactrb_import_component_class(_name)
106
+ nil
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,83 @@
1
+ module Hyperstack
2
+ module Internal
3
+ module Component
4
+ class TopLevelRailsComponent
5
+ include Hyperstack::Component
6
+
7
+ def self.search_path
8
+ @search_path ||= [Object]
9
+ end
10
+
11
+ export_component
12
+
13
+ param :component_name
14
+ param :controller
15
+ param :render_params
16
+
17
+ backtrace :off
18
+
19
+ def render
20
+ top_level_render
21
+ end
22
+
23
+ def top_level_render
24
+ paths_searched = []
25
+ component = nil
26
+ if @ComponentName.start_with?('::')
27
+ # if absolute path of component is given, look it up and fail if not found
28
+ paths_searched << @ComponentName
29
+ component = begin
30
+ Object.const_get(@ComponentName)
31
+ rescue NameError
32
+ nil
33
+ end
34
+ else
35
+ # if relative path is given, look it up like this
36
+ # 1) we check each path + controller-name + component-name
37
+ # 2) if we can't find it there we check each path + component-name
38
+ # if we can't find it we just try const_get
39
+ # so (assuming controller name is Home)
40
+ # ::Foo::Bar will only resolve to some component named ::Foo::Bar
41
+ # but Foo::Bar will check (in this order) ::Home::Foo::Bar, ::Components::Home::Foo::Bar, ::Foo::Bar, ::Components::Foo::Bar
42
+ self.class.search_path.each do |scope|
43
+ paths_searched << "#{scope.name}::#{@Controller}::#{@ComponentName}"
44
+ component = begin
45
+ scope.const_get(@Controller, false).const_get(@ComponentName, false)
46
+ rescue NameError
47
+ nil
48
+ end
49
+ break if component != nil
50
+ end
51
+ unless component
52
+ self.class.search_path.each do |scope|
53
+ paths_searched << "#{scope.name}::#{@ComponentName}"
54
+ component = begin
55
+ scope.const_get(@ComponentName, false)
56
+ rescue NameError
57
+ nil
58
+ end
59
+ break if component != nil
60
+ end
61
+ end
62
+ end
63
+ return RenderingContext.render(component, @RenderParams) if component && component.method_defined?(:render)
64
+ raise "Could not find component class '#{@ComponentName}' for @Controller '#{@Controller}' in any component directory. Tried [#{paths_searched.join(", ")}]"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ class Module
72
+ def add_to_react_search_path(replace_search_path = nil)
73
+ if replace_search_path
74
+ Hyperstack::Internal::Component::TopLevelRailsComponent.search_path = [self]
75
+ elsif !Hyperstack::Internal::Component::TopLevelRailsComponent.search_path.include? self
76
+ Hyperstack::Internal::Component::TopLevelRailsComponent.search_path << self
77
+ end
78
+ end
79
+ end
80
+
81
+ module Components
82
+ add_to_react_search_path
83
+ end
@@ -0,0 +1,149 @@
1
+ module Hyperstack
2
+ module Internal
3
+ module Component
4
+ class Validator
5
+
6
+ attr_accessor :errors
7
+ attr_reader :props_wrapper
8
+ private :errors, :props_wrapper
9
+
10
+ def copy(new_props_wrapper)
11
+ Validator.new(new_props_wrapper).tap do |c|
12
+ %i[@allow_undefined_props @rules @errors].each do |var|
13
+ c.instance_variable_set(var, instance_variable_get(var).dup)
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(props_wrapper = Class.new(PropsWrapper))
19
+ @props_wrapper = props_wrapper
20
+ end
21
+
22
+ def self.build(&block)
23
+ new.build(&block)
24
+ end
25
+
26
+ def build(&block)
27
+ instance_eval(&block)
28
+ self
29
+ end
30
+
31
+ def requires(name, options = {})
32
+ options[:required] = true
33
+ define_rule(name, options)
34
+ end
35
+
36
+ def optional(name, options = {})
37
+ options[:required] = false
38
+ define_rule(name, options)
39
+ end
40
+
41
+ def event(name)
42
+ rules[name] = coerce_native_hash_values(default: nil, type: Proc, allow_nil: true)
43
+ end
44
+
45
+ def all_other_params(name)
46
+ @allow_undefined_props = true
47
+ props_wrapper.define_all_others(name) { |props| props.reject { |name, value| rules[name] } }
48
+ end
49
+
50
+ def validate(props)
51
+ self.errors = []
52
+ validate_undefined(props) unless allow_undefined_props?
53
+ props = coerce_native_hash_values(defined_props(props))
54
+ validate_required(props)
55
+ props.each do |name, value|
56
+ validate_types(name, value)
57
+ validate_allowed(name, value)
58
+ end
59
+ errors
60
+ end
61
+
62
+ def default_props
63
+ rules
64
+ .select {|key, value| value.keys.include?("default") }
65
+ .inject({}) {|memo, (k,v)| memo[k] = v[:default]; memo}
66
+ end
67
+
68
+ private
69
+
70
+ def defined_props(props)
71
+ props.select { |name| rules.keys.include?(name) }
72
+ end
73
+
74
+ def allow_undefined_props?
75
+ !!@allow_undefined_props
76
+ end
77
+
78
+ def rules
79
+ @rules ||= { children: { required: false } }
80
+ end
81
+
82
+ def define_rule(name, options = {})
83
+ rules[name] = coerce_native_hash_values(options)
84
+ props_wrapper.define_param(name, options[:type], options[:alias])
85
+ end
86
+
87
+ def errors
88
+ @errors ||= []
89
+ end
90
+
91
+ def validate_types(prop_name, value)
92
+ return unless klass = rules[prop_name][:type]
93
+ if !klass.is_a?(Array)
94
+ allow_nil = !!rules[prop_name][:allow_nil]
95
+ type_check("`#{prop_name}`", value, klass, allow_nil)
96
+ elsif klass.length > 0
97
+ validate_value_array(prop_name, value)
98
+ else
99
+ allow_nil = !!rules[prop_name][:allow_nil]
100
+ type_check("`#{prop_name}`", value, Array, allow_nil)
101
+ end
102
+ end
103
+
104
+ def type_check(prop_name, value, klass, allow_nil)
105
+ return if allow_nil && value.nil?
106
+ return if value.is_a?(klass)
107
+ return if klass.respond_to?(:_react_param_conversion) &&
108
+ klass._react_param_conversion(value, :validate_only)
109
+ errors << "Provided prop #{prop_name} could not be converted to #{klass}"
110
+ end
111
+
112
+ def validate_allowed(prop_name, value)
113
+ return unless values = rules[prop_name][:values]
114
+ return if values.include?(value)
115
+ errors << "Value `#{value}` for prop `#{prop_name}` is not an allowed value"
116
+ end
117
+
118
+ def validate_required(props)
119
+ (rules.keys - props.keys).each do |name|
120
+ next unless rules[name][:required]
121
+ errors << "Required prop `#{name}` was not specified"
122
+ end
123
+ end
124
+
125
+ def validate_undefined(props)
126
+ (props.keys - rules.keys).each do |prop_name|
127
+ errors << "Provided prop `#{prop_name}` not specified in spec"
128
+ end
129
+ end
130
+
131
+ def validate_value_array(name, value)
132
+ klass = rules[name][:type]
133
+ allow_nil = !!rules[name][:allow_nil]
134
+ value.each_with_index do |item, index|
135
+ type_check("`#{name}`[#{index}]", Native(item), klass[0], allow_nil)
136
+ end
137
+ rescue NoMethodError
138
+ errors << "Provided prop `#{name}` was not an Array"
139
+ end
140
+
141
+ def coerce_native_hash_values(hash)
142
+ hash.each do |key, value|
143
+ hash[key] = Native(value)
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -9,9 +9,9 @@ if RUBY_ENGINE == 'opal'
9
9
  require 'react.js'
10
10
  require "react-server.js"
11
11
  else
12
- require "react/config"
12
+ require "hyperstack/internal/component"
13
13
  require "react/rails/asset_variant"
14
- variant = Hyperloop.env.production? ? 'production' : 'development'
14
+ variant = Hyperstack.env.production? ? 'production' : 'development'
15
15
  react_directory = React::Rails::AssetVariant.new({environment: variant}).react_directory
16
16
  Opal.append_path react_directory.untaint
17
17
  end