hot_module 1.0.0.alpha10 → 1.0.0.alpha11

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