reactive_component 0.1.0 → 0.6.2
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 +4 -4
- data/CHANGELOG.md +150 -0
- data/app/channels/reactive_component/channel.rb +10 -10
- data/app/controllers/reactive_component/actions_controller.rb +2 -2
- data/app/javascript/reactive_component/controllers/reactive_renderer_controller.js +25 -3
- data/app/javascript/reactive_component/lib/reactive_renderer_utils.js +34 -0
- data/config/importmap.rb +2 -2
- data/config/routes.rb +1 -1
- data/lib/reactive_component/broadcastable.rb +41 -0
- data/lib/reactive_component/compiler.rb +110 -52
- data/lib/reactive_component/data_evaluator.rb +58 -30
- data/lib/reactive_component/engine.rb +10 -4
- data/lib/reactive_component/erubi.rb +30 -0
- data/lib/reactive_component/transpiler.rb +586 -0
- data/lib/reactive_component/version.rb +1 -1
- data/lib/reactive_component/wrapper.rb +7 -12
- data/lib/reactive_component.rb +176 -43
- metadata +35 -25
- data/lib/reactive_component/erb_extractor.rb +0 -610
data/lib/reactive_component.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'active_support/concern'
|
|
4
4
|
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
10
|
-
require_relative
|
|
5
|
+
require_relative 'reactive_component/version'
|
|
6
|
+
require_relative 'reactive_component/compiler'
|
|
7
|
+
require_relative 'reactive_component/data_evaluator'
|
|
8
|
+
require_relative 'reactive_component/wrapper'
|
|
9
|
+
require_relative 'reactive_component/broadcastable'
|
|
10
|
+
require_relative 'reactive_component/engine' if defined?(Rails::Engine)
|
|
11
11
|
|
|
12
12
|
module ReactiveComponent
|
|
13
13
|
extend ActiveSupport::Concern
|
|
@@ -17,15 +17,22 @@ module ReactiveComponent
|
|
|
17
17
|
|
|
18
18
|
class Error < StandardError; end
|
|
19
19
|
|
|
20
|
+
# A template the compiler cannot make reactive — e.g. a loop variable read in
|
|
21
|
+
# a way the client could never satisfy. Raised on first compile (lazily, on
|
|
22
|
+
# first render or from a test that touches compiled_template_js), never in a
|
|
23
|
+
# browser.
|
|
24
|
+
class CompileError < Error; end
|
|
25
|
+
|
|
20
26
|
included do
|
|
21
27
|
class_attribute :_live_model_attr, instance_writer: false
|
|
22
28
|
class_attribute :_live_model_class_name, instance_writer: false
|
|
23
29
|
class_attribute :_live_actions, instance_writer: false, default: {}
|
|
24
30
|
class_attribute :_broadcast_config, instance_writer: false
|
|
25
31
|
class_attribute :_client_state_fields, instance_writer: false, default: {}
|
|
32
|
+
class_attribute :_subscribed_events, instance_writer: false, default: %i[create update destroy]
|
|
26
33
|
end
|
|
27
34
|
|
|
28
|
-
def render_in(view_context, &
|
|
35
|
+
def render_in(view_context, &)
|
|
29
36
|
inner_html = super
|
|
30
37
|
return inner_html unless self.class._live_model_attr
|
|
31
38
|
return inner_html if @_skip_live_wrapper
|
|
@@ -36,26 +43,126 @@ module ReactiveComponent
|
|
|
36
43
|
stream = ReactiveComponent::Wrapper.find_stream_for(self.class, record)
|
|
37
44
|
|
|
38
45
|
client_state = if self.class._client_state_fields.any?
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
kwargs = {}
|
|
47
|
+
self.class._client_state_fields.each_key do |name|
|
|
48
|
+
val = instance_variable_get(:"@#{name}")
|
|
49
|
+
kwargs[name] = val unless val.nil?
|
|
50
|
+
end
|
|
51
|
+
self.class.client_state_values(**kwargs)
|
|
52
|
+
end
|
|
46
53
|
|
|
47
54
|
extra_opts = respond_to?(:live_wrapper_options, true) ? live_wrapper_options : {}
|
|
48
55
|
|
|
49
|
-
wrapped = ReactiveComponent::Wrapper.wrap(self.class, record, inner_html, stream: stream, client_state: client_state,
|
|
56
|
+
wrapped = ReactiveComponent::Wrapper.wrap(self.class, record, inner_html, stream: stream, client_state: client_state,
|
|
57
|
+
**extra_opts)
|
|
50
58
|
|
|
51
59
|
template_script = self.class.template_script_tag(view_context)
|
|
52
60
|
template_script ? (template_script + wrapped).html_safe : wrapped
|
|
53
61
|
end
|
|
54
62
|
|
|
63
|
+
# Raised when an extracted template expression returns something that is
|
|
64
|
+
# not a primitive — e.g. a full ActiveRecord record, a custom object, or
|
|
65
|
+
# a Date/Time. Broadcast payloads are JSON-serialized and visible to every
|
|
66
|
+
# connected client, so letting a record through would leak every column
|
|
67
|
+
# (including `password_digest` and tokens). Rather than silently coerce,
|
|
68
|
+
# we raise so the developer fixes the template.
|
|
69
|
+
class UnsafeBroadcastValueError < StandardError; end
|
|
70
|
+
|
|
71
|
+
# Types that are safe to ship verbatim in a broadcast payload. Everything
|
|
72
|
+
# else must be converted in the template (e.g. `@user.name` instead of
|
|
73
|
+
# `@user`, `@date.iso8601` instead of `@date`).
|
|
74
|
+
SAFE_PRIMITIVE_TYPES = [NilClass, TrueClass, FalseClass, Integer, Float, String].freeze
|
|
75
|
+
|
|
76
|
+
def self.sanitize_for_broadcast(value, source: nil)
|
|
77
|
+
return value if value.nil? || value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
78
|
+
return value if value.is_a?(Integer) || value.is_a?(Float) || value.is_a?(String)
|
|
79
|
+
return value.to_s if value.is_a?(Symbol)
|
|
80
|
+
return value.map { |v| sanitize_for_broadcast(v, source: source) } if value.is_a?(Array)
|
|
81
|
+
|
|
82
|
+
if value.is_a?(Hash)
|
|
83
|
+
return value.each_with_object({}) do |(k, v), h|
|
|
84
|
+
key = k.is_a?(Symbol) ? k.to_s : k
|
|
85
|
+
raise_unsafe!(k, source, context: 'Hash key') unless key.is_a?(String) || key.is_a?(Integer)
|
|
86
|
+
h[key] = sanitize_for_broadcast(v, source: source)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
raise_unsafe!(value, source)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.raise_unsafe!(value, source, context: 'Extracted expression')
|
|
94
|
+
label = source ? "`#{source}`" : 'an extracted expression'
|
|
95
|
+
hint =
|
|
96
|
+
if defined?(ActiveRecord::Base) && value.is_a?(ActiveRecord::Base)
|
|
97
|
+
"Narrow the ERB to a specific column (e.g. `#{source || '@record'}.name`) — " \
|
|
98
|
+
'shipping the record would leak every column over ActionCable.'
|
|
99
|
+
elsif value.is_a?(Time) || value.is_a?(Date) || (defined?(ActiveSupport::TimeWithZone) && value.is_a?(ActiveSupport::TimeWithZone))
|
|
100
|
+
"Call a formatter in the template (e.g. `#{source}.iso8601` or `time_ago_in_words(#{source})`)."
|
|
101
|
+
else
|
|
102
|
+
'Convert the value to a primitive in the template ' \
|
|
103
|
+
'(String, Integer, Float, Boolean, nil, Symbol, or Array/Hash of those) before outputting it.'
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
raise UnsafeBroadcastValueError,
|
|
107
|
+
"[ReactiveComponent] #{context} #{label} returned a #{value.class.name}, which is not safe to broadcast. #{hint}"
|
|
108
|
+
end
|
|
109
|
+
private_class_method :raise_unsafe!
|
|
110
|
+
|
|
111
|
+
def self.broadcast_for(component_class, record, action:)
|
|
112
|
+
return unless component_class._subscribed_events.include?(action)
|
|
113
|
+
|
|
114
|
+
config = component_class._broadcast_config
|
|
115
|
+
stream = if config&.dig(:stream)
|
|
116
|
+
s = config[:stream]
|
|
117
|
+
s.is_a?(Proc) ? s.call(record) : s
|
|
118
|
+
else
|
|
119
|
+
record
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
case action
|
|
123
|
+
when :update
|
|
124
|
+
Channel.broadcast_data(stream, action: :update, data: component_class.build_data(record))
|
|
125
|
+
when :destroy
|
|
126
|
+
Channel.broadcast_data(stream, action: :destroy, data: {
|
|
127
|
+
'id' => record.id, 'dom_id' => component_class.dom_id_for(record)
|
|
128
|
+
})
|
|
129
|
+
when :create
|
|
130
|
+
target = config&.dig(:prepend_target)
|
|
131
|
+
return unless target
|
|
132
|
+
return unless (renderer = ReactiveComponent.renderer)
|
|
133
|
+
|
|
134
|
+
html = renderer.render(component_class.new(component_class.live_model_attr => record), layout: false)
|
|
135
|
+
Turbo::StreamsChannel.broadcast_prepend_to(*Array(stream), target: target, html: html)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
55
139
|
class_methods do
|
|
56
|
-
def subscribes_to(attr_name, class_name: nil)
|
|
140
|
+
def subscribes_to(attr_name, class_name: nil, only: %i[create update destroy])
|
|
57
141
|
self._live_model_attr = attr_name.to_sym
|
|
58
142
|
self._live_model_class_name = class_name || attr_name.to_s.classify
|
|
143
|
+
self._subscribed_events = Array(only).map(&:to_sym)
|
|
144
|
+
|
|
145
|
+
component_class = self
|
|
146
|
+
|
|
147
|
+
# Wire up the model to broadcast when it changes.
|
|
148
|
+
# We need to handle two cases:
|
|
149
|
+
# 1. ActiveRecord isn't loaded yet - use on_load hook
|
|
150
|
+
# 2. ActiveRecord is already loaded - wire up immediately
|
|
151
|
+
wire_up_model = lambda do
|
|
152
|
+
model_class = component_class.live_model_class
|
|
153
|
+
return unless model_class
|
|
154
|
+
|
|
155
|
+
model_class.include(ReactiveComponent::Broadcastable)
|
|
156
|
+
model_class.register_reactive_component(component_class)
|
|
157
|
+
rescue NameError
|
|
158
|
+
# model class not yet defined — wiring skipped
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
if defined?(ActiveRecord::Base)
|
|
162
|
+
wire_up_model.call
|
|
163
|
+
else
|
|
164
|
+
ActiveSupport.on_load(:active_record) { wire_up_model.call }
|
|
165
|
+
end
|
|
59
166
|
end
|
|
60
167
|
|
|
61
168
|
def live_model_class
|
|
@@ -125,10 +232,10 @@ module ReactiveComponent
|
|
|
125
232
|
|
|
126
233
|
def encoded_template
|
|
127
234
|
@encoded_template ||= if ReactiveComponent.debug
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
235
|
+
compiled_template_js
|
|
236
|
+
else
|
|
237
|
+
Base64.strict_encode64(compiled_template_js)
|
|
238
|
+
end
|
|
132
239
|
end
|
|
133
240
|
|
|
134
241
|
def template_element_id
|
|
@@ -136,7 +243,7 @@ module ReactiveComponent
|
|
|
136
243
|
end
|
|
137
244
|
|
|
138
245
|
def template_script_tag(view_context)
|
|
139
|
-
emitted =
|
|
246
|
+
emitted = view_context.instance_variable_get(:@_reactive_component_templates) || Set.new
|
|
140
247
|
return nil if emitted.include?(name)
|
|
141
248
|
|
|
142
249
|
emitted.add(name)
|
|
@@ -144,12 +251,26 @@ module ReactiveComponent
|
|
|
144
251
|
%(<script type="text/x-template" id="#{template_element_id}">#{encoded_template}</script>).html_safe
|
|
145
252
|
end
|
|
146
253
|
|
|
254
|
+
# The wrapper id, and the `dom_id` the client matches broadcasts against.
|
|
255
|
+
#
|
|
256
|
+
# Always prefixed with the component, never the bare `dom_id(record)`: two
|
|
257
|
+
# components rendering the same record would otherwise share one id, and
|
|
258
|
+
# since routing is by id each would render the OTHER's payload — a
|
|
259
|
+
# TypeError deep in the compiled template the moment their shapes differ,
|
|
260
|
+
# plus duplicate ids in the page. `dom_id_prefix` still overrides the
|
|
261
|
+
# default when a shorter or hand-picked id is wanted.
|
|
147
262
|
def dom_id_for(record)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
263
|
+
ActionView::RecordIdentifier.dom_id(record, dom_id_prefix.presence || default_dom_id_prefix)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def dom_id_prefix = nil
|
|
267
|
+
|
|
268
|
+
# MessageRowComponent → message_row; Admin::MessageRowComponent → admin_message_row.
|
|
269
|
+
# An anonymous class (tests) has no name to distinguish it by.
|
|
270
|
+
def default_dom_id_prefix
|
|
271
|
+
return 'component' unless name
|
|
272
|
+
|
|
273
|
+
name.underscore.tr('/', '_').delete_suffix('_component')
|
|
153
274
|
end
|
|
154
275
|
|
|
155
276
|
def expression_field_map
|
|
@@ -163,10 +284,10 @@ module ReactiveComponent
|
|
|
163
284
|
|
|
164
285
|
compiled_data[:expressions].each do |var_name, ruby_source|
|
|
165
286
|
data[var_name] = if collection_computed.key?(var_name)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
287
|
+
evaluator.evaluate_collection(ruby_source, collection_computed[var_name])
|
|
288
|
+
else
|
|
289
|
+
evaluator.evaluate(ruby_source)
|
|
290
|
+
end
|
|
170
291
|
end
|
|
171
292
|
compiled_data[:simple_ivars].each do |ivar_name|
|
|
172
293
|
data[ivar_name] = kwargs[ivar_name.to_sym] if kwargs.key?(ivar_name.to_sym)
|
|
@@ -180,32 +301,44 @@ module ReactiveComponent
|
|
|
180
301
|
collection_computed = compiled_data[:collection_computed] || {}
|
|
181
302
|
|
|
182
303
|
compiled_data[:expressions].each do |var_name, ruby_source|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
304
|
+
value = if collection_computed.key?(var_name)
|
|
305
|
+
evaluator.evaluate_collection(ruby_source, collection_computed[var_name])
|
|
306
|
+
else
|
|
307
|
+
evaluator.evaluate(ruby_source)
|
|
308
|
+
end
|
|
309
|
+
data[var_name] = ReactiveComponent.sanitize_for_broadcast(value, source: ruby_source)
|
|
188
310
|
end
|
|
189
311
|
|
|
190
312
|
compiled_data[:simple_ivars].each do |ivar_name|
|
|
191
|
-
|
|
313
|
+
# The live-model ivar itself (e.g. `@message` when `subscribes_to :message`)
|
|
314
|
+
# is the subscription key, not a payload field. It's only in the ivar list
|
|
315
|
+
# because extract_ivar_names sees `@message.subject` etc. — the emitter never
|
|
316
|
+
# emits the bare name in the destructure for extracted chains, so don't
|
|
317
|
+
# ship it and don't sanitize-raise on it.
|
|
318
|
+
next if live_model_attr && ivar_name == live_model_attr.to_s
|
|
319
|
+
|
|
320
|
+
value = kwargs.key?(ivar_name.to_sym) ? kwargs[ivar_name.to_sym] : evaluator.evaluate("@#{ivar_name}")
|
|
321
|
+
data[ivar_name] = ReactiveComponent.sanitize_for_broadcast(value, source: "@#{ivar_name}")
|
|
192
322
|
end
|
|
193
323
|
|
|
194
324
|
(compiled_data[:nested_components] || {}).each do |key, info|
|
|
195
325
|
klass = info[:class_name].constantize
|
|
196
326
|
kwargs_values = {}
|
|
197
327
|
info[:kwargs].each do |kwarg_name, ruby_source|
|
|
328
|
+
# Nested-component kwargs keep full objects — the child evaluator
|
|
329
|
+
# needs them to compute its own extracted fields. Only the fields
|
|
330
|
+
# that actually ship to the client go through sanitize_for_broadcast.
|
|
198
331
|
kwargs_values[kwarg_name.to_sym] = evaluator.evaluate(ruby_source)
|
|
199
332
|
end
|
|
200
333
|
data[key] = if klass.respond_to?(:build_data_for_nested)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
334
|
+
klass.build_data_for_nested(**kwargs_values)
|
|
335
|
+
else
|
|
336
|
+
ReactiveComponent::Compiler.build_data_for_nested(klass, **kwargs_values)
|
|
337
|
+
end
|
|
205
338
|
end
|
|
206
339
|
|
|
207
|
-
data[
|
|
208
|
-
data[
|
|
340
|
+
data['id'] = record.id
|
|
341
|
+
data['dom_id'] = dom_id_for(record)
|
|
209
342
|
data
|
|
210
343
|
end
|
|
211
344
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: reactive_component
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Przemyslaw Lusar
|
|
@@ -10,81 +10,87 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: erubi
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '1.11'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '1.11'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: prism
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '1.0'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
39
|
+
version: '1.0'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
41
|
+
name: rails
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
46
|
+
version: '7.1'
|
|
47
|
+
- - "<"
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '9'
|
|
47
50
|
type: :runtime
|
|
48
51
|
prerelease: false
|
|
49
52
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
53
|
requirements:
|
|
51
|
-
- - "
|
|
54
|
+
- - ">="
|
|
52
55
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
56
|
+
version: '7.1'
|
|
57
|
+
- - "<"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '9'
|
|
54
60
|
- !ruby/object:Gem::Dependency
|
|
55
|
-
name:
|
|
61
|
+
name: turbo-rails
|
|
56
62
|
requirement: !ruby/object:Gem::Requirement
|
|
57
63
|
requirements:
|
|
58
64
|
- - "~>"
|
|
59
65
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '
|
|
66
|
+
version: '2.0'
|
|
61
67
|
type: :runtime
|
|
62
68
|
prerelease: false
|
|
63
69
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
70
|
requirements:
|
|
65
71
|
- - "~>"
|
|
66
72
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '
|
|
73
|
+
version: '2.0'
|
|
68
74
|
- !ruby/object:Gem::Dependency
|
|
69
|
-
name:
|
|
75
|
+
name: view_component
|
|
70
76
|
requirement: !ruby/object:Gem::Requirement
|
|
71
77
|
requirements:
|
|
72
78
|
- - ">="
|
|
73
79
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '
|
|
80
|
+
version: '3.0'
|
|
75
81
|
- - "<"
|
|
76
82
|
- !ruby/object:Gem::Version
|
|
77
|
-
version: '
|
|
83
|
+
version: '5'
|
|
78
84
|
type: :runtime
|
|
79
85
|
prerelease: false
|
|
80
86
|
version_requirements: !ruby/object:Gem::Requirement
|
|
81
87
|
requirements:
|
|
82
88
|
- - ">="
|
|
83
89
|
- !ruby/object:Gem::Version
|
|
84
|
-
version: '
|
|
90
|
+
version: '3.0'
|
|
85
91
|
- - "<"
|
|
86
92
|
- !ruby/object:Gem::Version
|
|
87
|
-
version: '
|
|
93
|
+
version: '5'
|
|
88
94
|
description: Build reactive, real-time UI components that automatically re-render
|
|
89
95
|
server-side when subscribed models change. Uses ViewComponent, Turbo Streams, and
|
|
90
96
|
ActionCable to keep your UI in sync without writing custom JavaScript.
|
|
@@ -103,19 +109,23 @@ files:
|
|
|
103
109
|
- config/importmap.rb
|
|
104
110
|
- config/routes.rb
|
|
105
111
|
- lib/reactive_component.rb
|
|
112
|
+
- lib/reactive_component/broadcastable.rb
|
|
106
113
|
- lib/reactive_component/compiler.rb
|
|
107
114
|
- lib/reactive_component/data_evaluator.rb
|
|
108
115
|
- lib/reactive_component/engine.rb
|
|
109
|
-
- lib/reactive_component/
|
|
116
|
+
- lib/reactive_component/erubi.rb
|
|
117
|
+
- lib/reactive_component/transpiler.rb
|
|
110
118
|
- lib/reactive_component/version.rb
|
|
111
119
|
- lib/reactive_component/wrapper.rb
|
|
112
|
-
homepage: https://github.
|
|
120
|
+
homepage: https://lluzak.github.io/reactive_component/
|
|
113
121
|
licenses:
|
|
114
122
|
- MIT
|
|
115
123
|
metadata:
|
|
116
|
-
homepage_uri: https://github.
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
homepage_uri: https://lluzak.github.io/reactive_component/
|
|
125
|
+
documentation_uri: https://lluzak.github.io/reactive_component/
|
|
126
|
+
source_code_uri: https://github.com/lluzak/reactive_component
|
|
127
|
+
changelog_uri: https://github.com/lluzak/reactive_component/blob/main/CHANGELOG.md
|
|
128
|
+
bug_tracker_uri: https://github.com/lluzak/reactive_component/issues
|
|
119
129
|
rubygems_mfa_required: 'true'
|
|
120
130
|
rdoc_options: []
|
|
121
131
|
require_paths:
|