view_component 4.0.0.alpha1 → 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: e8c0d5be4a21f5c14ed5447caeaffb8d6e51397a1ec786f573bc58d9486fc747
4
- data.tar.gz: 332de180e52e1b5eae806fb47e9d1dea4a32374cdf63a0642ca69d3661e4c802
3
+ metadata.gz: d926054ff51767aa08c688eef809327cbbda35e8b403a574af8baa642aefe7ed
4
+ data.tar.gz: ed0a01b7b1fe98080c4c7210c7bdde4b474d08f4b0c5ab0a36c39e9e6631f185
5
5
  SHA512:
6
- metadata.gz: e298e8e18d147034b30fb60f32a7bbf5d5f42d1235d5947cfeba4e3e1b8ad65a57379f3a3fb361ba7ffdec727687689ceb98b4b893e038a16ea5496663d4423a
7
- data.tar.gz: f6e8f8956abde8eac1e4825c09a2554dde1dd80efac74ba541210e2fe663e23399ddc01289ca44dee21e6c3551b32117b88444d9795fbb450b02639ebc9fadbf
6
+ metadata.gz: 54aa90e88b7fb9f0773842f0737739c15cfaff4609f62fe733badd14ab8676ec21532c5d00915947ce183aca678f7ed79573f15e8dec06f873067a9585a277ab
7
+ data.tar.gz: 3c4187ccc4f70942964fedd232fb387314e5f7cfa47624df5def3006ff9478947d4de9380c0baf29b5102e20217de0eb77a9bfe8a21240a79065cb719d02bf94
data/docs/CHANGELOG.md CHANGED
@@ -10,7 +10,19 @@ nav_order: 6
10
10
 
11
11
  ## main
12
12
 
13
- ## 4.0.0
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
+
19
+ ## 4.0.0.alpha2
20
+
21
+ * Add `#current_template` accessor and `Template#path` for diagnostic usage.
22
+
23
+ *Joel Hawksley*
24
+
25
+ ## 4.0.0.alpha1
14
26
 
15
27
  Almost six years after releasing [v1.0.0](https://github.com/ViewComponent/view_component/releases/tag/v1.0.0), we're proud to ship ViewComponent 4. This release marks a shift towards a Long Term Support model for the project, having reached significant feature maturity. While contributions are always welcome, we're unlikely to accept further breaking changes or major feature additions.
16
28
 
@@ -112,6 +124,14 @@ This release makes the following breaking changes:
112
124
 
113
125
  *Tiago Menegaz*, *Joel Hawksley*
114
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
+
115
135
  * Do not include internal `DocsBuilderComponent` or `YARD::MattrAccessorHandler` in published gem.
116
136
 
117
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,22 +48,28 @@ 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
72
+ attr_reader :current_template
53
73
 
54
74
  # Components render in their own view context. Helpers and other functionality
55
75
  # require a reference to the original Rails view context, an instance of
@@ -61,7 +81,7 @@ module ViewComponent
61
81
  # @param view_context [ActionView::Base] The original view context.
62
82
  # @return [void]
63
83
  def set_original_view_context(view_context)
64
- self.__vc_original_view_context = view_context
84
+ # noop
65
85
  end
66
86
 
67
87
  using RequestDetails
@@ -80,7 +100,7 @@ module ViewComponent
80
100
  @view_context = view_context
81
101
  self.__vc_original_view_context ||= view_context
82
102
 
83
- @output_buffer = ActionView::OutputBuffer.new
103
+ @output_buffer = view_context.output_buffer
84
104
 
85
105
  @lookup_context ||= view_context.lookup_context
86
106
 
@@ -107,14 +127,20 @@ module ViewComponent
107
127
  before_render
108
128
 
109
129
  if render?
110
- rendered_template = render_template_for(@__vc_requested_details).to_s
130
+ value = nil
111
131
 
112
- # Avoid allocating new string when output_preamble and output_postamble are blank
113
- if output_preamble.blank? && output_postamble.blank?
114
- rendered_template
115
- else
116
- 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
117
141
  end
142
+
143
+ value
118
144
  else
119
145
  ""
120
146
  end
@@ -206,7 +232,7 @@ module ViewComponent
206
232
  def render(options = {}, args = {}, &block)
207
233
  if options.respond_to?(:set_original_view_context)
208
234
  options.set_original_view_context(self.__vc_original_view_context)
209
- super
235
+ @view_context.render(options, args, &block)
210
236
  else
211
237
  __vc_original_view_context.render(options, args, &block)
212
238
  end
@@ -593,16 +619,38 @@ module ViewComponent
593
619
  # end
594
620
  # ```
595
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
+ #
596
632
  # @param value [Boolean] Whether to strip newlines.
597
633
  def strip_trailing_whitespace(value = true)
598
- 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
599
647
  end
600
648
 
601
649
  # Whether trailing whitespace will be stripped before compilation.
602
650
  #
603
651
  # @return [Boolean]
604
652
  def strip_trailing_whitespace?
605
- __vc_strip_trailing_whitespace
653
+ view_component_config.strip_trailing_whitespace
606
654
  end
607
655
 
608
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)
@@ -7,7 +7,7 @@ module ViewComponent
7
7
 
8
8
  DataWithSource = Struct.new(:format, :identifier, :short_identifier, :type, keyword_init: true)
9
9
 
10
- attr_reader :details
10
+ attr_reader :details, :path
11
11
 
12
12
  delegate :virtual_path, to: :@component
13
13
  delegate :format, :variant, to: :@details
@@ -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 = "alpha1"
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.alpha1
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