hot_module 1.0.0.alpha10 → 1.0.0.alpha12

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 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