-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Per-component classnames #227
Conversation
Generated by 🚫 dangerJS |
Nevermind babel-transform is for SSR. Re: performance. A fast fix for the performance is the ephermal helper for now. But I do agree this is the better solution. |
Using something like a UUID would be like |
I've written a first babel plugin for this! https://github.com/styled-components/babel-plugin-styled-components Go to |
For some reason this is throwing lots of these errors: |
Nah I didn't even run the tests while i was sketching this out, not surprised I broke everything :) |
These test failures are actually not really failures, they just happened because we now call the name generator (which we patch for the tests) on component creation as well as at rendering time, which didn't happen before, so now all classes are shifted by one. Fixed by manually shifting them back.
To add support for server-side rendering, it needs to be possible to pass in an identifier from the outside. This is a private API because the only thing ever writing this is babel-plugin-styled-components, which will automatically turn your normal styled components into the ssr compatible version by turning your styled() calls into this notation: styled({ target: 'div', identifier: 'YourComponent-asdf123', displayName: 'YourComponent' }) This commit adds support for this notation.
With the new private API, all the IIFEs are unnecessary since we can just pass an object to the styled() calls. This commit updates the plugin and the tests to reflect the new, much better behaviour. This way we can take full advantage of styled-components/styled-components#227 when it's merged!
So I think the last thing for this to be ready is that extending/inheritance is completely broken for some reason unknown to me. The tests fail in very weird ways, not applying the classes how you'd expect them to. Any ideas what's up with that? |
@mxstbr babel-plugin = works with SSR? |
Yes, and better debugging (because it adds the |
Do you mean by "not applying the classes how you'd expect them to" that the class name is always one extra time incremented. First in StyledComponent.js line 115 Then in componentWillMount line 65 Or I misunderstand something. And for the first extending error I guess it is because both are with the same identifier and as no style. they should have the same hash. So only one So It looks all normal... |
No, I'm aware of that issue and fixed all tests that broke because of that. The issue is with the |
templateFunction.classes = classes => expandedApi(tag, { ...options, classes }) | ||
templateFunction.componentId = componentId => expandedApi(tag, { ...options, componentId }) | ||
templateFunction.displayName = displayName => expandedApi(tag, { ...options, displayName }) | ||
templateFunction.css = templateFunction // pure sugar |
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.
Tasty, tasty sugar! 😃
This is really, really cool! I think it’d definitely fill the gap I’ve found which led to #291! 👍 |
…name-generation Conflicts: CHANGELOG.md
Ok, in the interests of getting this shipped, I've pulled this back to a single extra method const X = styled.div`
...
`
/* transformed to */
const X = styled.div.withConfig({ displayName: 'X', componentId: 'X_a1b2c3d4' })`
...
` This PR can now be merged and will close #142 and #160 and enable deterministic SSR, so it'll be good to get this shipped. I had hoped to solve |
Conflicts: CHANGELOG.md src/constructors/styled.js yarn.lock
Ok, kicking of the |
With the new private API, all the IIFEs are unnecessary since we can just pass an object to the styled() calls. This commit updates the plugin and the tests to reflect the new, much better behaviour. This way we can take full advantage of #227 when it's merged!
Super hacky proof of concept, but something's been bugging me for a while about Styled Components — we don't have any classnames that are always going to be attached on a component. This is the key problem with supporting #142 but it also causes a few DX annoyances, prevents an optimisation, and presents one nasty af potential bug that nobody has hit yet...
One annoyance is that, in dev tools, you don't have a single class hook to tweak settings if you have several instances of a component with different props.
<Button primary>
and<Button>
will generate totally different classnames, so you can't jump in to dev tools and tweak all the buttons. If they share a classname, you can.It also means that #160 is harder than it should be. If there's a unique class for every Styled Component then in dev you can make that whatever you like. 5 internet high fives for the person who implements BEM-style
.app_components_Logo_Inner
classnames using the directory structure 👍 , ping @markdalgleish.The performance optimisation has to do with #134. If we had a single shared classname for all instances of a SC then we could potentially lift all the static styles into them. Then, when props change, we're injecting just the styles that depend on props. I'm not convinced this will be a massive gain but it certainly would map better to how people think about their styles.
And finally the nasty af bug. Consider this setup:
The problem arises from the internal cache about rendered styles. If two components end up producing the exact same CSS, we'll only inject a single class. Which means in cases where source-order is important (two rules applying with equal specificity) the result can be unpredictable. But, without a legitimate per-component identifier, the alternative (render the same selector twice) is actually kinda worse, since it would break components you're not looking at. In the above example, rendering a single
<B selected>
would change all the<A className="override">
on the page to blue!! 😱So, it's something I've wanted for a while, but the problem is there's no deterministic information from which to derive a per-component identifier. This PR uses a simple counter, so the 5th time you call
styled.div
you'll get a classname based on a hash ofdiv
and5
(which is.sc-ioLzpW
). If you have two components withdisplayName = 'Inner'
, the first will get a classname of.Inner-jaWsLN
and the second.Inner-dBMIyz
. Which works really really well, replaces #160 and made #142 a one-line change 😎Note the empty
.Inner-jaWsLN
tag in dev tools. Makes it really to tweak all instances at once 👍The only issue is SSR, which we're about to land support for in #214. On the server, components for different pages might all be loaded, and so relying on order-of-instantiation will cause a checksum mismatch on the client. Not good.
So I've built this with a potential babel transform in mind, which would go through all StyledComponents instances and generates a deterministic
identifier
, much the way babel-plugin-styled-components-named generatesdisplayName
s. If that bundle is sent down to the client the counter won't be used and the checksum will match.(Note by @mxstbr: a first experimental version of this babel transform exists now! See
babel-plugin-styled-components
)I don't really have any time before CSS Conf AU next week to go any further with this, but as a proof of concept it really does prove the concept. We can bikeshed the generated classnames but I'm pretty convinced per-component classnames are the way to go. Anyone keen to take this and run with it?