view_component 4.0.0.alpha2 → 4.0.0.alpha3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35393079b843dba053e2e4acca6429b8181a5c255c5843c7ad39fcb5245b8762
4
- data.tar.gz: a9a602a05dd03d1865bfef3115904f8f7afec7bccbddc2497469638982259875
3
+ metadata.gz: d926054ff51767aa08c688eef809327cbbda35e8b403a574af8baa642aefe7ed
4
+ data.tar.gz: ed0a01b7b1fe98080c4c7210c7bdde4b474d08f4b0c5ab0a36c39e9e6631f185
5
5
  SHA512:
6
- metadata.gz: 04e1d26d5f24f76c75cf4b105842b71ce31887ec8ec939e6c9a232f1c0bca48fe61bdad32e602773aa95f19c338a2d2fc4962a381fc025bf3ea356d1343cbf80
7
- data.tar.gz: b2c254c725e460a0dc16cd90300218ffff424c889946b7df9e31561b93d89dba304510e07afd48d014fdf1f576d07aa9e59ac75e2dce0822fb45fac7b7f9fdf1
6
+ metadata.gz: 54aa90e88b7fb9f0773842f0737739c15cfaff4609f62fe733badd14ab8676ec21532c5d00915947ce183aca678f7ed79573f15e8dec06f873067a9585a277ab
7
+ data.tar.gz: 3c4187ccc4f70942964fedd232fb387314e5f7cfa47624df5def3006ff9478947d4de9380c0baf29b5102e20217de0eb77a9bfe8a21240a79065cb719d02bf94
data/docs/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@ nav_order: 6
10
10
 
11
11
  ## main
12
12
 
13
+ ## 4.0.0.alpha3
14
+
15
+ * BREAKING: Remove dependency on `ActionView::Base`, eliminating the need for capture compatibility patch.
16
+
17
+ *Cameron Dutro*
18
+
13
19
  ## 4.0.0.alpha2
14
20
 
15
21
  * Add `#current_template` accessor and `Template#path` for diagnostic usage.
@@ -118,6 +124,14 @@ This release makes the following breaking changes:
118
124
 
119
125
  *Tiago Menegaz*, *Joel Hawksley*
120
126
 
127
+ * Introduce component-local config and migrate `strip_trailing_whitespace` to use it under the hood.
128
+
129
+ *Simon Fish*
130
+
131
+ * Add docs about Slack channel in Ruby Central workspace. (Join us! #oss-view-component). Email joelhawksley@github.com for an invite.
132
+
133
+ *Joel Hawksley
134
+
121
135
  * Do not include internal `DocsBuilderComponent` or `YARD::MattrAccessorHandler` in published gem.
122
136
 
123
137
  *Joel Hawksley*
@@ -5,6 +5,7 @@ require "active_support/configurable"
5
5
  require "view_component/collection"
6
6
  require "view_component/compile_cache"
7
7
  require "view_component/compiler"
8
+ require "view_component/component_local_config"
8
9
  require "view_component/config"
9
10
  require "view_component/errors"
10
11
  require "view_component/inline_template"
@@ -17,8 +18,21 @@ require "view_component/translatable"
17
18
  require "view_component/with_content_helper"
18
19
  require "view_component/use_helpers"
19
20
 
21
+ module ActionView
22
+ class OutputBuffer
23
+ def with_buffer(buf = nil)
24
+ new_buffer = buf || +""
25
+ old_buffer, @raw_buffer = @raw_buffer, new_buffer
26
+ yield
27
+ new_buffer
28
+ ensure
29
+ @raw_buffer = old_buffer
30
+ end
31
+ end
32
+ end
33
+
20
34
  module ViewComponent
21
- class Base < ActionView::Base
35
+ class Base
22
36
  class << self
23
37
  delegate(*ViewComponent::Config.defaults.keys, to: :config)
24
38
 
@@ -34,21 +48,26 @@ module ViewComponent
34
48
  end
35
49
  end
36
50
 
51
+ include ActionView::Helpers
52
+ include ERB::Escape
53
+ include ActiveSupport::CoreExt::ERBUtil
54
+
37
55
  include ViewComponent::InlineTemplate
38
56
  include ViewComponent::UseHelpers
39
57
  include ViewComponent::Slotable
40
58
  include ViewComponent::Translatable
41
59
  include ViewComponent::WithContentHelper
60
+ include ViewComponent::ComponentLocalConfig
42
61
 
43
62
  # For CSRF authenticity tokens in forms
44
63
  delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers
45
64
 
65
+ # HTML construction methods
66
+ delegate :output_buffer, :lookup_context, :view_renderer, :view_flow, to: :helpers
67
+
46
68
  # For Content Security Policy nonces
47
69
  delegate :content_security_policy_nonce, to: :helpers
48
70
 
49
- # Config option that strips trailing whitespace in templates before compiling them.
50
- class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false, default: false
51
-
52
71
  attr_accessor :__vc_original_view_context
53
72
  attr_reader :current_template
54
73
 
@@ -62,7 +81,7 @@ module ViewComponent
62
81
  # @param view_context [ActionView::Base] The original view context.
63
82
  # @return [void]
64
83
  def set_original_view_context(view_context)
65
- self.__vc_original_view_context = view_context
84
+ # noop
66
85
  end
67
86
 
68
87
  using RequestDetails
@@ -81,7 +100,7 @@ module ViewComponent
81
100
  @view_context = view_context
82
101
  self.__vc_original_view_context ||= view_context
83
102
 
84
- @output_buffer = ActionView::OutputBuffer.new
103
+ @output_buffer = view_context.output_buffer
85
104
 
86
105
  @lookup_context ||= view_context.lookup_context
87
106
 
@@ -108,14 +127,20 @@ module ViewComponent
108
127
  before_render
109
128
 
110
129
  if render?
111
- rendered_template = render_template_for(@__vc_requested_details).to_s
130
+ value = nil
112
131
 
113
- # Avoid allocating new string when output_preamble and output_postamble are blank
114
- if output_preamble.blank? && output_postamble.blank?
115
- rendered_template
116
- else
117
- safe_output_preamble + rendered_template + safe_output_postamble
132
+ @output_buffer.with_buffer do
133
+ rendered_template = render_template_for(@__vc_requested_details).to_s
134
+
135
+ # Avoid allocating new string when output_preamble and output_postamble are blank
136
+ value = if output_preamble.blank? && output_postamble.blank?
137
+ rendered_template
138
+ else
139
+ safe_output_preamble + rendered_template + safe_output_postamble
140
+ end
118
141
  end
142
+
143
+ value
119
144
  else
120
145
  ""
121
146
  end
@@ -207,7 +232,7 @@ module ViewComponent
207
232
  def render(options = {}, args = {}, &block)
208
233
  if options.respond_to?(:set_original_view_context)
209
234
  options.set_original_view_context(self.__vc_original_view_context)
210
- super
235
+ @view_context.render(options, args, &block)
211
236
  else
212
237
  __vc_original_view_context.render(options, args, &block)
213
238
  end
@@ -594,16 +619,38 @@ module ViewComponent
594
619
  # end
595
620
  # ```
596
621
  #
622
+ # @deprecated Use the new component-local configuration option instead.
623
+ #
624
+ # ```ruby
625
+ # class MyComponent < ViewComponent::Base
626
+ # configure_view_component do |config|
627
+ # config.strip_trailing_whitespace = true
628
+ # end
629
+ # end
630
+ # ```
631
+ #
597
632
  # @param value [Boolean] Whether to strip newlines.
598
633
  def strip_trailing_whitespace(value = true)
599
- self.__vc_strip_trailing_whitespace = value
634
+ ViewComponent::Deprecation.deprecation_warning(
635
+ "strip_trailing_whitespace",
636
+ <<~DOC
637
+ Use the new component-local configuration option instead:
638
+
639
+ class #{self.class.name} < ViewComponent::Base
640
+ configure_view_component do |config|
641
+ config.strip_trailing_whitespace = #{value}
642
+ end
643
+ end
644
+ DOC
645
+ )
646
+ view_component_config.strip_trailing_whitespace = value
600
647
  end
601
648
 
602
649
  # Whether trailing whitespace will be stripped before compilation.
603
650
  #
604
651
  # @return [Boolean]
605
652
  def strip_trailing_whitespace?
606
- __vc_strip_trailing_whitespace
653
+ view_component_config.strip_trailing_whitespace
607
654
  end
608
655
 
609
656
  # Ensure the component initializer accepts the
@@ -9,15 +9,8 @@ module ViewComponent
9
9
 
10
10
  delegate :size, to: :@collection
11
11
 
12
- attr_accessor :__vc_original_view_context
13
-
14
- def set_original_view_context(view_context)
15
- self.__vc_original_view_context = view_context
16
- end
17
-
18
12
  def render_in(view_context, &block)
19
13
  components.map do |component|
20
- component.set_original_view_context(__vc_original_view_context)
21
14
  component.render_in(view_context, &block)
22
15
  end.join(rendered_spacer(view_context)).html_safe
23
16
  end
@@ -67,7 +60,6 @@ module ViewComponent
67
60
 
68
61
  def rendered_spacer(view_context)
69
62
  if @spacer_component
70
- @spacer_component.set_original_view_context(__vc_original_view_context)
71
63
  @spacer_component.render_in(view_context)
72
64
  else
73
65
  ""
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module ComponentLocalConfig
5
+ class Configuration
6
+ def self.defaults
7
+ ActiveSupport::Configurable::Configuration[
8
+ strip_trailing_whitespace: false
9
+ ]
10
+ end
11
+
12
+ def initialize(config = defaults)
13
+ @config = config
14
+ end
15
+
16
+ delegate_missing_to :@config
17
+
18
+ def inheritable_copy
19
+ self.class.new(@config.inheritable_copy)
20
+ end
21
+
22
+ private
23
+
24
+ delegate :defaults, to: :class
25
+ end
26
+
27
+ extend ActiveSupport::Concern
28
+
29
+ included do
30
+ # :nocov:
31
+ def view_component_config
32
+ @__vc_config ||= self.class.view_component_config.inheritable_copy
33
+ end
34
+
35
+ private
36
+
37
+ def inherited(child)
38
+ child.instance_variable_set(:@__vc_config, nil)
39
+ super
40
+ end
41
+ # :nocov:
42
+ end
43
+
44
+ class_methods do
45
+ def view_component_config
46
+ @__vc_config ||= if respond_to?(:superclass) && superclass.respond_to?(:view_component_config)
47
+ superclass.view_component_config.inheritable_copy
48
+ else
49
+ # create a new "anonymous" class that will host the compiled reader methods
50
+ ViewComponent::ComponentLocalConfig::Configuration.new
51
+ end
52
+ end
53
+
54
+ def configure_view_component(&block)
55
+ view_component_config.instance_eval(&block)
56
+ view_component_config.compile_methods!
57
+ end
58
+ end
59
+ end
60
+ end
@@ -21,8 +21,7 @@ module ViewComponent
21
21
  show_previews: Rails.env.development? || Rails.env.test?,
22
22
  preview_paths: default_preview_paths,
23
23
  test_controller: "ApplicationController",
24
- default_preview_layout: nil,
25
- capture_compatibility_patch_enabled: false
24
+ default_preview_layout: nil
26
25
  })
27
26
  end
28
27
 
@@ -145,13 +144,6 @@ module ViewComponent
145
144
  # previews.
146
145
  # Defaults to `nil`. If this is falsy, `"component_preview"` is used.
147
146
 
148
- # @!attribute capture_compatibility_patch_enabled
149
- # @return [Boolean]
150
- # Enables the experimental capture compatibility patch that makes ViewComponent
151
- # compatible with forms, capture, and other built-ins.
152
- # previews.
153
- # Defaults to `false`.
154
-
155
147
  def default_preview_paths
156
148
  (default_rails_preview_paths + default_rails_engines_preview_paths).uniq
157
149
  end
@@ -51,12 +51,6 @@ module ViewComponent
51
51
  end
52
52
  end
53
53
 
54
- initializer "view_component.enable_capture_patch" do |app|
55
- ActiveSupport.on_load(:view_component) do
56
- ActionView::Base.include(ViewComponent::CaptureCompatibility) if app.config.view_component.capture_compatibility_patch_enabled
57
- end
58
- end
59
-
60
54
  initializer "view_component.set_autoload_paths" do |app|
61
55
  options = app.config.view_component
62
56
 
@@ -66,6 +60,39 @@ module ViewComponent
66
60
  end
67
61
  end
68
62
 
63
+ initializer "view_component.propshaft_support" do |_app|
64
+ ActiveSupport.on_load(:view_component) do
65
+ if defined?(Propshaft)
66
+ include Propshaft::Helper
67
+ end
68
+ end
69
+ end
70
+
71
+ config.after_initialize do |app|
72
+ ActiveSupport.on_load(:view_component) do
73
+ if defined?(Sprockets::Rails)
74
+ include Sprockets::Rails::Helper
75
+
76
+ # Copy relevant config to VC context
77
+ # See: https://github.com/rails/sprockets-rails/blob/266ec49f3c7c96018dd75f9dc4f9b62fe3f7eecf/lib/sprockets/railtie.rb#L245
78
+ self.debug_assets = app.config.assets.debug
79
+ self.digest_assets = app.config.assets.digest
80
+ self.assets_prefix = app.config.assets.prefix
81
+ self.assets_precompile = app.config.assets.precompile
82
+
83
+ self.assets_environment = app.assets
84
+ self.assets_manifest = app.assets_manifest
85
+
86
+ self.resolve_assets_with = app.config.assets.resolve_with
87
+
88
+ self.check_precompiled_asset = app.config.assets.check_precompiled_asset
89
+ self.unknown_asset_fallback = app.config.assets.unknown_asset_fallback
90
+ # Expose the app precompiled asset check to the view
91
+ self.precompiled_asset_checker = ->(logical_path) { app.asset_precompiled? logical_path }
92
+ end
93
+ end
94
+ end
95
+
69
96
  initializer "view_component.eager_load_actions" do
70
97
  ActiveSupport.on_load(:after_initialize) do
71
98
  ViewComponent::Base.descendants.each(&:__vc_compile) if Rails.application.config.eager_load
@@ -58,15 +58,7 @@ module ViewComponent
58
58
  if defined?(@__vc_content_block)
59
59
  # render_in is faster than `parent.render`
60
60
  @__vc_component_instance.render_in(view_context) do |*args|
61
- return @__vc_content_block.call(*args) if @__vc_content_block&.source_location.nil?
62
-
63
- block_context = @__vc_content_block.binding.receiver
64
-
65
- if block_context.class < ActionView::Base
66
- block_context.capture(*args, &@__vc_content_block)
67
- else
68
- @__vc_content_block.call(*args)
69
- end
61
+ @__vc_content_block.call(*args)
70
62
  end
71
63
  else
72
64
  @__vc_component_instance.render_in(view_context)
@@ -98,8 +98,9 @@ module ViewComponent
98
98
  @component.silence_redefinition_of_method(call_method_name)
99
99
 
100
100
  # rubocop:disable Style/EvalWithLocation
101
- @component.class_eval <<~RUBY, @path, @lineno
101
+ @component.class_eval <<~RUBY, @path, @lineno - 1
102
102
  def #{call_method_name}
103
+ @view_context.instance_variable_set(:@virtual_path, virtual_path)
103
104
  #{compiled_source}
104
105
  end
105
106
  RUBY
@@ -93,7 +93,7 @@ module ViewComponent
93
93
  def translate(key = nil, **options)
94
94
  raise ViewComponent::TranslateCalledBeforeRenderError if view_context.nil?
95
95
 
96
- return super unless __vc_i18n_backend
96
+ return @view_context.translate(key, **options) unless __vc_i18n_backend
97
97
  return key.map { |k| translate(k, **options) } if key.is_a?(Array)
98
98
 
99
99
  locale = options.delete(:locale) || ::I18n.locale
@@ -110,13 +110,13 @@ module ViewComponent
110
110
 
111
111
  # Fallback to the global translations
112
112
  if translated.is_a? ::I18n::MissingTranslation
113
- return super(key, locale: locale, **options)
113
+ return @view_context.translate(key, locale: locale, **options)
114
114
  end
115
115
 
116
116
  translated = html_safe_translation(translated) if as_html
117
117
  translated
118
118
  else
119
- super(key, locale: locale, **options)
119
+ @view_context.translate(key, locale: locale, **options)
120
120
  end
121
121
  end
122
122
  alias_method :t, :translate
@@ -5,7 +5,7 @@ module ViewComponent
5
5
  MAJOR = 4
6
6
  MINOR = 0
7
7
  PATCH = 0
8
- PRE = "alpha2"
8
+ PRE = "alpha3"
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
11
11
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.alpha2
4
+ version: 4.0.0.alpha3
5
5
  platform: ruby
6
6
  authors:
7
7
  - ViewComponent Team
@@ -466,10 +466,10 @@ files:
466
466
  - docs/CHANGELOG.md
467
467
  - lib/view_component.rb
468
468
  - lib/view_component/base.rb
469
- - lib/view_component/capture_compatibility.rb
470
469
  - lib/view_component/collection.rb
471
470
  - lib/view_component/compile_cache.rb
472
471
  - lib/view_component/compiler.rb
472
+ - lib/view_component/component_local_config.rb
473
473
  - lib/view_component/config.rb
474
474
  - lib/view_component/configurable.rb
475
475
  - lib/view_component/deprecation.rb
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ViewComponent
4
- # CaptureCompatibility is a module that patches #capture to fix issues
5
- # related to ViewComponent and functionality that relies on `capture`
6
- # like forms, capture itself, turbo frames, etc.
7
- #
8
- # This underlying incompatibility with ViewComponent and capture is
9
- # that several features like forms keep a reference to the primary
10
- # `ActionView::Base` instance which has its own @output_buffer. When
11
- # `#capture` is called on the original `ActionView::Base` instance while
12
- # evaluating a block from a ViewComponent the @output_buffer is overridden
13
- # in the ActionView::Base instance, and *not* the component. This results
14
- # in a double render due to `#capture` implementation details.
15
- #
16
- # To resolve the issue, we override `#capture` so that we can delegate
17
- # the `capture` logic to the ViewComponent that created the block.
18
- module CaptureCompatibility
19
- def self.included(base)
20
- return if base < InstanceMethods
21
-
22
- base.class_eval do
23
- alias_method :original_capture, :capture
24
- end
25
-
26
- base.prepend(InstanceMethods)
27
- end
28
-
29
- module InstanceMethods
30
- def capture(*args, &block)
31
- # Handle blocks that originate from C code and raise, such as `&:method`
32
- return original_capture(*args, &block) if block.source_location.nil?
33
-
34
- block_context = block.binding.receiver
35
-
36
- if block_context != self && block_context.class < ActionView::Base
37
- block_context.original_capture(*args, &block)
38
- else
39
- original_capture(*args, &block)
40
- end
41
- end
42
- end
43
- end
44
- end