Skip to content
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

Enforce pseudo-selector ordering by increasing specificity #1667

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

dddlr
Copy link
Collaborator

@dddlr dddlr commented May 2, 2024

Add the enforcePseudoOrder option. This ensures that the pseudo-selectors from Compiled are always applied in the order

  • :link
  • :visited
  • :focus-within
  • :focus
  • :focus-visible
  • :hover
  • :active

even when components are imported from multiple packages. This is at the expense of lengthening those selectors considerably (by adding :not(#\\##\\##\\##\\##\\#) to the end of those selectors).

This is necessary if you use multiple pseudo-selectors on the same component (e.g. :hover and :active), and if you are creating Compiled components that are consumed in other packages or projects.

Another approach we could have taken for this was to use @layer, but this was determined to be impractical as styles in @layer have lower specificity than styles outside @layer, which is risky when mixing Compiled styles with non-Compiled styles. We want Compiled styles to have higher specificity! (This might change in the future depending on whether we care about using Compiled alongside other CSS solutions* or not...)

This is turned off by default. To turn on, set enforcePseudoOrder to true in the Parcel, Webpack, or @compiled/babel-plugin configuration.


Todos for myself

  • Add tests to packages/css/src/plugins/__tests__/enforce-pseudo-order.ts
  • Add changeset
  • Add PR description
  • Double-check that this works in the Webpack and Parcel examples
  • Update @compiled/jest helper function to work with this (and potentially the increaseSpecificity option too)
  • Create a PR on the Compiled website to update docs

*other CSS solutions = other CSS-in-JS libraries, and using plain class names (<div className="hello" />)

Copy link

changeset-bot bot commented May 2, 2024

🦋 Changeset detected

Latest commit: 7e5a666

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@compiled/parcel-app Patch
@compiled/webpack-app Patch
@compiled/parcel-transformer Patch
@compiled/webpack-loader Patch
@compiled/babel-plugin Patch
@compiled/utils Patch
@compiled/css Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Collaborator

@kylorhall-atlassian kylorhall-atlassian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a review on this following #1666, I think this all makes sense in conjunction with that PR. No gaps I see, but I did not fully follow the AST and code; I can give a more thorough review of required after that lands, but nothing stood out worth that review from me today.

packages/jest/src/matchers.ts Show resolved Hide resolved
packages/utils/src/increase-specificity.ts Show resolved Hide resolved
OnceExit(root) {
root.walkRules((rule) => {
rule.selectors = rule.selectors.map((selector) => {
if (selector.includes('._')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't configured or a constant, right? Given the circular dependencies issues in the past, not worth the effort, just making sure it doesn't already exist!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't feel right. We assume Atmoic CSS classname starts with ._. The assumption might not be correct If we change the classname pattern in the future. We might forget about this if condition here. (TBH, I don't know why we would change classname pattern in the future. Maybe restart classname compression)

I think you won't need to check selector.includes('._'), If you do enforcePseudoOrder as part of atomicifyRules.

Copy link
Collaborator Author

@dddlr dddlr May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Move enforcePseudoOrder into the atomicifyRules stage, where we no longer need to worry about accidentally processing non-Compiled selectors

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh hmm i suspect there is no need to move enforcePseudoOrder into atomicifyRules, as all of the PostCSS plugins should only run on Compiled CSS... haven't confirmed this though

  • Confirm that plugins in packages/css/src/plugins/ never process non-Compiled CSS

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, well, yeah something to confirm I'm unsure unfortunately! Would depend on what is picked up in the bundler plugins.


export const getPseudoSelectorScore = (selector: string): number => {
const index = styleOrder.findIndex((pseudoClass) => selector.trim().endsWith(pseudoClass));
return index + 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if you even need the +1? I don't care much!

Copy link
Collaborator Author

@dddlr dddlr May 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yepp i plan to clean up and de-dupe these util functions once the other PR gets merged in

looks like packages/jest/ will need its own copy of these util functions though - i ran into the same problem as you did, where i can't import @compiled/utils from inside @compiled/jest

i'm hesitant to do the cleanup prematurely cuz there is overlap in the files modified across the two PRs, and dealing with merge conflicts is not fun (that was my bad for not coordinating this more efficiently)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Collaborator Author

@dddlr dddlr May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • De-duplicate packages/utils/src/style-ordering.ts

examples/parcel/.compiledcssrc.json Show resolved Hide resolved
@@ -49,6 +51,7 @@ export const transformCss = (
classNameCompressionMap: opts.classNameCompressionMap,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are quite a lot of references of classNameCompression in the code base. Since we don't support this feature, we should clean them up.

I am happy to do it after this PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets not clean up classNameCompression from elsewhere in the codebase yet, as we might use it for future performance projects

@dddlr dddlr force-pushed the feat/pseudo-selector-sorting branch from 5e229cd to 96264ed Compare May 14, 2024 01:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants