Skip to content

opt-out mechanism for unavoidable global CSS scenarios #78

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

Closed
jantimon opened this issue Oct 28, 2024 · 5 comments · Fixed by #80
Closed

opt-out mechanism for unavoidable global CSS scenarios #78

jantimon opened this issue Oct 28, 2024 · 5 comments · Fixed by #80

Comments

@jantimon
Copy link
Contributor

postcss-modules-local-by-default enforces pure CSS Modules by requiring all styles to be locally scoped, with :global only being allowed within :local wrappers. Generally this is exactly the desired behavior, but there are several cases where it is hardly possible to use pure CSS Modules:

1. third-party integration scenarios

when integrating third-party services that inject their own DOM nodes with React Portals there are sometimes cases where we need to style elements outside of our component tree:

/* These elements are injected directly into body by third-party scripts */
#stripe-modal-backdrop {
  background: rgba(0, 0, 0, 0.5);
}

2. new platform apis

The View Transitions API is a good example of where pure CSS Modules are very hard to use:

::view-transition-group(my-custom-name) {
  animation-timing-function: ease-in-out;
}

3. global animations

Right now it is not possible to target global animations e.g.:

animation-name: fade-out

Solution in similar tools

One tool which limits the usage of javascript is typescript - but it allows disabling this for edge cases e.g.:

// @ts-ignore
// @ts-expect-error

Proposal:

We could add something similar to css modules to allow explicitly opting-out

/* @cssmodules-pure: false */
::view-transition-group(modal) {
  animation-timing-function: ease-in-out;
}

/* Re-enable pure mode */
/* @cssmodules-pure: true */
.component {
  color: blue;
}

Alternative syntax options could include:

/* @cssmodules-disable-pure */
/* @cssmodules-enable-pure */

/* or */

/* @cssmodules-impure-start */
/* @cssmodules-impure-end */

I believe this would keep the current behaviour of postcss-modules-local-by-default as it is today and also offer an escape hatch in very special edge case scenarios. The advantages might be obvious (because of the learnings from typescript or one of these other tools):

  1. Maintains Security: Explicit opt-outs make it clear when pure modules are being bypassed
  2. Better DX: Developers don't need hacky workarounds for legitimate use cases
  3. Clear Intent: Code reviews can easily identify and validate non-pure usage
  4. Future Proof: Ready for new web platform features that may require global scope

How would you design such an opt-out?
I can help test different approaches and create a PR once we figure out how to move on

@alexander-akait
Copy link
Member

I see your problem, using

/* @cssmodules-pure: false */
::view-transition-group(modal) {
  animation-timing-function: ease-in-out;
}

/* Re-enable pure mode */
/* @cssmodules-pure: true */
.component {
  color: blue;
}

potentially has performance issues when searching for comments (binary search using ranges for start and end, not all module implementations use AST), that's why ts doesn't have them and have only complete disable for file or disable by lines, since this is quite a rare case and tracking the start and end can take quite a bit of time I would suggest going the other way and using only inline comments:

/* @cssmodules-pure-ignore */
#stripe-modal-backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.test,
.foo, 
.bar,
/* @cssmodules-pure-ignore */
#stripe-modal-backdrop {
  background: rgba(0, 0, 0, 0.5);
}

@jantimon
Copy link
Contributor Author

thanks @alexander-akait for the feedback!

Good point with the parsing - haven't thought about that.

I see two ways @cssmodules-pure-ignore (similar to typescript) or a little bit more explicit @cssmodules-pure-ignore-next-line like most linters

/* @cssmodules-pure-ignore */
#stripe-modal-backdrop {
  background: rgba(0, 0, 0, 0.5);
  /* @cssmodules-pure-ignore */
  animation-name:  foo;
}

/* or */

/* @cssmodules-pure-ignore-next-line */
#stripe-modal-backdrop {
  background: rgba(0, 0, 0, 0.5);
  /* @cssmodules-pure-ignore-next-line */
  animation-name:  foo;
}

I'll start working on a PR and will try to implement the basic ignore logic

Please let me know once you chose the best fitting comment

@alexander-akait
Copy link
Member

@jantimon Looks good for me

@jaggli
Copy link

jaggli commented Oct 31, 2024

Following some popular libraries among styling and linting, I suggest to skip the @ character.

/* stylelint-disable selector-max-id, declaration-no-important */
// eslint-disable-next-line no-use-before-define
<!-- prettier-ignore-attribute (mouseup) -->

@alexander-akait
Copy link
Member

I am fine without @ too

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 a pull request may close this issue.

3 participants