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