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 +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 +106 -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: d90f5f3aee3f096c2b0dc962592cef3ff02563a5a8b5bef47cab9adc9eabf309
|
4
|
+
data.tar.gz: cdda20661fa3aab05d14f793a4b6ef3cf6d9c0bfc116a28a49a1118ab1286d2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff109253d98e063cfcf6a79481a3cde7934afca492dcca0edd865266cc1401918ba858f04f1cd7ae125d424332f9cdbfd1e562fe4e9914c55313a08070aa61cc
|
7
|
+
data.tar.gz: bb1864cc03b42c1612bd53aa29ef7278d38ad46b8d3362ab31b6093aa8abe907a7a2d1587b04338b2e2b74b868136235181a6871ef27eecb2690e59765d9e3c7
|
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,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
|
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
|
-
|
79
|
+
self.tag_name tag_name
|
58
80
|
|
59
81
|
if html_module
|
60
|
-
|
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
|
-
|
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
|
-
|
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 ||=
|
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 [
|
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,
|
121
|
-
def content
|
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,
|
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 <<
|
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(
|
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 [
|
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.
|
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-
|
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
|