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*

* Add link to article by Matouš Borák.

*Joel Hawksley*
Expand Down
68 changes: 55 additions & 13 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,35 @@ nav_order: 3

## Class methods

### .strip_trailing_whitespace(value = true)

Strips trailing whitespace from templates before compiling them.

```ruby
class MyComponent < ViewComponent::Base
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)):

render(ProductsComponent.with_collection(@products, foo: :bar))
```ruby
render(ProductsComponent.with_collection(@products, foo: :bar))
```

### .with_collection_parameter(parameter)

Set the parameter name used when rendering elements of a collection ([documentation](/guide/collections)):

with_collection_parameter :item
```ruby
with_collection_parameter :item
```

## Instance methods

Expand Down Expand Up @@ -79,8 +97,10 @@ Returns HTML that has been escaped by the respective template handler.
Subclass components that call `super` inside their template code will cause a
double render if they emit the result:

<%= super %> # double-renders
<% super %> # does not double-render
```erb
<%= super %> # double-renders
<% super %> # does not double-render
```

Calls `super`, returning `nil` to avoid rendering the result twice.

Expand Down Expand Up @@ -110,7 +130,9 @@ _Will be removed in v3.0.0._

Parent class for generated components

config.view_component.component_parent_class = "MyBaseComponent"
```ruby
config.view_component.component_parent_class = "MyBaseComponent"
```

Defaults to nil. If this is falsy, generators will use
"ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
Expand All @@ -132,25 +154,33 @@ stated.

Always generate a component with a sidecar directory:

config.view_component.generate.sidecar = true
```ruby
config.view_component.generate.sidecar = true
```

#### #stimulus_controller

Always generate a Stimulus controller alongside the component:

config.view_component.generate.stimulus_controller = true
```ruby
config.view_component.generate.stimulus_controller = true
```

#### #locale

Always generate translations file alongside the component:

config.view_component.generate.locale = true
```ruby
config.view_component.generate.locale = true
```

#### #distinct_locale_files

Always generate as many translations files as available locales:

config.view_component.generate.distinct_locale_files = true
```ruby
config.view_component.generate.distinct_locale_files = true
```

One file will be generated for each configured `I18n.available_locales`,
falling back to `[:en]` when no `available_locales` is defined.
Expand All @@ -159,7 +189,9 @@ falling back to `[:en]` when no `available_locales` is defined.

Always generate preview alongside the component:

config.view_component.generate.preview = true
```ruby
config.view_component.generate.preview = true
```

Defaults to `false`.

Expand Down Expand Up @@ -193,7 +225,9 @@ Defaults to `/rails/view_components` when `show_previews` is enabled.

Set if render monkey patches should be included or not in Rails <6.1:

config.view_component.render_monkey_patch_enabled = false
```ruby
config.view_component.render_monkey_patch_enabled = false
```

### #show_previews

Expand All @@ -215,7 +249,9 @@ Defaults to `false`.

Set the controller used for testing components:

config.view_component.test_controller = "MyTestController"
```ruby
config.view_component.test_controller = "MyTestController"
```

Defaults to ApplicationController. Can also be configured on a per-test
basis using `with_controller_class`.
Expand All @@ -224,7 +260,9 @@ basis using `with_controller_class`.

Path for component files

config.view_component.view_component_path = "app/my_components"
```ruby
config.view_component.view_component_path = "app/my_components"
```

Defaults to `app/components`.

Expand Down Expand Up @@ -253,6 +291,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/guide/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,19 @@ To render a parent component's template from a subclass, call `render_parent`:
<% render_parent %>
</div>
```

## Trailing whitespace

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
```
74 changes: 61 additions & 13 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 @@ -135,8 +139,10 @@ def perform_render
# Subclass components that call `super` inside their template code will cause a
# double render if they emit the result:
#
# <%= super %> # double-renders
# <% super %> # does not double-render
# ```erb
# <%= super %> # double-renders
# <% super %> # does not double-render
# ```
#
# Calls `super`, returning `nil` to avoid rendering the result twice.
def render_parent
Expand Down Expand Up @@ -315,7 +321,9 @@ def content_evaluated?

# Set the controller used for testing components:
#
# config.view_component.test_controller = "MyTestController"
# ```ruby
# config.view_component.test_controller = "MyTestController"
# ```
#
# Defaults to ApplicationController. Can also be configured on a per-test
# basis using `with_controller_class`.
Expand All @@ -325,21 +333,27 @@ def content_evaluated?

# Set if render monkey patches should be included or not in Rails <6.1:
#
# config.view_component.render_monkey_patch_enabled = false
# ```ruby
# config.view_component.render_monkey_patch_enabled = false
# ```
#
mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true

# Path for component files
#
# config.view_component.view_component_path = "app/my_components"
# ```ruby
# config.view_component.view_component_path = "app/my_components"
# ```
#
# Defaults to `app/components`.
#
mattr_accessor :view_component_path, instance_writer: false, default: "app/components"

# Parent class for generated components
#
# config.view_component.component_parent_class = "MyBaseComponent"
# ```ruby
# config.view_component.component_parent_class = "MyBaseComponent"
# ```
#
# Defaults to nil. If this is falsy, generators will use
# "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
Expand All @@ -355,25 +369,33 @@ def content_evaluated?
#
# Always generate a component with a sidecar directory:
#
# config.view_component.generate.sidecar = true
# ```ruby
# config.view_component.generate.sidecar = true
# ```
#
# #### #stimulus_controller
#
# Always generate a Stimulus controller alongside the component:
#
# config.view_component.generate.stimulus_controller = true
# ```ruby
# config.view_component.generate.stimulus_controller = true
# ```
#
# #### #locale
#
# Always generate translations file alongside the component:
#
# config.view_component.generate.locale = true
# ```ruby
# config.view_component.generate.locale = true
# ```
#
# #### #distinct_locale_files
#
# Always generate as many translations files as available locales:
#
# config.view_component.generate.distinct_locale_files = true
# ```ruby
# config.view_component.generate.distinct_locale_files = true
# ```
#
# One file will be generated for each configured `I18n.available_locales`,
# falling back to `[:en]` when no `available_locales` is defined.
Expand All @@ -382,7 +404,9 @@ def content_evaluated?
#
# Always generate preview alongside the component:
#
# config.view_component.generate.preview = true
# ```ruby
# config.view_component.generate.preview = true
# ```
#
# Defaults to `false`.
mattr_accessor :generate, instance_writer: false, default: ActiveSupport::OrderedOptions.new(false)
Expand Down Expand Up @@ -436,7 +460,9 @@ def _sidecar_files(extensions)

# Render a component for each element in a collection ([documentation](/guide/collections)):
#
# render(ProductsComponent.with_collection(@products, foo: :bar))
# ```ruby
# render(ProductsComponent.with_collection(@products, foo: :bar))
# ```
#
# @param collection [Enumerable] A list of items to pass the ViewComponent one at a time.
# @param args [Arguments] Arguments to pass to the ViewComponent every time.
Expand Down Expand Up @@ -532,13 +558,35 @@ def identifier

# Set the parameter name used when rendering elements of a collection ([documentation](/guide/collections)):
#
# with_collection_parameter :item
# ```ruby
# with_collection_parameter :item
# ```
#
# @param parameter [Symbol] The parameter name used when rendering elements of a collection.
def with_collection_parameter(parameter)
@provided_collection_parameter = parameter
end

# Strips trailing whitespace from templates before compiling them.
#
# ```ruby
# 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
Loading