glimmer-dsl-web 0.3.1 → 0.3.2

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: 162d98e6fa4d8eefcec1f4edb2f5af6c3d5d3a88aaa01704ded825b982aabbd9
4
- data.tar.gz: 8c133105ac1cf160658c476771a9b58c0ac351116a1b919cdef37db19bd085e7
3
+ metadata.gz: fda2e015b57a53d9281d9f04b7c222d8d42bf61bb33d670cb6ddb8cb0893d2c5
4
+ data.tar.gz: 327db5ff9687b0fb8f5ed96533c9c86dc71ca20876b8db39e28d7b156aed280b
5
5
  SHA512:
6
- metadata.gz: 5bc70f98db8b2fec83310e789967447663f9d1d7017ad6dd8b1984ce5e520ef835430d5f5b8c885fa92282d63617a58ba42cb30dda6b0212cafd63e3ce378b93
7
- data.tar.gz: 75561cba8a7a7d61e71e286e197dbbb80879261715500249ddd565f0833273777039632f2553a1cb5e0e3c4c1258704fc83f73ea7e163c1d4ab64a0af0e2f307
6
+ metadata.gz: 7b36ba84170c0a90ac89a28218275fa4da16b21972bb298cd59b675fb41670c367aec3524f8582a7181d91f2a723ea36f7d7e6b8e2a1d4e2733b634b7e8e808f
7
+ data.tar.gz: 4d88b75d775ca693fe50602f7d6b9fd136188f1d88d8d13dee8d7e281c9218a1fc81540e662b1428ff67ac1ee124a015919aec31b2853eb49378ff022003c225
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.3.2
4
+
5
+ - Optimize performance (~248% faster) of rendering by changing DSL ordering to avoid component checks at the top
6
+ - Optimize performance of formatting html elements by adding Glimmer DSL shortcut methods
7
+ - Optimize performance of component expressions by indexing component keywords
8
+
3
9
  ## 0.3.1
4
10
 
5
11
  - Optimize Todo MVC performance for filtering between all, active, and completed (it happens instantly now)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.3.1 (Beta)
1
+ # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.3.2 (Beta)
2
2
  ## Ruby in the Browser Web Frontend Framework
3
3
  ### Finally, Ruby Developer Productivity, Happiness, and Fun in the Frontend!!!
4
4
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-web.svg)](http://badge.fury.io/rb/glimmer-dsl-web)
@@ -1333,7 +1333,7 @@ rails new glimmer_app_server
1333
1333
  Add the following to `Gemfile`:
1334
1334
 
1335
1335
  ```
1336
- gem 'glimmer-dsl-web', '~> 0.3.1'
1336
+ gem 'glimmer-dsl-web', '~> 0.3.2'
1337
1337
  ```
1338
1338
 
1339
1339
  Run:
@@ -1562,7 +1562,7 @@ Disable the `webpacker` gem line in `Gemfile`:
1562
1562
  Add the following to `Gemfile`:
1563
1563
 
1564
1564
  ```ruby
1565
- gem 'glimmer-dsl-web', '~> 0.3.1'
1565
+ gem 'glimmer-dsl-web', '~> 0.3.2'
1566
1566
  ```
1567
1567
 
1568
1568
  Run:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.3.2
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: glimmer-dsl-web 0.3.1 ruby lib
5
+ # stub: glimmer-dsl-web 0.3.2 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer-dsl-web".freeze
9
- s.version = "0.3.1"
9
+ s.version = "0.3.2"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Andy Maleh".freeze]
14
- s.date = "2024-07-01"
14
+ s.date = "2024-07-02"
15
15
  s.description = "Glimmer DSL for Web (Ruby in the Browser Web Frontend Framework) enables building Web Frontends using Ruby in the Browser, as per Matz's recommendation in his RubyConf 2022 keynote speech to replace JavaScript with Ruby. It aims at providing the simplest, most intuitive, most straight-forward, and most productive frontend framework in existence. The framework follows the Ruby way (with DSLs and TIMTOWTDI) and the Rails way (Convention over Configuration) in building Isomorphic Ruby on Rails Applications. It provides a Ruby HTML DSL, which uniquely enables writing both structure code and logic code in one language. It supports both Unidirectional (One-Way) Data-Binding (using <=) and Bidirectional (Two-Way) Data-Binding (using <=>). Dynamic rendering (and re-rendering) of HTML content is also supported via Content Data-Binding. Modular design is supported with Glimmer Web Components. And, a Ruby CSS DSL is supported with the included Glimmer DSL for CSS. Many samples are demonstrated in the Rails sample app (there is a very minimal Standalone [No Rails] sample app too). You can finally live in pure Rubyland on the Web in both the frontend and backend with Glimmer DSL for Web! This gem relies on Opal Ruby.".freeze
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.extra_rdoc_files = [
@@ -93,7 +93,7 @@ Gem::Specification.new do |s|
93
93
 
94
94
  s.specification_version = 4
95
95
 
96
- s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.7.8"])
96
+ s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.7.9"])
97
97
  s.add_runtime_dependency(%q<glimmer-dsl-xml>.freeze, ["~> 1.4.0"])
98
98
  s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.4.0"])
99
99
  s.add_runtime_dependency(%q<opal>.freeze, ["= 1.8.2"])
@@ -4,7 +4,6 @@ require 'glimmer/data_binding/observer'
4
4
  module Glimmer
5
5
  module DataBinding
6
6
  class ElementBinding
7
- include Glimmer
8
7
  include Observable
9
8
  include Observer
10
9
 
@@ -12,7 +11,7 @@ module Glimmer
12
11
  def initialize(element, property, translator = nil)
13
12
  @element = element
14
13
  @property = property
15
- @translator = translator || proc {|value| value}
14
+ @translator = translator
16
15
 
17
16
  # TODO implement automatic cleanup upon calling element.remove
18
17
  # Alternatively, have this be built into ElementProxy and remove this code
@@ -24,8 +23,9 @@ module Glimmer
24
23
  end
25
24
 
26
25
  def call(value)
27
- converted_value = translated_value = @translator.call(value, evaluate_property)
28
- @element.send("#{@property}=", converted_value) unless evaluate_property == converted_value
26
+ evaluated_property_value = evaluate_property
27
+ converted_value = @translator&.call(value, evaluated_property_value) || value
28
+ @element.send("#{@property}=", converted_value) unless converted_value == evaluated_property_value
29
29
  end
30
30
 
31
31
  def evaluate_property
@@ -8,7 +8,7 @@ module Glimmer
8
8
  include ParentExpression
9
9
 
10
10
  def can_interpret?(parent, keyword, *args, &block)
11
- !!Glimmer::Web::Component.for(keyword)
11
+ Glimmer::Web::Component.for(keyword)
12
12
  end
13
13
 
14
14
  def interpret(parent, keyword, *args, &block)
@@ -40,14 +40,14 @@ module Glimmer
40
40
  Engine.add_dynamic_expressions(
41
41
  Web,
42
42
  %w[
43
- component
44
43
  listener
45
- data_binding
46
- property
47
- content_data_binding
48
- shine_data_binding
49
44
  style
45
+ content_data_binding
46
+ component
50
47
  formatting_element
48
+ data_binding
49
+ shine_data_binding
50
+ property
51
51
  ]
52
52
  )
53
53
  end
@@ -17,3 +17,14 @@ module Glimmer
17
17
  end
18
18
  end
19
19
  end
20
+
21
+ module Glimmer
22
+ # Optimize performance through shortcut methods for all HTML formatting elements that circumvent the DSL chain of responsibility
23
+ element_expression = Glimmer::DSL::Web::FormattingElementExpression.new
24
+ Glimmer::Web::FormattingElementProxy::FORMATTING_ELEMENT_KEYWORDS.each do |keyword|
25
+ Glimmer::DSL::Engine.static_expressions[keyword] ||= Concurrent::Hash.new
26
+ element_expression_dsl = element_expression.class.dsl
27
+ Glimmer::DSL::Engine.static_expressions[keyword][element_expression_dsl] = element_expression
28
+ Glimmer.send(:define_method, keyword, &Glimmer::DSL::Engine::STATIC_EXPRESSION_METHOD_FACTORY.call(keyword))
29
+ end
30
+ end
@@ -119,71 +119,54 @@ module Glimmer
119
119
  end
120
120
  end
121
121
 
122
+ ADD_COMPONENT_KEYWORDS_UPON_INHERITANCE = proc do
123
+ class << self
124
+ def inherited(subclass)
125
+ Glimmer::Web::Component.add_component_keyword_to_classes_map_for(subclass)
126
+ subclass.class_eval(&Glimmer::Web::Component::ADD_COMPONENT_KEYWORDS_UPON_INHERITANCE)
127
+ end
128
+ end
129
+ end
130
+
122
131
  class << self
123
132
  def included(klass)
124
133
  if !klass.ancestors.include?(GlimmerSupersedable)
125
134
  klass.extend(ClassMethods)
126
135
  klass.include(Glimmer)
127
136
  klass.prepend(GlimmerSupersedable)
128
- Glimmer::Web::Component.add_component_namespaces_for(klass)
137
+ Glimmer::Web::Component.add_component_keyword_to_classes_map_for(klass)
138
+ klass.class_eval(&Glimmer::Web::Component::ADD_COMPONENT_KEYWORDS_UPON_INHERITANCE)
129
139
  end
130
140
  end
131
141
 
132
142
  def for(underscored_component_name)
133
- extracted_namespaces = underscored_component_name.
134
- to_s.
135
- split(/__/).map do |namespace|
136
- namespace.camelcase(:upper)
137
- end
138
- Glimmer::Web::Component.component_namespaces.each do |base|
139
- extracted_namespaces.reduce(base) do |result, namespace|
140
- if !result.constants.include?(namespace)
141
- namespace = result.constants.detect {|c| c.to_s.upcase == namespace.to_s.upcase } || namespace
142
- end
143
- begin
144
- constant = result.const_get(namespace)
145
- return constant if constant&.respond_to?(:ancestors) &&
146
- (
147
- constant&.ancestors&.to_a.include?(Glimmer::Web::Component) ||
148
- # TODO checking GlimmerSupersedable as a hack because when a class is loaded twice (like when loading samples
149
- # by reloading ruby files), it loses its Glimmer::Web::Component ancestor as a bug in Opal
150
- # but somehow the prepend module remains
151
- constant&.ancestors&.to_a.include?(GlimmerSupersedable)
152
- )
153
- constant
154
- rescue => e
155
- # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
156
- result
157
- end
158
- end
143
+ component_classes = Glimmer::Web::Component.component_keyword_to_classes_map[underscored_component_name]
144
+ if component_classes.nil? || component_classes.empty?
145
+ Glimmer::Config.logger.debug {"#{underscored_component_name} has no Glimmer web component class!" }
146
+ nil
147
+ else
148
+ component_class = component_classes.first
159
149
  end
160
- raise "#{underscored_component_name} has no Glimmer web component class!"
161
- rescue => e
162
- Glimmer::Config.logger.debug {e.message}
163
- Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
164
- nil
165
150
  end
166
151
 
167
- def add_component_namespaces_for(klass)
168
- Glimmer::Web::Component.namespaces_for_class(klass).drop(1).each do |namespace|
169
- Glimmer::Web::Component.component_namespaces << namespace
152
+ def add_component_keyword_to_classes_map_for(component_class)
153
+ keywords_for_class(component_class).each do |keyword|
154
+ Glimmer::Web::Component.component_keyword_to_classes_map[keyword] ||= []
155
+ Glimmer::Web::Component.component_keyword_to_classes_map[keyword] << component_class
170
156
  end
171
157
  end
172
158
 
173
- def namespaces_for_class(m)
174
- return [m] if m.name.nil?
175
- namespace_constants = m.name.split(/::/).map(&:to_sym)
176
- namespace_constants.reduce([Object]) do |output, namespace_constant|
177
- output += [output.last.const_get(namespace_constant)]
178
- end[1..-1].uniq.reverse
159
+ def keywords_for_class(component_class)
160
+ namespaces = component_class.to_s.split(/::/).map(&:underscore).reverse
161
+ namespaces.size.times.map { |n| namespaces[0..n].reverse.join('__') }
179
162
  end
180
163
 
181
- def component_namespaces
182
- @component_namespaces ||= reset_component_namespaces
164
+ def component_keyword_to_classes_map
165
+ @component_keyword_to_classes_map ||= reset_component_keyword_to_classes_map
183
166
  end
184
167
 
185
- def reset_component_namespaces
186
- @component_namespaces = Set[Object, Glimmer::Web]
168
+ def reset_component_keyword_to_classes_map
169
+ @component_keyword_to_classes_map = {}
187
170
  end
188
171
 
189
172
  def interpretation_stack
@@ -319,6 +302,14 @@ module Glimmer
319
302
  def remove
320
303
  @markup_root&.remove
321
304
  end
305
+
306
+ def data_bind(property, model_binding)
307
+ @markup_root&.data_bind(property, model_binding)
308
+ end
309
+
310
+ def bind_content(*binding_args, &content_block)
311
+ @markup_root&.bind_content(*binding_args, &content_block)
312
+ end
322
313
 
323
314
  # Returns content block if used as an attribute reader (no args)
324
315
  # Otherwise, if a block is passed, it adds it as content to this Glimmer web component
@@ -539,8 +539,8 @@ module Glimmer
539
539
  end
540
540
 
541
541
  def data_bind(property, model_binding)
542
- element_binding_translator = value_converters_for_input_type(type)[:model_to_view]
543
- element_binding_parameters = [self, property, element_binding_translator]
542
+ element_binding_read_translator = value_converters_for_input_type(type)&.[](:model_to_view)
543
+ element_binding_parameters = [self, property, element_binding_read_translator]
544
544
  element_binding = DataBinding::ElementBinding.new(*element_binding_parameters)
545
545
  #TODO make this options observer dependent and all similar observers in element specific data binding handlers
546
546
  element_binding.observe(model_binding)
@@ -552,7 +552,8 @@ module Glimmer
552
552
  if listener_keyword
553
553
  data_binding_read_listener = lambda do |event|
554
554
  view_property_value = send(property)
555
- converted_view_property_value = value_converters_for_input_type(type)[:view_to_model].call(view_property_value, model_binding.evaluate_property)
555
+ element_binding_write_translator = value_converters_for_input_type(type)&.[](:view_to_model)
556
+ converted_view_property_value = element_binding_write_translator&.call(view_property_value, model_binding.evaluate_property) || view_property_value
556
557
  model_binding.call(converted_view_property_value)
557
558
  end
558
559
  handle_observation_request(listener_keyword, data_binding_read_listener)
@@ -722,7 +723,7 @@ module Glimmer
722
723
  end
723
724
 
724
725
  def value_converters_for_input_type(input_type)
725
- input_value_converters[input_type] || {model_to_view: ->(value, old_value) {value}, view_to_model: ->(value, old_value) {value}}
726
+ input_value_converters[input_type]
726
727
  end
727
728
 
728
729
  def input_value_converters
@@ -5,10 +5,10 @@ class EditTodoInput < TodoInput
5
5
  option :todo
6
6
 
7
7
  markup {
8
- input(class: todo_input_class) { |edit_input|
8
+ input(class: self.class.todo_input_class) { |edit_input|
9
9
  style <= [ todo, :editing,
10
10
  on_read: ->(editing) { editing ? '' : 'display: none;' },
11
- after_read: ->(_) { edit_input.focus if todo.editing? }
11
+ after_read: -> { edit_input.focus if todo.editing? }
12
12
  ]
13
13
 
14
14
  value <=> [todo, :task]
@@ -25,31 +25,29 @@ class EditTodoInput < TodoInput
25
25
  onblur do |event|
26
26
  todo.save_editing
27
27
  end
28
-
29
- style {
30
- todo_input_styles
31
- }
32
28
  }
33
29
  }
34
30
 
35
- def todo_input_class
36
- 'edit-todo'
37
- end
38
-
39
- def todo_input_styles
40
- super
41
-
42
- rule("*:has(> .#{todo_input_class})") {
43
- position 'relative'
44
- }
31
+ class << self
32
+ def todo_input_class
33
+ 'edit-todo'
34
+ end
45
35
 
46
- rule(".#{todo_input_class}") {
47
- position 'absolute'
48
- display 'block'
49
- width 'calc(100% - 43px)'
50
- padding '12px 16px'
51
- margin '0 0 0 43px'
52
- top '0'
53
- }
36
+ def todo_input_styles
37
+ super
38
+
39
+ rule("*:has(> .#{todo_input_class})") {
40
+ position 'relative'
41
+ }
42
+
43
+ rule(".#{todo_input_class}") {
44
+ position 'absolute'
45
+ display 'block'
46
+ width 'calc(100% - 43px)'
47
+ padding '12px 16px'
48
+ margin '0 0 0 43px'
49
+ top '0'
50
+ }
51
+ end
54
52
  end
55
53
  end
@@ -4,7 +4,7 @@ class NewTodoInput < TodoInput
4
4
  option :presenter
5
5
 
6
6
  markup {
7
- input(class: todo_input_class, placeholder: "What needs to be done?", autofocus: "") {
7
+ input(class: self.class.todo_input_class, placeholder: "What needs to be done?", autofocus: "") {
8
8
  value <=> [presenter.new_todo, :task]
9
9
 
10
10
  onkeyup do |event|
@@ -12,30 +12,32 @@ class NewTodoInput < TodoInput
12
12
  end
13
13
 
14
14
  style {
15
- todo_input_styles
15
+ self.class.todo_input_styles
16
16
  }
17
17
  }
18
18
  }
19
19
 
20
- def todo_input_class
21
- 'new-todo'
22
- end
23
-
24
- def todo_input_styles
25
- super
26
-
27
- rule(".#{todo_input_class}") {
28
- padding '16px 16px 16px 60px'
29
- height '65px'
30
- border 'none'
31
- background 'rgba(0, 0, 0, 0.003)'
32
- box_shadow 'inset 0 -2px 1px rgba(0,0,0,0.03)'
33
- }
20
+ class << self
21
+ def todo_input_class
22
+ 'new-todo'
23
+ end
34
24
 
35
- rule(".#{todo_input_class}::placeholder") {
36
- font_style 'italic'
37
- font_weight '400'
38
- color 'rgba(0, 0, 0, 0.4)'
39
- }
25
+ def todo_input_styles
26
+ super
27
+
28
+ rule(".#{todo_input_class}") {
29
+ padding '16px 16px 16px 60px'
30
+ height '65px'
31
+ border 'none'
32
+ background 'rgba(0, 0, 0, 0.003)'
33
+ box_shadow 'inset 0 -2px 1px rgba(0,0,0,0.03)'
34
+ }
35
+
36
+ rule(".#{todo_input_class}::placeholder") {
37
+ font_style 'italic'
38
+ font_weight '400'
39
+ color 'rgba(0, 0, 0, 0.4)'
40
+ }
41
+ end
40
42
  end
41
43
  end
@@ -2,29 +2,31 @@
2
2
  class TodoInput
3
3
  include Glimmer::Web::Component
4
4
 
5
- def todo_input_class
6
- 'todo-input'
7
- end
8
-
9
- def todo_input_styles
10
- rule(".#{todo_input_class}") {
11
- position 'relative'
12
- margin '0'
13
- width '100%'
14
- font_size '24px'
15
- font_family 'inherit'
16
- font_weight 'inherit'
17
- line_height '1.4em'
18
- color 'inherit'
19
- padding '6px'
20
- border '1px solid #999'
21
- box_shadow 'inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2)'
22
- box_sizing 'border-box'
23
- _webkit_font_smoothing 'antialiased'
24
- }
5
+ class << self
6
+ def todo_input_class
7
+ 'todo-input'
8
+ end
25
9
 
26
- rule(".#{todo_input_class}::selection") {
27
- background 'red'
28
- }
10
+ def todo_input_styles
11
+ rule(".#{todo_input_class}") {
12
+ position 'relative'
13
+ margin '0'
14
+ width '100%'
15
+ font_size '24px'
16
+ font_family 'inherit'
17
+ font_weight 'inherit'
18
+ line_height '1.4em'
19
+ color 'inherit'
20
+ padding '6px'
21
+ border '1px solid #999'
22
+ box_shadow 'inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2)'
23
+ box_sizing 'border-box'
24
+ _webkit_font_smoothing 'antialiased'
25
+ }
26
+
27
+ rule(".#{todo_input_class}::selection") {
28
+ background 'red'
29
+ }
30
+ end
29
31
  end
30
32
  end
@@ -46,6 +46,8 @@ class TodoList
46
46
  }
47
47
 
48
48
  def todo_list_styles
49
+ TodoListItem.todo_list_item_styles
50
+
49
51
  rule('.main') {
50
52
  border_top '1px solid #e6e6e6'
51
53
  position 'relative'
@@ -102,7 +104,5 @@ class TodoList
102
104
  rule('.todo-list.completed li.active') {
103
105
  display 'none'
104
106
  }
105
-
106
- TodoListItem.todo_list_item_styles
107
107
  end
108
108
  end
@@ -55,6 +55,8 @@ class TodoListItem
55
55
  end
56
56
 
57
57
  def self.todo_list_item_styles
58
+ EditTodoInput.todo_input_styles
59
+
58
60
  rule('.todo-list li.completed label') {
59
61
  color '#949494'
60
62
  text_decoration 'line-through'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-01 00:00:00.000000000 Z
11
+ date: 2024-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.7.8
19
+ version: 2.7.9
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.7.8
26
+ version: 2.7.9
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: glimmer-dsl-xml
29
29
  requirement: !ruby/object:Gem::Requirement