hot_module 1.0.0.alpha10 → 1.0.0.alpha11

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: d90f5f3aee3f096c2b0dc962592cef3ff02563a5a8b5bef47cab9adc9eabf309
4
+ data.tar.gz: cdda20661fa3aab05d14f793a4b6ef3cf6d9c0bfc116a28a49a1118ab1286d2c
5
5
  SHA512:
6
- metadata.gz: 6285f80875501f2b04b28f10bfdf7f9145a4a5a6843a3f01ed3b3a2c0848d9584b829e1ee5b0048c41d70dde248aa8f506be25155fae1a94472c57c46c77669d
7
- data.tar.gz: ca42664335930b9468bdcd0d2e09620cd5b3b81cd2e3dfbc1fd2bd256cabe12515796f7288fa3051d59dfe80fbb5164205f1d906d270ca43e75191b36973228f
6
+ metadata.gz: ff109253d98e063cfcf6a79481a3cde7934afca492dcca0edd865266cc1401918ba858f04f1cd7ae125d424332f9cdbfd1e562fe4e9914c55313a08070aa61cc
7
+ data.tar.gz: bb1864cc03b42c1612bd53aa29ef7278d38ad46b8d3362ab31b6093aa8abe907a7a2d1587b04338b2e2b74b868136235181a6871ef27eecb2690e59765d9e3c7
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.alpha11)
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.alpha11"
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,30 @@ 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
+
26
41
  # @param klass [Class]
27
42
  # @return [void]
28
43
  def self.included(klass)
29
44
  klass.extend ClassMethods
45
+
46
+ klass.attribute_binding "hmod:children", :_hmod_children_binding, only: :template
47
+ klass.attribute_binding "hmod:replace", :_hmod_replace_binding
48
+ klass.attribute_binding "hmod:text", :_hmod_expr_binding
49
+ klass.attribute_binding "hmod:html", :_hmod_expr_binding
50
+
30
51
  # Don't stomp on a superclass's `content` method
31
52
  return if klass.instance_methods.include?(:content)
32
53
 
@@ -41,7 +62,8 @@ module HoTModuLe
41
62
  end
42
63
  end
43
64
 
44
- def html_file_extensions = %w[module.html tmpl.html html].freeze
65
+ def html_file_extensions = %w[module.html mod.html html].freeze
66
+
45
67
  def processed_css_extension = "css-local"
46
68
 
47
69
  # @param tag_name [String]
@@ -54,43 +76,42 @@ module HoTModuLe
54
76
  raise HoTModuLe::Error, "You must either supply a file path argument or respond to `source_location'"
55
77
  end
56
78
 
57
- @tag_name = tag_name
79
+ self.tag_name tag_name
58
80
 
59
81
  if html_module
60
- @html_module = html_module
82
+ self.html_module html_module
61
83
  else
62
84
  basepath = File.join(File.dirname(source_location), File.basename(source_location, ".*"))
63
85
 
64
- @html_module = html_file_extensions.lazy.filter_map do |ext|
86
+ self.html_module(html_file_extensions.lazy.filter_map do |ext|
65
87
  path = "#{basepath}.#{ext}"
66
88
  File.exist?(path) ? path : nil
67
- end.first
89
+ end.first)
68
90
 
69
91
  raise HoTModuLe::Error, "Cannot find sidecar HTML module for `#{self}'" unless @html_module
70
92
  end
71
93
 
72
- @shadow_root = shadow_root
94
+ self.shadow_root shadow_root
73
95
  end
74
96
 
75
97
  # @param value [String]
76
98
  # @return [String]
77
99
  def tag_name(value = nil)
78
- @tag_name ||= value
100
+ @tag_name ||= begin
101
+ HoTModuLe.registered_elements << self
102
+ value
103
+ end
79
104
  end
80
105
 
81
106
  # @param value [String]
82
107
  # @return [String]
83
- def html_module(value = nil)
84
- @html_module ||= value
85
- end
108
+ def html_module(value = nil) = @html_module ||= value
86
109
 
87
110
  # @param value [Boolean]
88
111
  # @return [Boolean]
89
- def shadow_root(value = nil)
90
- @shadow_root ||= value
91
- end
112
+ def shadow_root(value = nil) = @shadow_root ||= value
92
113
 
93
- # @return [Nokogiri::XML::Element]
114
+ # @return [Nokolexbor::Element]
94
115
  def doc
95
116
  @doc ||= begin
96
117
  @doc_html = "<#{tag_name}>#{File.read(html_module).strip}</#{tag_name}>"
@@ -103,9 +124,7 @@ module HoTModuLe
103
124
  instance_variable_get(:@doc_html)[0..loc].count("\n") + 1
104
125
  end
105
126
 
106
- def attribute_bindings
107
- @attribute_bindings ||= []
108
- end
127
+ def attribute_bindings = @attribute_bindings ||= []
109
128
 
110
129
  def attribute_binding(matcher, method_name, only: nil)
111
130
  attribute_bindings << AttributeBinding.new(
@@ -117,22 +136,21 @@ module HoTModuLe
117
136
  end
118
137
 
119
138
  module ContentMethod
120
- # @return [String, Nokogiri::XML::Element]
121
- def content; end
139
+ # @return [String, Nokolexbor::Element]
140
+ def content = @_content
122
141
  end
123
142
 
124
143
  # Override in component
125
144
  #
126
145
  # @return [Hash]
127
- def attributes
128
- {}
129
- end
146
+ def attributes = {}
130
147
 
131
148
  # @param attributes [Hash]
132
- # @param content [String, Nokogiri::XML::Element]
149
+ # @param content [String, Nokolexbor::Element]
133
150
  # @param return_node [Boolean]
134
151
  def render_element(attributes: self.attributes, content: self.content, return_node: false) # rubocop:disable Metrics
135
152
  doc = self.class.doc.clone
153
+ @_content = content
136
154
 
137
155
  tmpl_el = doc.css("> template").find { _1.attributes.empty? }
138
156
 
@@ -146,6 +164,33 @@ module HoTModuLe
146
164
  # Process all the template bits
147
165
  process_fragment(tmpl_el)
148
166
 
167
+ HoTModuLe.registered_elements.each do |component|
168
+ tmpl_el.children[0].css(component.tag_name).reverse.each do |node|
169
+ if node["hmod:ignore"]
170
+ node.attribute("hmod:ignore").remove
171
+ next
172
+ end
173
+
174
+ attrs = node.attributes.transform_values(&:value)
175
+ attrs.reject! { |k| k.start_with?("hmod:") }
176
+ new_attrs = {}
177
+ attrs.each do |k, v|
178
+ next unless k.start_with?("arg:")
179
+
180
+ new_key = k.delete_prefix("arg:")
181
+ attrs.delete(k)
182
+ new_attrs[new_key] = instance_eval(v, self.class.html_module, self.class.line_number_of_node(node))
183
+ end
184
+ attrs.merge!(new_attrs)
185
+ attrs.transform_keys!(&:to_sym)
186
+
187
+ new_node = node.replace(
188
+ component.new(**attrs).render_element(content: node.children)
189
+ )
190
+ new_node.first.attribute("hmod:ignore")&.remove
191
+ end
192
+ end
193
+
149
194
  # Set attributes on the custom element
150
195
  attributes.each { |k, v| doc[k.to_s.tr("_", "-")] = value_to_attribute(v) if v }
151
196
 
@@ -182,14 +227,15 @@ module HoTModuLe
182
227
  style_tag.content = output_styles
183
228
  end
184
229
 
230
+ child_content = @_replaced_children || content
185
231
  if self.class.shadow_root
186
232
  # Guess what? We can reuse the same template tag! =)
187
233
  tmpl_el["shadowrootmode"] = "open"
188
234
  tmpl_el.children[0] << style_tag if style_tag
189
- doc << content if content
235
+ doc << child_content if child_content
190
236
  else
191
237
  tmpl_el.children[0] << style_tag if style_tag
192
- tmpl_el.children[0].at_css("slot:not([name])")&.swap(content) if content
238
+ tmpl_el.children[0].at_css("slot:not([name])")&.swap(child_content) if child_content
193
239
  tmpl_el.children[0].children.each do |node|
194
240
  doc << node
195
241
  end
@@ -200,13 +246,9 @@ module HoTModuLe
200
246
  return_node ? doc : doc.to_html
201
247
  end
202
248
 
203
- def call(...)
204
- render_element(...)
205
- end
249
+ def call(...) = render_element(...)
206
250
 
207
- def inspect
208
- "#<#{self.class.name} #{attributes}>"
209
- end
251
+ def inspect = "#<#{self.class.name} #{attributes}>"
210
252
 
211
253
  def value_to_attribute(val)
212
254
  case val
@@ -219,13 +261,15 @@ module HoTModuLe
219
261
  end
220
262
  end
221
263
 
264
+ def node_or_string(val)
265
+ val.is_a?(Nokolexbor::Node) ? val : val.to_s
266
+ end
267
+
222
268
  # Override in component if need be, otherwise we'll use the node walker/binding pipeline
223
269
  #
224
- # @param fragment [Nokogiri::XML::Element]
270
+ # @param fragment [Nokolexbor::Element]
225
271
  # @return [void]
226
- def process_fragment(fragment)
227
- Fragment.new(fragment, self).process
228
- end
272
+ def process_fragment(fragment) = Fragment.new(fragment, self).process
229
273
 
230
274
  def process_list(attribute:, node:, item_node:, for_in:) # rubocop:disable Metrics
231
275
  _context_nodes.push(node)
@@ -275,13 +319,9 @@ module HoTModuLe
275
319
  end.join(" ")
276
320
  end
277
321
 
278
- def _context_nodes
279
- @_context_nodes ||= []
280
- end
322
+ def _context_nodes = @_context_nodes ||= []
281
323
 
282
- def _context_locals
283
- @_context_locals ||= {}
284
- end
324
+ def _context_locals = @_context_locals ||= {}
285
325
 
286
326
  def _check_stack(node)
287
327
  node_and_ancestors = [node, *node.ancestors.to_a]
@@ -303,6 +343,31 @@ module HoTModuLe
303
343
  yield previous_context
304
344
  @_context_locals = previous_context
305
345
  end
346
+
347
+ def _hmod_children_binding(attribute:, node:) # rubocop:disable Lint/UnusedMethodArgument
348
+ @_replaced_children = node.children[0]
349
+ node.remove
350
+ end
351
+
352
+ def _hmod_replace_binding(attribute:, node:)
353
+ if node.name == "template"
354
+ node.children[0].inner_html = node_or_string(evaluate_attribute_expression(attribute))
355
+ node.replace(node.children[0].children)
356
+ else
357
+ node.inner_html = node_or_string(evaluate_attribute_expression(attribute))
358
+ node.replace(node.children)
359
+ end
360
+ end
361
+
362
+ def _hmod_expr_binding(attribute:, node:)
363
+ if attribute.name.end_with?(":text")
364
+ node.content = node_or_string(evaluate_attribute_expression(attribute))
365
+ attribute.parent.delete(attribute.name)
366
+ elsif attribute.name.end_with?(":html")
367
+ node.inner_html = node_or_string(evaluate_attribute_expression(attribute))
368
+ attribute.parent.delete(attribute.name)
369
+ end
370
+ end
306
371
  end
307
372
 
308
373
  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.alpha11
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