-
Notifications
You must be signed in to change notification settings - Fork 98
Extending Styles #210
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
base: master
Are you sure you want to change the base?
Extending Styles #210
Conversation
This seems in conflict with the principles here - ie, the idea is that nothing but the component itself has any direct control over its own styling. |
Spoke w/ @ljharb. Sounds like this is a reasonable approach with some tweaks (although the need to extend styles could be solved at a design level). The component would continue to maintain control over its own styling - "it’s effectively performant sugar for making explicit props to allow styles to be controlled'". 🍬 🥅 Goals
⚙️ Tweaks
🛠 Future Tooling
|
Validate extending styles
I'd love to get @janpaul123's perspective on this! |
@ljharb @lencioni New API:
|
Co-Authored-By: TaeKimJR <[email protected]>
…t-with-styles into tkjr-custom-styles
I ran some performance tests to compare Given these results, I feel that the goals have been met. (on master) Creating and rendering a Component wrapped in (on this branch) Creating and rendering a Component wrapped in Creating and rendering a Component wrapped in Creating and rendering a Component wrapped in You can see the code I used to run the perf tests here. |
Co-Authored-By: TaeKimJR <[email protected]>
…t-with-styles into tkjr-custom-styles
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.
Let's get as many stamps on this as possible before merging it, but i think all my concerns are addressed. Thanks for iterating!
container: { | ||
color: (value, theme) => true, | ||
background: (value, theme) => value === theme.color.primary || value === theme.color.secondary | ||
}, |
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 would be really great to have an eslint rule that ensures that the shape of the extendableStyles
object stays in sync with the shape of the styles object.
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.
Created a rule for the above.
airbnb/eslint-plugin-react-with-styles#37
extendableStyles: { | ||
container: { | ||
color: (value, theme) => true, | ||
background: (value, theme) => value === theme.color.primary || value === theme.color.secondary |
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 would be good if there was a babel plugin to remove these validator functions in production builds.
|
||
You can extend a Component that already extending another Component. All validation will still occur. | ||
```jsx | ||
const NestedExtendedMyComponent = ExtendedMyComponent.extendStyles(() => ({ |
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 know that TypeScript currently has some troubles with HOCs, but it looks like it might be getting better soon (microsoft/TypeScript#30215). Have you given any thought into how this might work with TypeScript?
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.
Yeah! I think TS's ability to autocomplete and validate arguments will be super nice with extendStyles
. I'm sure we could re-use a lot of the PropsType
work that Brie has already setup and apply it to extendStyles
argument. This should be a super nice for developers so they don't have to dig into the component to know which styles can be extended.
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 love this new direction. This such a needed feature. Thanks for all the work to drive this @TaeKimJR
I was chatting with @majapw about this yesterday and I think it would be really valuable to sketch out what a workable compile target for static CSS would look like with this change in place. In other words, in a world where we are compiling all of react-with-styles out at build time and generating static .css files, what would the module with extendable styles end up looking like and what would the module that is extending files end up looking like? I think @wonnage and @monokrome would be good allies on helping you sketch this out. |
const baseStyle = baseStyleFn(theme); | ||
const extendingStyle = extendingStyleFn(theme); | ||
|
||
validateStyle(extendingStyle, extendableStyles, theme); |
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.
Can you think of a good way to surface this validation before runtime (e.g. in the editor and at build time)--maybe via TypeScript or ESLint? Most linter rules don't cross module boundaries, but there are some in eslint-plugin-import that do.
@lencioni Thanks for the feedback! I've worked with @monokrome on building out a plan for static analysis. I think the general gist is...
|
I think the main things to think through there are:
|
I think we take advantage of ordering in the stylesheet here. I spoke w/ @monokrome about this, and it sounds like the order that the files are loaded into the loader should go from |
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 🤘
If we are already generating a className for the I think I get what you are hinting at though. If we can generate a single className, we don't have to worry about specificity/ordering and we can de-dupe. This is would be an awesome goal, but may be a future goal (would be a behind-the-scenes enhancement). |
I think that might not always be the case, since the base component might be built into a different chunk (and thus a different stylesheet) than the extending component and its styles. At this point the order would depend partly on how the bundler decides to split things up and the order it puts them in, and potentially in the order that they happen to be downloaded by the browser. It might be a good idea to have the safety of specificity built into the selectors themselves to avoid having to rely on the order they happen to end up in.
One option would be to generate a single className, but I think we should avoid that because it will cause a lot of duplicated styles in the build stylesheets. Instead, I think we should aim at something that gets the specificity correct for extending classNames, regardless of ordering. |
|
||
expect(WrappedComponent.extendStyles).to.equal(undefined); | ||
}); | ||
}); |
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.
Similar to airbnb/eslint-plugin-react-with-styles#37 (comment)
Can we add some valid and invalid test cases for nested styles? Media queries, @supports
queries, pseudo-elements, pseudo-selectors, etc.
I keep running into the same issue w/ specificity where psuedo-classes are overwritten (or not overwritten) based on ordering. I put together a fiddle to highlight my thoughts. Are there good ways of handling psuedo-classes? |
Will hold-off on merging until we can solve for static analysis. There are concerns around ordering of stylesheets for chunks. |
@TaeKimJR @lencioni Some thoughts on static build:
|
It's a common use-case to re-use a component's styles but make slight stylistic changes related to your product, business, etc.
react-with-styles
doesn't support this... until this PR! Extending styles is a common feature in css-in-js solutions (styled-components) and provides the necessary flexibility without bloating components.Extending styles within reason
Styles on a component should be extendable... within reason! We do not want to open up the entire
className
andstyle
props on components, those flood gates should remain tightly sealed. When the component can be entirely re-styled, any internal changes to a component could break unknown external usages.In order to allow a style to be extended, it must be defined in the
extendableStyles
option that gets passed to thewithStyles
call on the component.Any styles that are later extended but are not explicitly defined here, will throw an error (in dev).
Deeply nested extension
It is possible to extend a component that has already been extended. This way you can build up a component level by level, slowly piecing together the styles you care about.
Notice that
ButtonWithColorAndBackground
extends upon the already extendingButtonWithColor
.API / Usage
Creating a Base Component
Extend a Component's Styles
Invalid Extension of Component's Styles