hyper-component 0.99.6 → 1.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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