Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strip trailing whitespace in templates #1379

Merged
merged 13 commits into from
Jun 21, 2022
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ title: Changelog

## main

* Add per-component config option for stripping newlines from templates before compilation.

*Cameron Dutro*

## 2.57.1

* Fix issue causing `NoMethodError`s when calling helper methods from components rendered as part of a collection.
Expand Down
16 changes: 16 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ nav_order: 3

## Class methods

### .strip_trailing_whitespace(value = true)

Strips trailing whitespace from templates before compiling them.

class MyComponent < ViewComponent::Base
camertron marked this conversation as resolved.
Show resolved Hide resolved
strip_trailing_whitespace
end

### .strip_trailing_whitespace? → [Boolean]

Whether trailing whitespace will be stripped before compilation.

### .with_collection(collection, **args)

Render a component for each element in a collection ([documentation](/guide/collections)):
Expand Down Expand Up @@ -253,6 +265,10 @@ render_inline(MyComponent.new)
assert_text("Hello, World!")
```

### #rendered_component → [String]

Returns the result of a render_inline call.

### #with_controller_class(klass)

Set the controller to be used while executing the given block,
Expand Down
16 changes: 16 additions & 0 deletions docs/known_issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,19 @@ Calls to form helpers such as `form_with` in ViewComponents [don't use the defau
<%= f.text_field :name %>
<% end %>
```

## Unwanted whitespace
camertron marked this conversation as resolved.
Show resolved Hide resolved

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.

```ruby
class MyComponent < ViewComponent::Base
# do strip whitespace
strip_trailing_whitespace

# don't strip whitespace
strip_trailing_whitespace(false)
end
```
22 changes: 22 additions & 0 deletions lib/view_component/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class Base < ActionView::Base
class_attribute :content_areas
self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2

# 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
Expand Down Expand Up @@ -539,6 +543,24 @@ def with_collection_parameter(parameter)
@provided_collection_parameter = parameter
end

# Strips trailing whitespace from templates before compiling them.
#
# class MyComponent < ViewComponent::Base
# strip_trailing_whitespace
# end
#
# @param value [Boolean] Whether or not to strip newlines.
def strip_trailing_whitespace(value = true)
self.__vc_strip_trailing_whitespace = value
end

# Whether trailing whitespace will be stripped before compilation.
#
# @return [Boolean]
def strip_trailing_whitespace?
self.__vc_strip_trailing_whitespace
end

# Ensure the component initializer accepts the
# collection parameter. By default, we don't
# validate that the default parameter name
Expand Down
1 change: 1 addition & 0 deletions lib/view_component/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ def variants_from_inline_calls(calls)
def compiled_template(file_path)
handler = ActionView::Template.handler_for_extension(File.extname(file_path).gsub(".", ""))
template = File.read(file_path)
template.rstrip! if component_class.strip_trailing_whitespace?

if handler.method(:call).parameters.length > 1
handler.call(component_class, template)
Expand Down
3 changes: 3 additions & 0 deletions lib/view_component/test_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def refute_component_rendered
# @private
attr_reader :rendered_content

# Returns the result of a render_inline call.
#
# @return [String]
def rendered_component
ViewComponent::Deprecation.warn(
"`rendered_component` is deprecated and will be removed in v3.0.0. " \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello, world!
5 changes: 5 additions & 0 deletions test/sandbox/app/components/trailing_whitespace_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class TrailingWhitespaceComponent < ViewComponent::Base
strip_trailing_whitespace
end
13 changes: 12 additions & 1 deletion test/sandbox/test/rendering_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require "test_helper"

class ViewComponentTest < ViewComponent::TestCase
class RenderingTest < ViewComponent::TestCase
camertron marked this conversation as resolved.
Show resolved Hide resolved
def test_render_inline
render_inline(MyComponent.new)

Expand Down Expand Up @@ -1040,6 +1040,17 @@ def test_inherited_component_calls_super
end
end

def test_component_renders_without_trailing_whitespace
template = File.read(Rails.root.join("app/components/trailing_whitespace_component.html.erb"))
assert template =~ /\s+\z/, "Template does not contain any trailing whitespace"

without_template_annotations do
render_inline(TrailingWhitespaceComponent.new)
end

refute @rendered_content =~ /\s+\z/, "Rendered component contains trailing whitespace"
end

def test_renders_objects_in_component_view_context
not_a_component = RendersNonComponent::NotAComponent.new
component = RendersNonComponent.new(not_a_component: not_a_component)
Expand Down