Organizing Sidecar assets #331
-
IntroductionThere has been a lot of discussion on the right way to organize sidecar assets and ruby classes for ViewComponents: #15 (comment), #67, #168, #283. We don't have a clean solution yet but we have some ideas What do we know?
The Problem todaySuppose we have four components:
We can see app/components is already a 'fat' directory and we're only four components in. What would the ideal solution look like?In an ideal world the solution would look much like organizing front end assets where we group each component and its sidecar file in their own directory:
Solutions1. Use AutoloaderAt first glance we can use the auto loader to tell Rails to find our components inside directories (much like Rails eager loads ruby classes in all sub-directories of
This works great except for namespaces. Also add namespaces to the autoloader - nearly works 😄 😞We can add the namespaced components to the autoloader
Now Rails will load the There are other combinations of autoloader configuration, particularly with Zeitwerk in Rails 6 that allow us some flexibility. For a detailed discussion see fxn/zeitwerk#111. However, those solutions require very careful inclusion and exclusion of directories from the autoloader. It's not clear that we could automate the process to detect namespaces etc. Pros:
Cons:
2. Sidecar assets in a component directoryLeave the Ruby code as is but put the sidecar assets in a component specific directory.
The code we'd need here would be to change the component view resolution paths to look in the appropriate sub directory. js, css likely just work - would need to check the details of parent-child Stimulus controllers etc. Pros:
Cons:
3. Sidecar assets in one assets directoryLeave the Ruby code as is but put the sidecar assets in an assets directory with sub directories that match the components directories.
The code we'd need here would be a single change the component view resolution paths to look for views in the assets directory. js, css likely just work. Pros:
Cons:
ProposalI'd lean towards a configuration option to enable 2 unless the actual implementation is sufficiently complicated that 3 looks more appropriate. I'd love for 1 to work after a lot of experimentation I can't see a clean way to bundle it as part of the gem that covers all reasonable use cases without a lot of manual configuration. |
Beta Was this translation helpful? Give feedback.
Replies: 18 comments 10 replies
-
ping @jaredcwhite, @rosendi, @nicolas-brousse, @joelhawksley, @joelmoss Would love to hear your comments and thoughts given you've been deep in the prior conversations and likely have real world experience and thoughts of the pros & cons. |
Beta Was this translation helpful? Give feedback.
-
Solution 2 is pretty much exactly what I've been using for over a year now in a current production app, and it works nicely. Never had any issues, and it's clear where everything is. I'm not actually using ViewComponent, but looking to integrate it, so I obviously want to keep the structure I already have. One difference between this and my current setup, is the naming of the assets. You propose naming these identical to the component name, but I use Index is obviously a well known convention, and avoids duplicating the name everywhere. I also find it easier to identify other files that may exist in the same directory, such as those which are imported (JS), or other related files. |
Beta Was this translation helpful? Give feedback.
-
Hi @jonspalmer Solution 2 sounds the better for me, with the current state of this gem and Rails autoloading. Solutions1. Use AutoloaderAfter having working on this several hours too, I confirm that it's not clean enough as solution to be considered. At least for now. 2. Sidecar assets in a component directoryThe last time I've worked on structuring components with About cons:
I guess it's a small cons.
Does it complicate it that much?
I think I've missed some conversation about this. What is expected with StimulusReflex? 3. Sidecar assets in one assets directoryAbout cons:
When we work in controllers and views, I think we are not often working on both files at the same time. But for the components it may be the case for components, since in components we work in a smaller piece.
Same question that for solution 2.
Same question that for solution 2. |
Beta Was this translation helpful? Give feedback.
-
I'm currently refactoring all the components I have in my new app. I will post my thoughts in a week or so. |
Beta Was this translation helpful? Give feedback.
-
I threw up a quick version of option 2 in #335. There are some questions around supporting partials but the code seems reasonable and not too invasive. |
Beta Was this translation helpful? Give feedback.
-
I would give my arm for option nr 1, it just looks so amazing. Mb it could still be left open as an option for experimental people to try in rails 6? |
Beta Was this translation helpful? Give feedback.
-
Hi all, sorry I'm a bit late jumping into the conversation here. I totally understand the issues surrounding getting Option 1 to work…yet I feel that's exactly what a lot of people will be looking for, especially if they're coming at this from other contexts such as React dev. I'd personally be comfortable with Option 2 as a quicker path forward for now, but I think dropping Option 1 for an indeterminate period of time is unwise. Also, from a "Storybook.js" type perspective, even stories/tests live in the same component folder. I'm not necessarily advocating for that, but it's just another example of the inertia to keep everything about each component within a single folder. |
Beta Was this translation helpful? Give feedback.
-
@mickenorlen & @jaredcwhite I'm in total agreement that Option 1 is what we want. Sadly I just don't think Ruby/Rails supports it in any reasonable way. If you never want to use namespaces it can be made to work great. Whithout namespaces (or subfolders) you don't need that many components before things become unmanageable. Perhaps there is some clever combination of feature requests we can put in for Zeitwerk that would give us the pattern that we want but the conversation in fxn/zeitwerk#111 suggests is unlikely to work "out of the box" |
Beta Was this translation helpful? Give feedback.
-
Maybe something like this could be an option?
Or maybe it could be something like: if a class is not found and its name ends with Component. Make another check for the nested Component? I duno...
So then we could have something like
So now we could reach all of this as expected # With some kind of aliasing/delegation
Form::FormComponent => Form::FormComponent::FormComponent
Form::TextFieldComponent => Form::TextFieldComponent::TextFieldComponent
Form::TextFieldComponent::Js # Will render js variant
Form::TextFIeldComponent::Variation2 # Another variant
Do you think it would be too "unrailsy" to support configuration options such as find_assets_in_same_folder = true/false
implement_shorthand_delegation_namespace = true/false Thanks! |
Beta Was this translation helpful? Give feedback.
-
Is anyone familiar on how React projects organize their component-related assets? According to the docs https://reactjs.org/docs/faq-styling.html it seems like they don't impose anything, in fact they state:
I think that goes for any kind of assets (images, even translations). Given real projects might be different than what the documentation says (#67 (comment)), and the amount of tooling around transpilation and bundling in the JS land, what I wanted to bring to the discussion here is: Do we really want ViewComponent to have a word on this topic? Why not leave the decision to each project/devs? I think we can just provide a section in the readme for some suggestions (like we already do here https://github.com/github/view_component#sidecar-assets-experimental) (and also as React does in their docs). |
Beta Was this translation helpful? Give feedback.
-
I was thinking more if it's possible to hook into the "class not found functionality" to prepend an additional clause to check another class before raising the error. This code would then only be run if the class that is missing ends with
As we've separated namespace folders from component folders, the form namespace is open for any file you like - as long is it doesn't clash with your component names. Couldn't something like this work then? └── form
├── base_component.rb
├── another_base_component.rb
├── form_component
├── another_base_in_here_if_needed.rb
├── index.css
├── index.html.erb
├── index.rb
└── index_controller.js But I understand your points that delegation can be problematic. It seems a potential idea to delegate in the new-call instead. If we could make it work it could be a good option but otherwise I'm thinking delegation may not be the most important thing in the end. I could still be happy simply giving a "name" to the main component namespace, such as index/main/html (example below). I think the key for me is to add that next level of nesting so the directory itself defines the component namespace.
Form::FormComponent::Index # works decently :) Regarding the component variants (#328). I know it's a discussion in itself and I don't know which way is best. For me it just seems to make sense to keep all options open, also then i would be up and running with multiple variants today without having to add on multi template file/format management features and conditionals to the component base/render?/initialize methods etc. I just inherit from index or some base and tweak what i need. Also agree with @juanmanuelramallo that the user could be left more options to decide how to structure these files, within reason of course :) |
Beta Was this translation helpful? Give feedback.
-
Another (possibly strange) idea. When you generate a new component
Then there could be two rather clear cut options for users.
Then you could even decide to have different structures between different components to suit your needs as the special delegator class would handle the configuration for you when needed. And it could take options and inherit from an ApplicationComponentDelegator so you could set it up just the way you like with expected naming etc |
Beta Was this translation helpful? Give feedback.
-
Woah! Look at this simple class alias implementation: If I understand things correctly this could give us another option: class Form::FormComponent::Index < ApplicationComponent
#...
end
Form::FormComponent = Form::FormComponent::Index With wanted structure
And Form::FormComponent.new |
Beta Was this translation helpful? Give feedback.
-
🤯 WOW!!! Thanks for the thorough, thoughtful discussion folks! This is fantastic. Given all that has been said here, I think we should go with:
As it is an incremental change that doesn’t require any changes to the way we’re loading Ruby files. We can build on this approach as time goes on. @jonspalmer how about we start there and see how it feels? I know you have a WIP PR for that approach, so let’s see about getting it ready to merge! |
Beta Was this translation helpful? Give feedback.
-
Most of components (I would say 90%) have only There is a fourth option leave it as is and use modules for grouping components alongside with the flatten folder structure:
Later if
A few month ago I started a new project with 1 option (since it feels like a React way). Later I flattened my components and break them into logical peaces using modules and it feels OK. The only problem is with the UI library because the UI's components can't be grouped somehow: (self-packed) (flattened) I don't think the second option could help here either. |
Beta Was this translation helpful? Give feedback.
-
@rosendi I totally agree! For most cases, modules should do the trick: it's the approach we're taking here at GitHub. The subfolder architecture will be optional, and benefit folks using sidecar CSS and JS, in my opinion. |
Beta Was this translation helpful? Give feedback.
-
That's for all the great discussion. As a first step we're moving along with a simple implementation of option 2. See #335. Overall we're trying to balance a few things:
I'm curious to the last point if there is a way we could provide a configuration mechanism or expose a method to define your own "view resolution" so consumers to use whatever scheme they like. I have't spent much time thinking about it but if others wanted to we'd be happy to consider it. |
Beta Was this translation helpful? Give feedback.
-
Thank you everyone for your thoughtful contributions to this discussion! I'm really happy with the solution we've landed: Thank you to @jonspalmer for taking this on and guiding us to a simple, flexible solution. |
Beta Was this translation helpful? Give feedback.
That's for all the great discussion. As a first step we're moving along with a simple implementation of option 2. See #335.
Overall we're trying to balance a few things: