rbexy 1.1.0 → 2.0.0.beta5

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -1
  3. data/README.md +19 -1
  4. data/lib/rbexy.rb +26 -9
  5. data/lib/rbexy/ast_transformer.rb +21 -0
  6. data/lib/rbexy/cache_component.rb +17 -0
  7. data/lib/rbexy/component.rb +35 -21
  8. data/lib/rbexy/component_resolver.rb +60 -0
  9. data/lib/rbexy/configuration.rb +18 -1
  10. data/lib/rbexy/lexer.rb +21 -14
  11. data/lib/rbexy/nodes.rb +15 -135
  12. data/lib/rbexy/nodes/abstract_attr.rb +12 -0
  13. data/lib/rbexy/nodes/abstract_element.rb +13 -0
  14. data/lib/rbexy/nodes/abstract_node.rb +58 -0
  15. data/lib/rbexy/nodes/component_element.rb +69 -0
  16. data/lib/rbexy/nodes/component_prop.rb +29 -0
  17. data/lib/rbexy/nodes/declaration.rb +15 -0
  18. data/lib/rbexy/nodes/expression.rb +15 -0
  19. data/lib/rbexy/nodes/expression_group.rb +55 -0
  20. data/lib/rbexy/nodes/html_attr.rb +13 -0
  21. data/lib/rbexy/nodes/html_element.rb +48 -0
  22. data/lib/rbexy/nodes/newline.rb +9 -0
  23. data/lib/rbexy/nodes/raw.rb +23 -0
  24. data/lib/rbexy/nodes/root.rb +19 -0
  25. data/lib/rbexy/nodes/text.rb +15 -0
  26. data/lib/rbexy/nodes/util.rb +9 -0
  27. data/lib/rbexy/parser.rb +22 -16
  28. data/lib/rbexy/rails.rb +2 -0
  29. data/lib/rbexy/rails/component_template_resolver.rb +59 -4
  30. data/lib/rbexy/rails/controller_helper.rb +0 -6
  31. data/lib/rbexy/rails/engine.rb +3 -7
  32. data/lib/rbexy/rails/rbx_dependency_tracker.rb +37 -0
  33. data/lib/rbexy/refinements.rb +5 -0
  34. data/lib/rbexy/refinements/array.rb +9 -0
  35. data/lib/rbexy/refinements/array/find_map.rb +13 -0
  36. data/lib/rbexy/refinements/array/insert_between_types.rb +26 -0
  37. data/lib/rbexy/refinements/array/map_type_when_neighboring_type.rb +26 -0
  38. data/lib/rbexy/runtime.rb +15 -23
  39. data/lib/rbexy/template.rb +12 -0
  40. data/lib/rbexy/version.rb +1 -1
  41. data/rbexy.gemspec +1 -0
  42. metadata +43 -11
  43. data/example.rb +0 -113
  44. data/lib/rbexy/component_providers/namespaced_rbexy_provider.rb +0 -20
  45. data/lib/rbexy/component_providers/rbexy_provider.rb +0 -21
  46. data/lib/rbexy/component_providers/view_component_provider.rb +0 -21
  47. data/lib/rbexy/component_tag_builder.rb +0 -19
  48. data/lib/rbexy/hash_mash.rb +0 -15
  49. data/lib/rbexy/view_context_helper.rb +0 -19
@@ -0,0 +1,9 @@
1
+ module Rbexy
2
+ module Nodes
3
+ class Newline < AbstractNode
4
+ def compile
5
+ "\n"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ module Rbexy
2
+ module Nodes
3
+ class Raw < AbstractNode
4
+ attr_reader :content, :template
5
+
6
+ OUTPUT = "@output_buffer.safe_concat('%s'.freeze);"
7
+ EXPR_STRING = "'%s'.html_safe.freeze"
8
+
9
+ def initialize(content, template: OUTPUT)
10
+ @content = content
11
+ @template = template
12
+ end
13
+
14
+ def compile
15
+ template % content
16
+ end
17
+
18
+ def merge(other_raw)
19
+ content << other_raw.content
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module Rbexy
2
+ module Nodes
3
+ class Root < AbstractNode
4
+ attr_accessor :children
5
+
6
+ def initialize(children)
7
+ @children = children
8
+ end
9
+
10
+ def precompile
11
+ Root.new(compact(children.map(&:precompile).flatten))
12
+ end
13
+
14
+ def compile
15
+ "#{children.map(&:compile).join}@output_buffer.to_s"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module Rbexy
2
+ module Nodes
3
+ class Text < AbstractNode
4
+ attr_accessor :content
5
+
6
+ def initialize(content)
7
+ @content = content
8
+ end
9
+
10
+ def precompile
11
+ [Raw.new(Util.escape_string(content))]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Rbexy
2
+ module Nodes
3
+ module Util
4
+ def self.escape_string(str)
5
+ str.gsub('"', '\\"').gsub("'", "\\'")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -12,7 +12,7 @@ module Rbexy
12
12
 
13
13
  def parse
14
14
  validate_tokens!
15
- Nodes::Template.new(parse_tokens)
15
+ Nodes::Root.new(parse_tokens)
16
16
  end
17
17
 
18
18
  def parse_tokens
@@ -26,7 +26,7 @@ module Rbexy
26
26
  end
27
27
 
28
28
  def parse_token
29
- parse_text || parse_silent_newline || parse_expression || parse_tag || parse_declaration
29
+ parse_text || parse_newline || parse_expression || parse_tag || parse_declaration
30
30
  end
31
31
 
32
32
  def parse_text
@@ -37,14 +37,14 @@ module Rbexy
37
37
  def parse_expression
38
38
  return unless take(:OPEN_EXPRESSION)
39
39
 
40
- statements = []
40
+ members = []
41
41
 
42
42
  eventually!(:CLOSE_EXPRESSION)
43
43
  until take(:CLOSE_EXPRESSION)
44
- statements << (parse_expression_body || parse_tag)
44
+ members << (parse_expression_body || parse_tag)
45
45
  end
46
46
 
47
- Nodes::ExpressionGroup.new(statements)
47
+ Nodes::ExpressionGroup.new(members)
48
48
  end
49
49
 
50
50
  def parse_expression!
@@ -60,26 +60,32 @@ module Rbexy
60
60
  def parse_tag
61
61
  return unless take(:OPEN_TAG_DEF)
62
62
 
63
- name = take!(:TAG_NAME)
63
+ details = take!(:TAG_DETAILS)[1]
64
+ attr_class = details[:type] == :component ? Nodes::ComponentProp : Nodes::HTMLAttr
65
+
64
66
  members = []
65
- members.concat(take_all(:SILENT_NEWLINE).map { Nodes::SilentNewline.new })
66
- members.concat(parse_attrs)
67
+ members.concat(take_all(:NEWLINE).map { Nodes::Newline.new })
68
+ members.concat(parse_attrs(attr_class))
67
69
 
68
70
  take!(:CLOSE_TAG_DEF)
69
71
 
70
72
  children = parse_children
71
73
 
72
- Nodes::XmlNode.new(name[1], members, children)
74
+ if details[:type] == :component
75
+ Nodes::ComponentElement.new(details[:component_class], members, children)
76
+ else
77
+ Nodes::HTMLElement.new(details[:name], members, children)
78
+ end
73
79
  end
74
80
 
75
- def parse_attrs
81
+ def parse_attrs(attr_class)
76
82
  return [] unless take(:OPEN_ATTRS)
77
83
 
78
84
  attrs = []
79
85
 
80
86
  eventually!(:CLOSE_ATTRS)
81
87
  until take(:CLOSE_ATTRS)
82
- attrs << (parse_splat_attr || parse_silent_newline || parse_attr)
88
+ attrs << (parse_splat_attr || parse_newline || parse_attr(attr_class))
83
89
  end
84
90
 
85
91
  attrs
@@ -94,12 +100,12 @@ module Rbexy
94
100
  expression
95
101
  end
96
102
 
97
- def parse_silent_newline
98
- return unless take(:SILENT_NEWLINE)
99
- Nodes::SilentNewline.new
103
+ def parse_newline
104
+ return unless take(:NEWLINE)
105
+ Nodes::Newline.new
100
106
  end
101
107
 
102
- def parse_attr
108
+ def parse_attr(attr_class)
103
109
  name = take!(:ATTR_NAME)[1]
104
110
  value = nil
105
111
 
@@ -111,7 +117,7 @@ module Rbexy
111
117
  value = default_empty_attr_value
112
118
  end
113
119
 
114
- Nodes::XmlAttr.new(name, value)
120
+ attr_class.new(name, value)
115
121
  end
116
122
 
117
123
  def parse_children
@@ -1,9 +1,11 @@
1
1
  module Rbexy
2
2
  autoload :Component, "rbexy/component"
3
+ autoload :CacheComponent, "rbexy/cache_component"
3
4
 
4
5
  module Rails
5
6
  autoload :Engine, "rbexy/rails/engine"
6
7
  autoload :ControllerHelper, "rbexy/rails/controller_helper"
7
8
  autoload :ComponentTemplateResolver, "rbexy/rails/component_template_resolver"
9
+ autoload :RbxDependencyTracker, "rbexy/rails/rbx_dependency_tracker"
8
10
  end
9
11
  end
@@ -1,8 +1,14 @@
1
- require "action_view"
1
+ require "active_support/digest"
2
2
 
3
3
  module Rbexy
4
4
  module Rails
5
5
  class ComponentTemplateResolver < ActionView::FileSystemResolver
6
+ COMMENT_SYNTAX = {
7
+ rbx: "# %s",
8
+ erb: "<%# %s %>",
9
+ html: "<!-- %s -->"
10
+ }
11
+
6
12
  # Rails 6 requires us to override `_find_all` in order to hook
7
13
  def _find_all(name, prefix, partial, details, key, locals)
8
14
  find_templates(name, prefix, partial, details, locals)
@@ -14,22 +20,71 @@ module Rbexy
14
20
  return [] unless name.is_a? Rbexy::Component::TemplatePath
15
21
 
16
22
  templates_path = File.join(@path, prefix, name)
23
+ component_name = prefix.present? ? File.join(prefix, name) : name
24
+ virtual_path = Rbexy::Component::TemplatePath.new(component_name)
25
+
17
26
  extensions = details[:handlers].join(",")
27
+ templates = find_rbx_templates(templates_path, extensions, component_name, virtual_path)
28
+
29
+ if templates.none?
30
+ templates = find_call_component_cachebuster_templates(templates_path, component_name, virtual_path)
31
+ end
32
+
33
+ templates
34
+ end
18
35
 
36
+ def find_rbx_templates(templates_path, extensions, component_name, virtual_path)
19
37
  Dir["#{templates_path}.*{#{extensions}}"].map do |template_path|
20
38
  source = File.binread(template_path)
21
- handler = ActionView::Template.handler_for_extension(File.extname(template_path)[1..-1])
22
- virtual_path = ["rbexy_component", prefix, name].join("/")
39
+ extension = File.extname(template_path)[1..-1]
40
+ handler = ActionView::Template.handler_for_extension(extension)
23
41
 
24
42
  ActionView::Template.new(
25
- source,
43
+ "#{source}#{component_class_cachebuster(component_name, extension)}",
26
44
  template_path,
27
45
  handler,
46
+ format: extension.to_sym,
28
47
  locals: [],
29
48
  virtual_path: virtual_path
30
49
  )
31
50
  end
32
51
  end
52
+
53
+ def find_call_component_cachebuster_templates(templates_path, component_name, virtual_path)
54
+ component_class = find_component_class(component_name)
55
+ return [] unless component_class && component_class.call_component?
56
+
57
+ [
58
+ ActionView::Template.new(
59
+ cachebuster_digest_as_comment(component_class.component_file_location, :rbx),
60
+ "#{templates_path}.rbexycall",
61
+ ActionView::Template.handler_for_extension(:rbx),
62
+ format: :rbx,
63
+ locals: [],
64
+ virtual_path: virtual_path
65
+ )
66
+ ]
67
+ end
68
+
69
+ def component_class_cachebuster(component_name, template_format)
70
+ component_class = find_component_class(component_name)
71
+ return unless component_class
72
+
73
+ cachebuster_digest_as_comment(component_class.component_file_location, template_format)
74
+ end
75
+
76
+ def find_component_class(component_name)
77
+ Rbexy::ComponentResolver.try_constantize { component_name.classify.constantize }
78
+ end
79
+
80
+ def cachebuster_digest_as_comment(filename, format)
81
+ source = File.binread(filename)
82
+ digest = ActiveSupport::Digest.hexdigest(source)
83
+
84
+ comment_template = COMMENT_SYNTAX[format.to_sym] || COMMENT_SYNTAX[:html]
85
+ comment = comment_template % digest
86
+ "\n#{comment}"
87
+ end
33
88
  end
34
89
  end
35
90
  end
@@ -1,16 +1,10 @@
1
- require "active_support/concern"
2
-
3
1
  module Rbexy
4
2
  module Rails
5
3
  module ControllerHelper
6
4
  extend ActiveSupport::Concern
7
5
  include ComponentContext
8
6
 
9
- def rbexy_component_provider; end
10
-
11
7
  included do
12
- helper ViewContextHelper
13
- helper_method :rbexy_component_provider
14
8
  helper_method :rbexy_context, :create_context, :use_context
15
9
  end
16
10
 
@@ -1,24 +1,20 @@
1
1
  require "rbexy/rails"
2
+ require "action_view/dependency_tracker"
2
3
 
3
4
  module Rbexy
4
5
  module Rails
5
6
  class Engine < ::Rails::Engine
6
7
  initializer "rbexy" do |app|
7
- template_handler = proc { |template, source| Rbexy.compile(source) }
8
+ template_handler = proc { |template, source| Rbexy.compile(Rbexy::Template.new(source, template.identifier)) }
8
9
 
9
10
  ActionView::Template.register_template_handler(:rbx, template_handler)
11
+ ActionView::DependencyTracker.register_tracker(:rbx, RbxDependencyTracker)
10
12
 
11
13
  ActiveSupport.on_load :action_controller_base do
12
14
  include ControllerHelper
13
15
  end
14
16
 
15
- if defined?(ViewComponent)
16
- ViewComponent::Base.include ViewContextHelper
17
- end
18
-
19
17
  Rbexy.configure do |config|
20
- require "rbexy/component_providers/rbexy_provider"
21
- config.component_provider = ComponentProviders::RbexyProvider.new
22
18
  config.template_paths << ::Rails.root.join("app", "components")
23
19
  config.enable_context = true
24
20
  end
@@ -0,0 +1,37 @@
1
+ module Rbexy
2
+ module Rails
3
+ class RbxDependencyTracker
4
+ def self.supports_view_paths?
5
+ true
6
+ end
7
+
8
+ def self.call(name, template, view_paths = nil)
9
+ new(name, template, view_paths).dependencies
10
+ end
11
+
12
+ def initialize(name, template, view_paths = nil)
13
+ @name, @template, @view_paths = name, template, view_paths
14
+ end
15
+
16
+ def dependencies
17
+ rails_render_helper_dependencies + rbexy_dependencies
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :name, :template, :view_paths
23
+
24
+ def rails_render_helper_dependencies
25
+ ActionView::DependencyTracker::ERBTracker.call(name, template, view_paths)
26
+ end
27
+
28
+ def rbexy_dependencies
29
+ Lexer.new(template, Rbexy.configuration.element_resolver).tokenize
30
+ .select { |t| t[0] == :TAG_DETAILS && t[1][:type] == :component }
31
+ .map { |t| t[1][:component_class] }
32
+ .uniq
33
+ .map(&:template_path)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ module Rbexy
2
+ module Refinements
3
+ autoload :Array, "rbexy/refinements/array"
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module Rbexy
2
+ module Refinements
3
+ module Array
4
+ autoload :FindMap, "rbexy/refinements/array/find_map"
5
+ autoload :InsertBetweenTypes, "rbexy/refinements/array/insert_between_types"
6
+ autoload :MapTypeWhenNeighboringType, "rbexy/refinements/array/map_type_when_neighboring_type"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Rbexy
2
+ module Refinements
3
+ module Array
4
+ module FindMap
5
+ refine ::Array do
6
+ def find_map
7
+ lazy.map { |i| yield(i) }.reject { |v| !v }.first
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module Rbexy
2
+ module Refinements
3
+ module Array
4
+ module InsertBetweenTypes
5
+ refine ::Array do
6
+ def insert_between_types(type1, type2, &block)
7
+ map.with_index do |curr, i|
8
+ prev_i = i - 1
9
+
10
+ if prev_i >= 0 && InsertBetweenTypes.one_of_each_type?([self[prev_i], curr], [type1, type2])
11
+ [block.call, curr]
12
+ else
13
+ [curr]
14
+ end
15
+ end.flatten
16
+ end
17
+ end
18
+
19
+ def self.one_of_each_type?(items_pair, types_pair)
20
+ items_pair[0].is_a?(types_pair[0]) && items_pair[1].is_a?(types_pair[1]) ||
21
+ items_pair[0].is_a?(types_pair[1]) && items_pair[1].is_a?(types_pair[0])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module Rbexy
2
+ module Refinements
3
+ module Array
4
+ module MapTypeWhenNeighboringType
5
+ refine ::Array do
6
+ def map_type_when_neighboring_type(map_type, neighboring_type, &block)
7
+ map.with_index do |curr, i|
8
+ prev_i = i - 1
9
+ next_i = i + 1
10
+
11
+ if !curr.is_a?(map_type)
12
+ curr
13
+ elsif prev_i >= 0 && self[prev_i].is_a?(neighboring_type)
14
+ block.call(curr)
15
+ elsif next_i < length && self[next_i].is_a?(neighboring_type)
16
+ block.call(curr)
17
+ else
18
+ curr
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end