-
Notifications
You must be signed in to change notification settings - Fork 428
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
Enable (experimental) single file components #1556
Conversation
What do you mean by a sidecar template? Just "the .html.erb file with the same name as the component .rb file in the same directory"? |
Yep, exactly. |
@fsateler would this satisfy your requirements for multiple templates? |
If Although it seems to me this is punting on the difficult part, which is the arguments. The (not implemented) example would have a lambda taking an argument that returns an ERB string, how would that get turned into |
@fsateler What if we used the arguments approach from your PR, including them in the heredoc? |
I don't think the initial PR should support fragment! :row, accepts: [:post], <<~ERB
<tr>
<td><%= post.title %></td>
<td><%= post.body %></td>
</tr>
ERB We could take the |
Then that would be almost indistiguishable from my PR. A few extra steps if you want a separate file (which is likely if the component is big enough to want to split into multiple fragments).
Honestly, that would be a bit frustrating, as that would probably delay the solving of #387.
That sounds a lot like one of the options we discussed (and discarded): #387 (comment) |
@fsateler @BlakeWilliams why not use the Strict Locals syntax here? It's a solved problem 😄 |
Good point! I think |
It has just occurred to me, will this give proper line numbers in stack traces? |
As-is no, it's something I need to write a test for, but it's definitely doable. |
I just marked this as ready for review. It's pretty minimal, but I think it covers the basics needed to start experimenting with inline templates. We chatted a bit about fragments, but I want to keep that separate from this PR since that solve a separate problem and might require a lot more back-and-forth discussion. I'll tackle that in a follow-up PR assuming this PR gets a 👍. Happy to hear thoughts and feedback on this approach and discuss any specific points folks make. |
|
||
class_methods do | ||
def template!(template) | ||
caller = caller_locations(1..1)[0] |
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.
This makes a small assumption, which is that all template!
calls will be formatted like:
template <<~ERB
ERB
and not
template!(
<<~ERB
ERB
)
The newline is what's problematic here, since we assume that the template will start on the same line as the method call (template!
). I think that's fine, but if it becomes a problem we can provide more "fine grained" options like allowing an offset to be passed
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.
It also cannot be:
template! "<div>Alert!"
tmplt = "asdf"
template! tmplt
def self.generate_template
"a_template"
end
template! generate_template
template! File.read("some_file.html.erb")
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.
I'm not a fan of imposing this constraint. We should accept all valid Ruby.
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.
What if we were to wrap template errors with a message along the lines of "Error on line 3 of MyComponent inline template"
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.
I have some ideas here on how we might be able to improve the error messages, but I'd prefer they weren't blocking. Any opposition to merging this as-is and exploring better error strategies later? This is marked as experimental, so a little bit of churn seems fine here imo.
Looks good! Happy to see an experiment around this. I'm wondering:
|
I was thinking about passing on documentation for the first round, then fill it in once it's been tried out for real in a few spots.
Honestly, no real solid reason. I thought it would be nice to signal "this is happening only once!" but happy to change it if folks feel strongly.
Good call. I think that will be easy to implement for |
This change adds an experimental module, `ViewComponent::InlineTemplate` that allows you to define your templates using a basic DSL directly within your component. This eliminates the need for sidecar templates. This is what it looks like: ```erb class MyComponent < ApplicationComponent include ViewComponent::InlineTemplate template <<~ERB <div> <h1>Hello, <%= @name %>!</h1> </div> ERB def initialize(name:) @name = name end end ``` This results in component being "self-contained", in that a single file can represent the entire component. This makes it easier to understand a component and requires less jumping around between files. This also has benefits like enabling the following, which feels closely related to our multi-template efforts: (this is not implemented, just an example of what this functionality can enable) ```erb class MyComponent < ApplicationComponent include ViewComponent::InlineTemplate template <<~ERB <div> <h1>Hello, <%= @name %>!</h1> <% post.each do.%> <%= render_row(post) %> <% end %> </div> ERB fragment :row, -> (post) { <<~ERB <tr> <td><%= post.title %></td> <td><%= post.body %></td> </tr> ERB } def initialize(name:) @name = name end def posts Post.all end end ```
504d92c
to
dfef286
Compare
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.
LGTM, mind adding docs? Happy to edit heavily if you can get something rough together.
lib/view_component/compiler.rb
Outdated
# Remove existing compiled template methods, | ||
# as Ruby warns when redefining a method. | ||
method_name = call_method_name(template[:variant]) | ||
if component_class.respond_to?(:inline_template) && component_class.inline_template.present? |
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.
Could we use has_inline_template?
here?
Thanks for this! I think this will be really nice for tiny little components. |
Oooh, I really like this! Thanks for keeping DX improvements like this coming! |
* Enable (experimental) single file components This change adds an experimental module, `ViewComponent::InlineTemplate` that allows you to define your templates using a basic DSL directly within your component. This eliminates the need for sidecar templates. This is what it looks like: ```erb class MyComponent < ApplicationComponent include ViewComponent::InlineTemplate template <<~ERB <div> <h1>Hello, <%= @name %>!</h1> </div> ERB def initialize(name:) @name = name end end ``` This results in component being "self-contained", in that a single file can represent the entire component. This makes it easier to understand a component and requires less jumping around between files. This also has benefits like enabling the following, which feels closely related to our multi-template efforts: (this is not implemented, just an example of what this functionality can enable) ```erb class MyComponent < ApplicationComponent include ViewComponent::InlineTemplate template <<~ERB <div> <h1>Hello, <%= @name %>!</h1> <% post.each do.%> <%= render_row(post) %> <% end %> </div> ERB fragment :row, -> (post) { <<~ERB <tr> <td><%= post.title %></td> <td><%= post.body %></td> </tr> ERB } def initialize(name:) @name = name end def posts Post.all end end ``` * Use consistent method names * Add test using slots * add docs * use has_inline_template? method --------- Co-authored-by: Joel Hawksley <[email protected]>
* Enable (experimental) single file components This change adds an experimental module, `ViewComponent::InlineTemplate` that allows you to define your templates using a basic DSL directly within your component. This eliminates the need for sidecar templates. This is what it looks like: ```erb class MyComponent < ApplicationComponent include ViewComponent::InlineTemplate template <<~ERB <div> <h1>Hello, <%= @name %>!</h1> </div> ERB def initialize(name:) @name = name end end ``` This results in component being "self-contained", in that a single file can represent the entire component. This makes it easier to understand a component and requires less jumping around between files. This also has benefits like enabling the following, which feels closely related to our multi-template efforts: (this is not implemented, just an example of what this functionality can enable) ```erb class MyComponent < ApplicationComponent include ViewComponent::InlineTemplate template <<~ERB <div> <h1>Hello, <%= @name %>!</h1> <% post.each do.%> <%= render_row(post) %> <% end %> </div> ERB fragment :row, -> (post) { <<~ERB <tr> <td><%= post.title %></td> <td><%= post.body %></td> </tr> ERB } def initialize(name:) @name = name end def posts Post.all end end ``` * Use consistent method names * Add test using slots * add docs * use has_inline_template? method --------- Co-authored-by: Joel Hawksley <[email protected]>
This change adds an experimental module,
ViewComponent::InlineTemplate
that allows you to define your templates using a basic DSL directly within your component. This eliminates the need for sidecar templates.This is what it looks like:
This results in component being "self-contained", in that a single file can represent the entire component. This makes it easier to understand a component and requires less jumping around between files. This also has benefits like enabling the following, which feels closely related to our multi-template efforts:
(this is not implemented, just an example of what this functionality can enable)