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 +4 -4
- data/Gemfile.lock +2 -1
- data/hot_module.gemspec +1 -0
- data/lib/hot_module/component_renderer.rb +9 -13
- data/lib/hot_module/fragment.rb +3 -3
- data/lib/hot_module/query_selection.rb +3 -3
- data/lib/hot_module/version.rb +1 -1
- data/lib/hot_module.rb +111 -41
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51da117a031a9b9ad3c16a35b4b5276928874e742bf72e90bc9e1089f4b856a1
|
4
|
+
data.tar.gz: b1bb49f906b3dbc82671ec7a4850baac1899c93ce3237cc88c8bb3c69dff75f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b6b8661ec4ee9f416eff3373a049c126a744e5bd280ddf88fac6d896f58df1f844f170ddc85d5addbd099e70e5fc8f5d3421a966ea5b13bba2f568eb2bc8f86
|
7
|
+
data.tar.gz: d50b1b02e71f78e14ebde1c8c08abcd80eb79911fd1fafcf15de6a89ea5c412db50ddeecc23e804f05acee61b8d75d1be8ca5bef38937105b779f32b39b4e6e4
|
data/Gemfile.lock
CHANGED
data/hot_module.gemspec
CHANGED
@@ -7,21 +7,16 @@ module HoTModuLe
|
|
7
7
|
|
8
8
|
class ComponentRenderer < Bridgetown::Builder
|
9
9
|
def build
|
10
|
-
|
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
|
-
|
21
|
-
|
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
|
-
|
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?("
|
31
|
+
next unless k.start_with?("arg:")
|
37
32
|
|
38
|
-
new_key = k.delete_prefix("
|
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
|
data/lib/hot_module/fragment.rb
CHANGED
@@ -10,9 +10,9 @@ module HoTModuLe
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
# NOTE: for some reason, the
|
14
|
-
# parent node. That doesn't work for our case. We want to go strictly in source order.
|
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
|
4
|
+
# Add a couple familar DOM API features to Nokolexbor
|
5
5
|
module QuerySelection
|
6
6
|
# @param selector [String]
|
7
|
-
# @return [
|
7
|
+
# @return [Nokolexbor::Element]
|
8
8
|
def query_selector(selector) = at_css(selector)
|
9
9
|
|
10
10
|
# @param selector [String]
|
11
|
-
# @return [
|
11
|
+
# @return [Nokolexbor::Element]
|
12
12
|
def query_selector_all(selector) = css(selector)
|
13
13
|
end
|
14
14
|
|
data/lib/hot_module/version.rb
CHANGED
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
|
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
|
-
|
84
|
+
self.tag_name tag_name
|
58
85
|
|
59
86
|
if html_module
|
60
|
-
|
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
|
-
|
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
|
-
|
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 ||=
|
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 [
|
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,
|
121
|
-
def content
|
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,
|
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 <<
|
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(
|
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 [
|
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.
|
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-
|
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
|