-
Notifications
You must be signed in to change notification settings - Fork 443
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
Feature/multiple templates arguments #1096
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,88 @@ | ||||||||||||||
# 1. Allow multiple templates | ||||||||||||||
|
||||||||||||||
Date: 2021-10-13 | ||||||||||||||
|
||||||||||||||
## Status | ||||||||||||||
|
||||||||||||||
Proposed. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
## Context | ||||||||||||||
|
||||||||||||||
As components become larger (for example, because you are implementing a whole page), it becomes | ||||||||||||||
useful to be able to extract sections of the view to a different file. ActionView has | ||||||||||||||
partials, and ViewComponent lacks a similar mechanism. | ||||||||||||||
Comment on lines
+11
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
ActionView partials have the problem that their interface is not introspectable. Data | ||||||||||||||
may be passed into the partial via ivars or locals, and it is impossible to know | ||||||||||||||
which without actually opening up the file. Additionally, partials are globally | ||||||||||||||
invocable, thus making it difficult to detect if a given partial is in use or not, | ||||||||||||||
and who are its users. | ||||||||||||||
Comment on lines
+15
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
## Considered Options | ||||||||||||||
|
||||||||||||||
* Introduce component partials to components | ||||||||||||||
* Keep components as-is | ||||||||||||||
jsolas marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+23
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's just remove these lines in favor of the section headers below.
Suggested change
|
||||||||||||||
|
||||||||||||||
### Component partials | ||||||||||||||
|
||||||||||||||
Allow multiple ERB templates available within the component, and make it possible to | ||||||||||||||
invoke them from the main view.Templates are compiled to methods in the format `render_#{template_basename}(locals = {})` | ||||||||||||||
Comment on lines
+28
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
**Pros:** | ||||||||||||||
* Better performance due to lack of GC pressure and object creation | ||||||||||||||
* Reduces the number of components needed to express a more complex view. | ||||||||||||||
* Extracted sections are not exposed outside the component, thus reducing component library API surface. | ||||||||||||||
|
||||||||||||||
**Cons:** | ||||||||||||||
* Another concept for users of ViewComponent to learn and understand. | ||||||||||||||
* Components are no longer the only way to encapsulate behavior. | ||||||||||||||
|
||||||||||||||
### Partial components by [fstaler](https://github.com/fsateler) | ||||||||||||||
|
||||||||||||||
In this approach the template arguments are previously defined in the `template_arguments` method | ||||||||||||||
|
||||||||||||||
[See here](https://github.com/github/view_component/pull/451): | ||||||||||||||
|
||||||||||||||
**Pros:** | ||||||||||||||
* Better performance due to lack of GC pressure and object creation | ||||||||||||||
* Reduces the number of components needed to express a more complex view. | ||||||||||||||
* Extracted sections are not exposed outside the component, thus reducing component library API surface. | ||||||||||||||
|
||||||||||||||
**Cons:** | ||||||||||||||
* Another concept for users of ViewComponent to learn and understand. | ||||||||||||||
* Components are no longer the only way to encapsulate behavior. | ||||||||||||||
* `call_foo` api feels awkward and not very Rails like | ||||||||||||||
* Declare templates and their arguments explicitly before using them | ||||||||||||||
|
||||||||||||||
### Keeping components as-is | ||||||||||||||
|
||||||||||||||
**Pros:** | ||||||||||||||
* The API remains simple and components are the only way to encapsulate behavior. | ||||||||||||||
* Encourages creating reusable sub-components. | ||||||||||||||
|
||||||||||||||
**Cons:** | ||||||||||||||
* Extracting a component results in more GC and intermediate objects. | ||||||||||||||
* Extracting a component may result in tightly coupled but split components. | ||||||||||||||
* Creates new public components thus expanding component library API surface. | ||||||||||||||
|
||||||||||||||
## Decision | ||||||||||||||
|
||||||||||||||
We will allow having multiple templates in the sidecar asset. Each asset will be compiled to | ||||||||||||||
it's own method `render_<template_name>`. I think it is simple, similar to rails and meets what is expected | ||||||||||||||
Comment on lines
+70
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
## Consequences | ||||||||||||||
|
||||||||||||||
This implementation has better performance characteristics over both an extracted component | ||||||||||||||
and ActionView partials, because it avoids creating intermediate objects, and the overhead of | ||||||||||||||
creating bindings and `instance_exec`. | ||||||||||||||
Having explicit arguments makes the interface explicit. | ||||||||||||||
Comment on lines
+75
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
TODO: The following are consequences of the current approach, but the approach might be extended | ||||||||||||||
to avoid them: | ||||||||||||||
|
||||||||||||||
The interface to render a sidecar partial would be a method call, and depart from the usual | ||||||||||||||
`render(*)` interface used in ActionView. | ||||||||||||||
|
||||||||||||||
The generated methods cannot have arguments with default values. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, but they can be simulated by the view. |
||||||||||||||
|
||||||||||||||
The generated methods are public, and thus could be invoked by a third party. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be changed, but not sure if it's worth it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be nice to introduce them as private to start. |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -36,6 +36,10 @@ title: Changelog | |||||
|
||||||
*Hans Lemuet* | ||||||
|
||||||
* Add support for multiple templates. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
*Rob Sterner*, *Joel Hawksley* | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
## 2.40.0 | ||||||
|
||||||
* Replace antipatterns section in the documentation with best practices. | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -92,3 +92,36 @@ Component subclasses inherit the parent component's template if they don't defin | |||||
class MyLinkComponent < LinkComponent | ||||||
end | ||||||
``` | ||||||
|
||||||
## Multiple templates with arguments | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
ViewComponents can render multiple templates defined in the sidecar directory and send arguments to it: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
```console | ||||||
app/components | ||||||
├── ... | ||||||
├── test_component.rb | ||||||
├── test_component | ||||||
| ├── list.html.erb | ||||||
| └── summary.html.erb | ||||||
├── ... | ||||||
``` | ||||||
|
||||||
Templates are compiled to methods in the format `render_#{template_basename}(locals = {})`, which can then be called in the component. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
```ruby | ||||||
class TestComponent < ViewComponent::Base | ||||||
def initialize(mode:) | ||||||
@mode = mode | ||||||
end | ||||||
|
||||||
def call | ||||||
case @mode | ||||||
when :list | ||||||
render_list number: 1 | ||||||
when :summary | ||||||
render_summary string: "foo" | ||||||
end | ||||||
end | ||||||
end | ||||||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -331,7 +331,7 @@ def _sidecar_files(extensions) | |
# view files in the same directory as the component | ||
sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"] | ||
|
||
sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"] | ||
sidecar_directory_files = Dir["#{directory}/#{component_name}/*.*{#{extensions}}"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if there is a slot/subcomponent with a template in the Sidecar directory? How can we prevent/manage conflicts between the two? |
||
|
||
(sidecar_files - [source_location] + sidecar_directory_files + nested_component_files).uniq | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# frozen_string_literal: true | ||
|
||
class MultipleTemplatesComponent < ViewComponent::Base | ||
def initialize | ||
@items = ["Apple", "Banana", "Pear"] | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<ul data-number="<%= locals[:number] %>"> | ||
<% @items.each do |item| %> | ||
<li><%= item %></li> | ||
<% end %> | ||
</ul> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<div class="container"> | ||
<%= render_summary string: "foo" %> | ||
<%= render_list number: 1 %> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<div>The items are: <%= @items.to_sentence %>, <%= locals[:string] %></div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a name for this feature, I think. What about: