diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3f97cc503..6499a9dee 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,10 @@ nav_order: 6 ## main +* Introduce component-local config and migrate `strip_trailing_whitespace` to use it under the hood. + + *Simon Fish* + * Add docs about Slack channel in Ruby Central workspace. (Join us! #oss-view-component). Email joelhawksley@github.com for an invite. *Joel Hawksley diff --git a/docs/guide/templates.md b/docs/guide/templates.md index 2caf95ec0..9d7216633 100644 --- a/docs/guide/templates.md +++ b/docs/guide/templates.md @@ -181,14 +181,18 @@ end Code editors commonly add a trailing newline character to source files in keeping with the Unix standard. Including trailing whitespace in component templates can result in unwanted whitespace in the HTML, eg. if the component is rendered before the period at the end of a sentence. -To strip trailing whitespace from component templates, use the `strip_trailing_whitespace` class method. +To strip trailing whitespace from component templates, use the `strip_trailing_whitespace` component-local config option. ```ruby class MyComponent < ViewComponent::Base # do strip whitespace - strip_trailing_whitespace + configure_view_component do |config| + config.strip_trailing_whitespace = true + end # don't strip whitespace - strip_trailing_whitespace(false) + configure_view_component do |config| + config.strip_trailing_whitespace = false + end end ``` diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 95a2e0ca5..8b55a13d3 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -5,6 +5,7 @@ require "view_component/collection" require "view_component/compile_cache" require "view_component/compiler" +require "view_component/component_local_config" require "view_component/config" require "view_component/errors" require "view_component/inline_template" @@ -38,6 +39,7 @@ def config include ViewComponent::Slotable include ViewComponent::Translatable include ViewComponent::WithContentHelper + include ViewComponent::ComponentLocalConfig RESERVED_PARAMETER = :content VC_INTERNAL_DEFAULT_FORMAT = :html @@ -48,10 +50,6 @@ def config # For Content Security Policy nonces delegate :content_security_policy_nonce, to: :helpers - # Config option that strips trailing whitespace in templates before compiling them. - class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false - self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2 - attr_accessor :__vc_original_view_context # Components render in their own view context. Helpers and other functionality @@ -613,16 +611,38 @@ def with_collection_parameter(parameter) # end # ``` # + # @deprecated Use the new component-local configuration option instead. + # + # ```ruby + # class MyComponent < ViewComponent::Base + # configure_view_component do |config| + # config.strip_trailing_whitespace = true + # end + # end + # ``` + # # @param value [Boolean] Whether to strip newlines. def strip_trailing_whitespace(value = true) - self.__vc_strip_trailing_whitespace = value + ViewComponent::Deprecation.deprecation_warning( + "strip_trailing_whitespace", + <<~DOC + Use the new component-local configuration option instead: + + class #{self.class.name} < ViewComponent::Base + configure_view_component do |config| + config.strip_trailing_whitespace = #{value} + end + end + DOC + ) + view_component_config.strip_trailing_whitespace = value end # Whether trailing whitespace will be stripped before compilation. # # @return [Boolean] def strip_trailing_whitespace? - __vc_strip_trailing_whitespace + view_component_config.strip_trailing_whitespace end # Ensure the component initializer accepts the diff --git a/lib/view_component/component_local_config.rb b/lib/view_component/component_local_config.rb new file mode 100644 index 000000000..2bebccdbe --- /dev/null +++ b/lib/view_component/component_local_config.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module ViewComponent + module ComponentLocalConfig + class Configuration + def self.defaults + ActiveSupport::Configurable::Configuration[ + strip_trailing_whitespace: false + ] + end + + def initialize(config = defaults) + @config = config + end + + delegate_missing_to :@config + + def inheritable_copy + self.class.new(@config.inheritable_copy) + end + + private + + delegate :defaults, to: :class + end + + extend ActiveSupport::Concern + + included do + # :nocov: + def view_component_config + @__vc_config ||= self.class.view_component_config.inheritable_copy + end + + private + + def inherited(child) + child.instance_variable_set(:@__vc_config, nil) + super + end + # :nocov: + end + + class_methods do + def view_component_config + @__vc_config ||= if respond_to?(:superclass) && superclass.respond_to?(:view_component_config) + superclass.view_component_config.inheritable_copy + else + # create a new "anonymous" class that will host the compiled reader methods + ViewComponent::ComponentLocalConfig::Configuration.new + end + end + + def configure_view_component(&block) + view_component_config.instance_eval(&block) + view_component_config.compile_methods! + end + end + end +end diff --git a/test/sandbox/app/components/configurable_component.rb b/test/sandbox/app/components/configurable_component.rb new file mode 100644 index 000000000..8209d4cc6 --- /dev/null +++ b/test/sandbox/app/components/configurable_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class ConfigurableComponent < ViewComponent::Base + configure_view_component do |config| + config.strip_trailing_whitespace = true + end +end diff --git a/test/sandbox/test/base_test.rb b/test/sandbox/test/base_test.rb index 0e7b26d84..4091fe2e8 100644 --- a/test/sandbox/test/base_test.rb +++ b/test/sandbox/test/base_test.rb @@ -197,4 +197,12 @@ def test_uses_module_configuration assert_equal "AnotherController", TestAlreadyConfigurableModule::SomeComponent.test_controller assert_equal "AnotherController", TestAlreadyConfiguredModule::SomeComponent.test_controller end + + def test_component_local_config_is_inheritable + assert_equal false, ViewComponent::Base.view_component_config.strip_trailing_whitespace + # This component doesn't call configure, so it should inherit the defaults. + assert_equal false, AnotherComponent.view_component_config.strip_trailing_whitespace + # This component overrides the defaults. + assert_equal true, ConfigurableComponent.view_component_config.strip_trailing_whitespace + end end