hot_module 1.0.0.alpha10 → 1.0.0.alpha12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84445fa8f0ceb1c241dc1497597ff16665383a4bcea444d41569ba673cf51e84
4
- data.tar.gz: 8416c036a600a2655c1e1883238d49b9cfefe187b35be5e93f7aa4508c364e15
3
+ metadata.gz: 51da117a031a9b9ad3c16a35b4b5276928874e742bf72e90bc9e1089f4b856a1
4
+ data.tar.gz: b1bb49f906b3dbc82671ec7a4850baac1899c93ce3237cc88c8bb3c69dff75f3
5
5
  SHA512:
6
- metadata.gz: 6285f80875501f2b04b28f10bfdf7f9145a4a5a6843a3f01ed3b3a2c0848d9584b829e1ee5b0048c41d70dde248aa8f506be25155fae1a94472c57c46c77669d
7
- data.tar.gz: ca42664335930b9468bdcd0d2e09620cd5b3b81cd2e3dfbc1fd2bd256cabe12515796f7288fa3051d59dfe80fbb5164205f1d906d270ca43e75191b36973228f
6
+ metadata.gz: 0b6b8661ec4ee9f416eff3373a049c126a744e5bd280ddf88fac6d896f58df1f844f170ddc85d5addbd099e70e5fc8f5d3421a966ea5b13bba2f568eb2bc8f86
7
+ data.tar.gz: d50b1b02e71f78e14ebde1c8c08abcd80eb79911fd1fafcf15de6a89ea5c412db50ddeecc23e804f05acee61b8d75d1be8ca5bef38937105b779f32b39b4e6e4
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hot_module (1.0.0.alpha10)
4
+ hot_module (1.0.0.alpha12)
5
+ concurrent-ruby (~> 1.2)
5
6
  nokolexbor (>= 0.4.2)
6
7
 
7
8
  GEM
data/hot_module.gemspec CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  end
28
28
  spec.require_paths = ["lib"]
29
29
 
30
+ spec.add_dependency "concurrent-ruby", "~> 1.2"
30
31
  spec.add_dependency "nokolexbor", ">= 0.4.2"
31
32
 
32
33
  spec.add_development_dependency "hash_with_dot_access", "~> 1.2"
@@ -7,21 +7,16 @@ module HoTModuLe
7
7
 
8
8
  class ComponentRenderer < Bridgetown::Builder
9
9
  def build
10
- registered_tags = {}
11
- Bridgetown::Component.subclasses.each do |component|
12
- next unless component.respond_to?(:tag_name)
13
-
14
- registered_tags[component.tag_name] = component
15
- end
16
-
17
- render_html_modules(registered_tags)
10
+ render_html_modules
18
11
  end
19
12
 
20
- def render_html_modules(registered_tags) # rubocop:todo Metrics
21
- inspect_html do |doc, resource| # rubocop:todo Metrics
13
+ # rubocop:todo Metrics
14
+ def render_html_modules
15
+ inspect_html do |doc, resource|
22
16
  view_context = HoTModuLe::View.new(resource)
23
17
 
24
- registered_tags.each do |tag_name, component|
18
+ HoTModuLe.registered_elements.each do |component|
19
+ tag_name = component.tag_name
25
20
  doc.xpath("//#{tag_name}").reverse.each do |node|
26
21
  if node["hmod:ignore"]
27
22
  node.attribute("hmod:ignore").remove
@@ -33,9 +28,9 @@ module HoTModuLe
33
28
 
34
29
  new_attrs = {}
35
30
  attrs.each do |k, v|
36
- next unless k.start_with?("ruby:")
31
+ next unless k.start_with?("arg:")
37
32
 
38
- new_key = k.delete_prefix("ruby:")
33
+ new_key = k.delete_prefix("arg:")
39
34
  attrs.delete(k)
40
35
  new_attrs[new_key] = resource.instance_eval(v)
41
36
  end
@@ -54,5 +49,6 @@ module HoTModuLe
54
49
  end
55
50
  end
56
51
  end
52
+ # rubocop:enable Metrics
57
53
  end
58
54
  end
@@ -10,9 +10,9 @@ module HoTModuLe
10
10
  end
11
11
  end
12
12
 
13
- # NOTE: for some reason, the Nokogiri traverse method yields node children first, then the
14
- # parent node. That doesn't work for our case. We want to go strictly in source order. So this
15
- # is our own implementation of that.
13
+ # NOTE: for some reason, the traverse method yields node children first, then the
14
+ # parent node. That doesn't work for our case. We want to go strictly in source order.
15
+ # So this is our own implementation of that.
16
16
  def traverse(node, &block)
17
17
  yield(node)
18
18
  node.children.each { |child| traverse(child, &block) }
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HoTModuLe
4
- # Add a couple familar DOM API features to Nokogiri
4
+ # Add a couple familar DOM API features to Nokolexbor
5
5
  module QuerySelection
6
6
  # @param selector [String]
7
- # @return [Nokogiri::XML::Element]
7
+ # @return [Nokolexbor::Element]
8
8
  def query_selector(selector) = at_css(selector)
9
9
 
10
10
  # @param selector [String]
11
- # @return [Nokogiri::XML::Element]
11
+ # @return [Nokolexbor::Element]
12
12
  def query_selector_all(selector) = css(selector)
13
13
  end
14
14
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module HoTModuLe
4
4
  # @return [String]
5
- VERSION = "1.0.0.alpha10"
5
+ VERSION = "1.0.0.alpha12"
6
6
  end
data/lib/hot_module.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative "hot_module/version"
4
4
 
5
5
  require "nokolexbor"
6
+ require "concurrent"
6
7
 
7
8
  # Include this module into your own component class
8
9
  module HoTModuLe
@@ -23,10 +24,35 @@ module HoTModuLe
23
24
  require_relative "hot_module/fragment"
24
25
  require_relative "hot_module/query_selection"
25
26
 
27
+ def self.registered_elements
28
+ @registered_elements ||= Concurrent::Set.new
29
+
30
+ @registered_elements.each do |component|
31
+ begin
32
+ next if Kernel.const_get(component.to_s) == component # thin out unloaded consts
33
+ rescue NameError; end # rubocop:disable Lint/SuppressedException
34
+
35
+ @registered_elements.delete component
36
+ end
37
+
38
+ @registered_elements
39
+ end
40
+
41
+ def self.register_element(component)
42
+ @registered_elements ||= Concurrent::Set.new
43
+ @registered_elements << component
44
+ end
45
+
26
46
  # @param klass [Class]
27
47
  # @return [void]
28
48
  def self.included(klass)
29
49
  klass.extend ClassMethods
50
+
51
+ klass.attribute_binding "hmod:children", :_hmod_children_binding, only: :template
52
+ klass.attribute_binding "hmod:replace", :_hmod_replace_binding
53
+ klass.attribute_binding "hmod:text", :_hmod_expr_binding
54
+ klass.attribute_binding "hmod:html", :_hmod_expr_binding
55
+
30
56
  # Don't stomp on a superclass's `content` method
31
57
  return if klass.instance_methods.include?(:content)
32
58
 
@@ -41,7 +67,8 @@ module HoTModuLe
41
67
  end
42
68
  end
43
69
 
44
- def html_file_extensions = %w[module.html tmpl.html html].freeze
70
+ def html_file_extensions = %w[module.html mod.html html].freeze
71
+
45
72
  def processed_css_extension = "css-local"
46
73
 
47
74
  # @param tag_name [String]
@@ -54,43 +81,42 @@ module HoTModuLe
54
81
  raise HoTModuLe::Error, "You must either supply a file path argument or respond to `source_location'"
55
82
  end
56
83
 
57
- @tag_name = tag_name
84
+ self.tag_name tag_name
58
85
 
59
86
  if html_module
60
- @html_module = html_module
87
+ self.html_module html_module
61
88
  else
62
89
  basepath = File.join(File.dirname(source_location), File.basename(source_location, ".*"))
63
90
 
64
- @html_module = html_file_extensions.lazy.filter_map do |ext|
91
+ self.html_module(html_file_extensions.lazy.filter_map do |ext|
65
92
  path = "#{basepath}.#{ext}"
66
93
  File.exist?(path) ? path : nil
67
- end.first
94
+ end.first)
68
95
 
69
96
  raise HoTModuLe::Error, "Cannot find sidecar HTML module for `#{self}'" unless @html_module
70
97
  end
71
98
 
72
- @shadow_root = shadow_root
99
+ self.shadow_root shadow_root
73
100
  end
74
101
 
75
102
  # @param value [String]
76
103
  # @return [String]
77
104
  def tag_name(value = nil)
78
- @tag_name ||= value
105
+ @tag_name ||= begin
106
+ HoTModuLe.register_element self
107
+ value
108
+ end
79
109
  end
80
110
 
81
111
  # @param value [String]
82
112
  # @return [String]
83
- def html_module(value = nil)
84
- @html_module ||= value
85
- end
113
+ def html_module(value = nil) = @html_module ||= value
86
114
 
87
115
  # @param value [Boolean]
88
116
  # @return [Boolean]
89
- def shadow_root(value = nil)
90
- @shadow_root ||= value
91
- end
117
+ def shadow_root(value = nil) = @shadow_root ||= value
92
118
 
93
- # @return [Nokogiri::XML::Element]
119
+ # @return [Nokolexbor::Element]
94
120
  def doc
95
121
  @doc ||= begin
96
122
  @doc_html = "<#{tag_name}>#{File.read(html_module).strip}</#{tag_name}>"
@@ -103,9 +129,7 @@ module HoTModuLe
103
129
  instance_variable_get(:@doc_html)[0..loc].count("\n") + 1
104
130
  end
105
131
 
106
- def attribute_bindings
107
- @attribute_bindings ||= []
108
- end
132
+ def attribute_bindings = @attribute_bindings ||= []
109
133
 
110
134
  def attribute_binding(matcher, method_name, only: nil)
111
135
  attribute_bindings << AttributeBinding.new(
@@ -117,22 +141,21 @@ module HoTModuLe
117
141
  end
118
142
 
119
143
  module ContentMethod
120
- # @return [String, Nokogiri::XML::Element]
121
- def content; end
144
+ # @return [String, Nokolexbor::Element]
145
+ def content = @_content
122
146
  end
123
147
 
124
148
  # Override in component
125
149
  #
126
150
  # @return [Hash]
127
- def attributes
128
- {}
129
- end
151
+ def attributes = {}
130
152
 
131
153
  # @param attributes [Hash]
132
- # @param content [String, Nokogiri::XML::Element]
154
+ # @param content [String, Nokolexbor::Element]
133
155
  # @param return_node [Boolean]
134
156
  def render_element(attributes: self.attributes, content: self.content, return_node: false) # rubocop:disable Metrics
135
157
  doc = self.class.doc.clone
158
+ @_content = content
136
159
 
137
160
  tmpl_el = doc.css("> template").find { _1.attributes.empty? }
138
161
 
@@ -146,6 +169,33 @@ module HoTModuLe
146
169
  # Process all the template bits
147
170
  process_fragment(tmpl_el)
148
171
 
172
+ HoTModuLe.registered_elements.each do |component|
173
+ tmpl_el.children[0].css(component.tag_name).reverse.each do |node|
174
+ if node["hmod:ignore"]
175
+ node.attribute("hmod:ignore").remove
176
+ next
177
+ end
178
+
179
+ attrs = node.attributes.transform_values(&:value)
180
+ attrs.reject! { |k| k.start_with?("hmod:") }
181
+ new_attrs = {}
182
+ attrs.each do |k, v|
183
+ next unless k.start_with?("arg:")
184
+
185
+ new_key = k.delete_prefix("arg:")
186
+ attrs.delete(k)
187
+ new_attrs[new_key] = instance_eval(v, self.class.html_module, self.class.line_number_of_node(node))
188
+ end
189
+ attrs.merge!(new_attrs)
190
+ attrs.transform_keys!(&:to_sym)
191
+
192
+ new_node = node.replace(
193
+ component.new(**attrs).render_element(content: node.children)
194
+ )
195
+ new_node.first.attribute("hmod:ignore")&.remove
196
+ end
197
+ end
198
+
149
199
  # Set attributes on the custom element
150
200
  attributes.each { |k, v| doc[k.to_s.tr("_", "-")] = value_to_attribute(v) if v }
151
201
 
@@ -182,14 +232,15 @@ module HoTModuLe
182
232
  style_tag.content = output_styles
183
233
  end
184
234
 
235
+ child_content = @_replaced_children || content
185
236
  if self.class.shadow_root
186
237
  # Guess what? We can reuse the same template tag! =)
187
238
  tmpl_el["shadowrootmode"] = "open"
188
239
  tmpl_el.children[0] << style_tag if style_tag
189
- doc << content if content
240
+ doc << child_content if child_content
190
241
  else
191
242
  tmpl_el.children[0] << style_tag if style_tag
192
- tmpl_el.children[0].at_css("slot:not([name])")&.swap(content) if content
243
+ tmpl_el.children[0].at_css("slot:not([name])")&.swap(child_content) if child_content
193
244
  tmpl_el.children[0].children.each do |node|
194
245
  doc << node
195
246
  end
@@ -200,13 +251,9 @@ module HoTModuLe
200
251
  return_node ? doc : doc.to_html
201
252
  end
202
253
 
203
- def call(...)
204
- render_element(...)
205
- end
254
+ def call(...) = render_element(...)
206
255
 
207
- def inspect
208
- "#<#{self.class.name} #{attributes}>"
209
- end
256
+ def inspect = "#<#{self.class.name} #{attributes}>"
210
257
 
211
258
  def value_to_attribute(val)
212
259
  case val
@@ -219,13 +266,15 @@ module HoTModuLe
219
266
  end
220
267
  end
221
268
 
269
+ def node_or_string(val)
270
+ val.is_a?(Nokolexbor::Node) ? val : val.to_s
271
+ end
272
+
222
273
  # Override in component if need be, otherwise we'll use the node walker/binding pipeline
223
274
  #
224
- # @param fragment [Nokogiri::XML::Element]
275
+ # @param fragment [Nokolexbor::Element]
225
276
  # @return [void]
226
- def process_fragment(fragment)
227
- Fragment.new(fragment, self).process
228
- end
277
+ def process_fragment(fragment) = Fragment.new(fragment, self).process
229
278
 
230
279
  def process_list(attribute:, node:, item_node:, for_in:) # rubocop:disable Metrics
231
280
  _context_nodes.push(node)
@@ -275,13 +324,9 @@ module HoTModuLe
275
324
  end.join(" ")
276
325
  end
277
326
 
278
- def _context_nodes
279
- @_context_nodes ||= []
280
- end
327
+ def _context_nodes = @_context_nodes ||= []
281
328
 
282
- def _context_locals
283
- @_context_locals ||= {}
284
- end
329
+ def _context_locals = @_context_locals ||= {}
285
330
 
286
331
  def _check_stack(node)
287
332
  node_and_ancestors = [node, *node.ancestors.to_a]
@@ -303,6 +348,31 @@ module HoTModuLe
303
348
  yield previous_context
304
349
  @_context_locals = previous_context
305
350
  end
351
+
352
+ def _hmod_children_binding(attribute:, node:) # rubocop:disable Lint/UnusedMethodArgument
353
+ @_replaced_children = node.children[0]
354
+ node.remove
355
+ end
356
+
357
+ def _hmod_replace_binding(attribute:, node:)
358
+ if node.name == "template"
359
+ node.children[0].inner_html = node_or_string(evaluate_attribute_expression(attribute))
360
+ node.replace(node.children[0].children)
361
+ else
362
+ node.inner_html = node_or_string(evaluate_attribute_expression(attribute))
363
+ node.replace(node.children)
364
+ end
365
+ end
366
+
367
+ def _hmod_expr_binding(attribute:, node:)
368
+ if attribute.name.end_with?(":text")
369
+ node.content = node_or_string(evaluate_attribute_expression(attribute))
370
+ attribute.parent.delete(attribute.name)
371
+ elsif attribute.name.end_with?(":html")
372
+ node.inner_html = node_or_string(evaluate_attribute_expression(attribute))
373
+ attribute.parent.delete(attribute.name)
374
+ end
375
+ end
306
376
  end
307
377
 
308
378
  if defined?(Bridgetown)
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hot_module
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.alpha10
4
+ version: 1.0.0.alpha12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jared White
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-05 00:00:00.000000000 Z
11
+ date: 2023-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: nokolexbor
15
29
  requirement: !ruby/object:Gem::Requirement