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

Proposal/RFC scoped styles #539

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open

Conversation

tomalec
Copy link
Member

@tomalec tomalec commented May 2, 2021

This is an experiment/proposal/RFC for a style scoping solution to address #65

  • leaking & conflicting styles,
  • class names conflicts
  • CSS-in-JS ergonomics

(Could be considered a native alternative to libraries like styled-components / emotion #71)

This change plays well with #538, but could be totally separated.

Live demo

https://codesandbox.io/s/scoped-shadow-styles-for-react-82wrh?file=/src/components/QuoteCard.js

Changes proposed in this Pull Request:

Encapsulate ContentButtonLayout's styles.
Move them as inline styles to element's shadow root.

Add useShadowStyles hook, to separate style encapsulation logic.

I'm using inline styles here to simplify the PR, we can use CSSStyleSheets but making them work in all browsers https://caniuse.com/mdn-api_cssstylesheet_replacesync would require a little bit more code that could blur the picture. I guess with our heavy cannon of webpack, generating stylesheet objects, adding css imports, adding them as chunked styles, should be a big deal.

See styleSheet version at fix/65-encapsulated-stylesheets

Please note, that components children are still rendered in the light DOM, so are accessible for external use, styling, and manipulation as there were before.

Screenshots:

Screenshot of devtools showing :host styles for ContentButtonLayout

Detailed test instructions:

  1. Go to any page that uses ContentButtonLayout component, like https://gla1.test/wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fsetup-ads
  2. Open dev tools, highlight the element.

Pros:

  1. Styles do not leak. No other element would be styled with them, no matter what.
  2. Element could be still easily styled/themed/specialized, with regular light DOM styles.
  3. There is no class name to collide with something else.
  4. Better DevX:
    1. Clear distinction, which styles pertains to the class which are instance-specific
      Shadow, :host ... selector styles are the class ones - applies to all instances.
      .gla-some-class indicates a special subset of instances to be selected.
    2. You don't have to think about the hardest thing in programming - a new class name.
    3. You can use CSS-in-JS to build, import, etc. your CSS string, or CSSStyleSheet object
    4. We can have single-file components (CSS and JS together), as well as the distributed cases.
    5. It's a native Web platform feature - any browser tooling will work to support it.
  5. Forever backward-compatibility (native Web Feature)
  6. No dependencies.

Cons:

  1. IE and older browsers are not supported, at least without polyfill. https://caniuse.com/shadowdomv1

Fields for improvements / further exploration

  1. I'm not a React expert, but I guess we could add some sugar on top, not to have to hardwire ref manually.
  2. I don't know how React serializes markup for SSR. This should be fully serializable for Chromium with Declarative Shadow DOM. For other browsers, we could probably shim it with some selectors. Probably we will have to add few lines that dump shadowRoot styles back to HTML.
  3. We need to take care of the fact that shadow host styles have lower origin importance than light dom ones. That's basically what we want, but we need to be aware, that it means we are slightly easier to be overwritten than overwrite.
  4. We could consider adding more content to the shadow root, to be able to have encapsulated styles for more than :host and ::slotted( foo )

Move them as inline styles to element's shadow root.

Add `useShadowStyles` hook,  to separate style encapsulation logic.

Addresses #65
@tomalec tomalec requested review from ecgan and a team May 2, 2021 18:03
@tomalec tomalec self-assigned this May 2, 2021
@ecgan
Copy link
Member

ecgan commented May 5, 2021

So I've took at the code in this PR and I'm not really sure / kind of torn about it. 💅 (Personal opinion ahead)

  • Personally I feel that I prefer CSS-in-JS solutions, which also gives most of the Pros mentioned in this PR. And CSS-in-JS solutions can work with IE and older browsers.
  • When I look at the Browser Dev Tool's Element panel, the shadow root feels like noise to me. (Maybe it's just me not getting used to seeing it yet.)

image

Also, I found a third party library that seem to do similar thing: https://github.com/Wildhoney/ReactShadow, with 961 GitHub stars. It mentions SSR too. At the bottom of the page, it has a link to @tomalec's https://tomalec.github.io/declarative-shadow-dom/ 😄

Similar to #538, the ultimate question: Do we want to merge this in and release this? Are we comfortable with this in production? If there is some sort of prior art within WooCommerce / WordPress, then that would give us more confidence on this. (Personally, because of the opinions above, I feel more reluctant on this PR than #538.)

@tomalec
Copy link
Member Author

tomalec commented May 5, 2021

Thanks, for the review and your opinions :)

Personally I feel that I prefer CSS-in-JS solutions, which also gives most of the Pros mentioned in this PR. And CSS-in-JS solutions can work with IE and older browsers.

Have you tried fix/65-encapsulated-stylesheets? What makes this PR not be a CSS-in-JS solution, or at least not CSS-in-JS-compatible? I believe it's one, but just native, forever-backward-compatible, with 0 dependencies and framework agnostic.

With this PR, you can do, things like:

  1. Import styles as modules:
    import { style as buttonStyle } from '@wcdl/button';
    
    const ContentButtonLayout = ( props ) => (
        <div ref={ useShadowStyles( null, [ buttonStyle ] ) } { ...props } />
    );
  2. Use template literals to construct your styles:
    /fix/65-encapsulated-stylesheets#diff-R7-R17
    const sheet = new CSSStyleSheet(); 
    sheet.replaceSync( /* css */ `:host {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: calc(var(--main-gap) / 2);
    }` );
    
    
    const ContentButtonLayout = ( props ) => (
        <div ref={ useShadowStyles( null, [ sheet ] ) } { ...props } />
    );
    Or with tagged template sugar:
    const sheet =  css`
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: calc(var(--main-gap) / 2);
    `;

@tomalec
Copy link
Member Author

tomalec commented May 5, 2021

When I look at the Browser Dev Tool's Element panel, the shadow root feels like noise to me. (Maybe it's just me not getting used to seeing it yet.)

Again, have you checked also the fix/65-encapsulated-stylesheets?

Screenshot with shadow root with no inline style

Probably you're not used to working with pages that use native Shadow DOM (ones that could disregard IE, or even other browsers before 2018).

Try setting your Chrome dev tools preferences:
Chrome preferences: "Show user agent shadow DOM"

And you will see it everywhere 😈
Youtube video element with shadow root

Speaking on the noise you will see reduced noise when it comes to the classes:

  • fewer classes need to be attached to the HTML elements. Compare the selected div with the div above, in the first picture.
  • Cleaner styles section, as you can easily distinguish :host ones from and .class - that gives you a semantic separation from "This is how the element styles itself" vs. "This is somebody from the outside would like it to look", and you simply get less of classes overwriting each other:
    image

@tomalec
Copy link
Member Author

tomalec commented May 5, 2021

Also, I found a third party library that seem to do similar thing: Wildhoney/ReactShadow, with 961 GitHub stars. It mentions SSR too. At the bottom of the page, it has a link to @tomalec's tomalec.github.io/declarative-shadow-dom smile

❤️
Nice, given absolute 0 advertising and maintenance effort for my repo ;)
It means I need to update my polyfill to support the latest spec and Chromium implementation.

BTW, I'm not 100% happy with ReactShadow:

  1. I would rather go with smaller iterative steps given the size of Woo. What I propose here, is just a way to use shadow root as a way to scope the host's styles. I consciously avoided moving more elements to the shadow root, to reduce the number of potential conflicts, problems with theming and accessing elements across shadow boundary, React not being able to work with composed DOM Events, etc.
  2. Their component, creates an HTML element and puts everything into the shadow root, and not allow to put anything to the light DOM. Blocking us from using content distribution and <slot> elements. These are in my opinion the key and most useful features of Shadow DOM.

In short, by using ReactShadow we would have to pay the price of all potential problems related to going fully, deep into Shadow DOM, without using all its benefits.

In my proposal here, I suggest using a tiny fraction of Shadow DOM features: style scoping, and even a fraction of that: host element style scoping. To solve a serious problem we have with class name conflicts, and providing CSS-in-JS ergonomics, with minimal effort and price to pay.

@tomalec
Copy link
Member Author

tomalec commented May 5, 2021

Do we want to merge this in and release this? Are we comfortable with this in production? If there is some sort of prior art within WooCommerce / WordPress, then that would give us more confidence on this. (Personally, because of the opinions above, I feel more reluctant on this PR than #538.)

Speaking of using it on production as is. I'd say the IE support is the only blocker here.

Given we are about to use the relatively young feature for our standards - ~10 years since started, ~3 years since supported in all evergreen browsers, we should probably need to reach a broader audience for approval. I was trying to reach out to our team to find whether the idea is appealing and give value at least in the scale of our plugin. I plan to address any feedback here, and prepare a P2 post over the weekend if I find some spare time.

If the idea looks interesting enough, I think we could experiment with it in a calmer, cooldown period, or once we face a serious problem that could be solved using it (and we will not support IE).

@jconroy jconroy added the status: on hold The issue is currently not prioritized. label May 26, 2021
@eason9487 eason9487 changed the base branch from trunk to develop August 25, 2021 03:26
@tomalec
Copy link
Member Author

tomalec commented Nov 11, 2022

We no longer need to support IE, so maybe it's a time to revisit this idea :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: on hold The issue is currently not prioritized.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants