facebook / docusaurus

Easy to maintain open source documentation websites.
https://docusaurus.io
MIT License
56.6k stars 8.5k forks source link

Remove all global css pollution #6032

Open Airkro opened 2 years ago

Airkro commented 2 years ago

Have you read the Contributing Guidelines on issues?

Motivation

Global css messes up many things, please consider removing them (rewrite in classes), it will save the day.

image

Self-service

Josh-Cena commented 2 years ago

Agree that this is a valid concern that I have been asked on Discord. However, several things missing from the proposal that I wish we can get response on:

  1. Impact. Is there a really strong use-case that's hindered by the global CSS? Won't all: revert work if you want a part of the page unstyled?
  2. Possible implementation. How would we work to implement your proposal? Many of these elements are directly generated from Markdown (e.g. <kbd>), which is also the primary focus of Docusaurus. Does that mean we should map all Markdown elements to one with a class name?

Global css messes up many things

This is an assertion and I don't see exact what it messes up

please consider removing them (rewrite in classes)

You should report that on the Infima side, the CSS framework developed alongside Docusaurus. In Docusaurus we are more concerned about how CSS classes can interoperate with the current way that HTML is generated.

slorber commented 2 years ago

IMHO we could scope those rules under a .infima-app class or something used at the root of the Docusaurus app.

But would this solve your problem @Airkro ?

Please describe your actual problem. Include screenshots if possible to show broken styling.

Josh-Cena commented 2 years ago

we could scope those rules under a .infima-app class or something used at the root of the Docusaurus app.

The OP wants to easily opt-out from Infima. Scoping probably doesn't prevent the tag selectors from being applied to every element within the <Layout>

tobemaster56 commented 2 years ago

I meet this problem too

We are building our own React component library. There are a lot of documents based on docusaurus. Thanks to the mdx of docusaurus, we can easily render React components in the document. We need to create a lot of demos. We have a pagination component, the li element is used, but in the demo, the style is a bit abnormal. I found out that it was a problem with the global styles of docusaurus.

I created a discussion css style isolation about it, with detailed instructions and screenshots, and I tried all:revert, but it didn't work.

While we can override the relevant styles to solve this kind of problem temporarily, I really feel that this is not the best solution and is a bit ugly. Soon after I discovered the problem, another colleague had the same problem, a calendar demo in which the style of the tables in the component was contaminated, and it took him a lot of time to find out the cause.

I hope there is a once and for all way to be free from global styles.

After discussing it with @Josh-Cena , I tried to use [react-shadow-root]https://www.npmjs.com/package/react, but in the end, it failed, and for our component library, our styles were also global and blocked by shadow-dom. I gave up

In the end, I still hope that the official will come up with an elegant plan, and I hope to hear a response as soon as possible.

slorber commented 2 years ago

Agree, it should be possible to mount a component library in Docusaurus and expect it to not be affected by Docusaurus styles (without requiring the usage of an iframe)

We'll look into this.

In the meantime please report here any Docusaurus style that is affecting the styling of your components. The more exhaustive the list, the better.

Josh-Cena commented 2 years ago

What about having a special class in Infima that reverts all Infima styles? I still find value in certain global styles instead of moving everything to class names.

Airkro commented 2 years ago

Sorry for not responding. I integrated Swagger UI with Docusaurus. I have to render Swagger UI in shadow dom to avoid CSS pollution.

Josh-Cena commented 2 years ago

Closing this since it's unactionable on Docusaurus side. We can discuss in Infima instead, see also https://github.com/facebookincubator/infima/issues/8

I think the easiest solution is to provide an OOTB Sandbox component which is basically a shadow DOM? A reset utility class can do the job as well.

slorber commented 2 years ago

I'll re-open because we can also do something on Docusaurus side, such as providing another theme (likely Tailwind) that does not have this problem in the first place.

It should be possible to mount MDX components in your doc and have those components not affected by Docusaurus theme unscoped styles.

Now we can also open another discussion on Infima to ensure all styles there are scoped properly.

Josh-Cena commented 2 years ago

I still don't understand this. First is "why", second is "how".

Infima is more comparable to Bootstrap than Tailwind. We don't ask users to compose class names to get any useful styles; we directly target elements. If user writes <kbd>foo</kbd> she should immediately get styling that looks consistent with the rest of the design, instead of having to compose layers of class names to adjust the margin, the border, the color... Its goal of driving content-centric website effectively means users should stop caring about styling in all and delegate everything to the default styles.

Let's say the use-case of showcasing custom components is justified. How do we scope Infima? We inevitably have to do it "the Tailwind way", i.e. use class names instead of tag selectors everywhere. But does that sound like the best solution?

Now, can this be done WITHOUT "remove all global css polution"? Yes. We can do it in userland today with iframes and/or shadow DOM and/or a reset class. Those all sound like viable solutions that (a) is extensible (b) is robust (c) doesn't require any work from Docusaurus.

Taking a step further back, how can Docusaurus help with this use-case? This should be discussed in Infima instead of Docusaurus. Until we decide which path we want to take within an Infima thread, there doesn't seem to be anything Docusaurus can do, if we need to do anything at all.

slorber commented 2 years ago

It instantly means we have to do A LOT more MDX component mapping because literally everything, from ul to table to hr, has Infima-default styles today. We have to transform them to something like <table className="table" />.

We'll likely have to do all that anyway if we want to support a tailwind theme, unless you also want Tailwind to apply global classes?

We can do it in userland today with iframes and/or shadow DOM and/or a reset class.


I'm open to exploring the shadow DOM solution.

Maybe the issue is not global CSS, but simply the inability to embed natively your lib's code in your doc without having any styling conflicts?

If we have a solution to this problem, with documentation and some dogfooding, maybe we don't have to handle the global unscoped CSS issue

Josh-Cena commented 2 years ago

Yeah, if we decide to go with shadow DOM it could be most relevant to Docusaurus (we can even offer a theme component like Sandbox). I think someone did tell me they solved it this way.

unless you also want Tailwind to apply global classes?

That I'm actually not sure. Tailwind is, really, a reusable styles library rather than a reusable components library (which Bootstrap/MUI/Infima are more of). To some degree, that contradicts with the goal of a content-centric rather than design-centric site. Maybe I'm okay with the Tailwind theme doing more MDX component mapping? Certainly don't want it to be a widespread pattern though.

Josh-Cena commented 2 years ago

Also note that if a user opts into Tailwind I'd assume she's already aware of the Tailwind ideology (which may not be at all apparent to other casual developers), so the JSX/MDX behavior deviation may be less important. But for the classic theme, which I still aim to be the most accessible for everyone in the long term, should be intuitive enough.

gpbl commented 2 years ago

Thanks for reopening this!

Please note this is not just related to the markdown rendering. Even outside the markdown docs, there are selectors polluting the global scope, which affects also regular pages/routes. So I'm not sure if the "shadow" solution would work.

For example, this is a route in Docusaurus: https://react-day-picker.js.org/render?example=start. The style is not correct because the CSS overrides the table head style:

table th {
  background-color: var(--ifm-table-head-background);
  color: var(--ifm-table-head-color);
  font-weight: var(--ifm-table-head-font-weight);
}

We shouldn't see these selectors in the CSS. However, reverting them would affect the existing implementations, so fixing this is impracticable.

Maybe it's time to develop a new theme? I evaluated https://docusaurus-theme-no-style.netlify.app, to start implementing my own, but that is a lot of work.

gpbl commented 2 years ago

Now, can this be done WITHOUT "remove all global css polution"? Yes. We can do it in userland today with iframes and/or shadow DOM and/or a reset class. Those all sound like viable solutions that (a) is extensible (b) is robust (c) doesn't require any work from Docusaurus

@Josh-Cena those are not viable solutions. Iframe is still affected by CSS pollution, and a "reset" class will never work well. To me, writing the CSS that way has been a bad choice from start, and it has nothing to do with MDX mapping or the "tailwind" way.

I understand that Infima intends to replicate Bootstrap philosophy (even if Bootstrap splits the global CSS classes in a "reboot" CSS). I question if Infima is the right choice for Docusaurus: importing a components in MDX, and having its style overridden by Docusaurus, makes the MDX feature much less useful.

Josh-Cena commented 2 years ago

Infima is developed for Docusaurus, so asking if Infima is the right choice is asking if theme-classic is the right choice😅 If there are things going wrong we can fix it.

Even outside the markdown docs, there are selectors polluting the global scope,

I may have failed to make it, because that's literally my entire point. There should be minimal effort to make a non-MD page look exactly like an MD page. For example,

## Keyboard

Press <kbd>Ctrl</kbd> + <kbd>C</kbd>.

...should be directly replicable in a React file as...

function Section() {
  return (
    <>
      <h2>Keyboard</h2>
      <p>Press <kbd>Ctrl</kbd> + <kbd>C</kbd>.</p>
    </>
  );
}

...instead of forcing most users to write...

function Section() {
  return (
    <>
      <h2 className="h2">Keyboard</h2>
      <p className="p">Press <kbd className="kbd">Ctrl</kbd> + <kbd className="kbd">C</kbd>.</p>
    </>
  );
}

That's (a) tedious (b) unintuitive (c) easily inviting for inconsistent design. Through the years, we figured that it's hardly possible to please both power users and users who didn't even know React is before this, so it's better if the bar of entry can be low and things "just work", even if it becomes more headaches for power users.

Josh-Cena commented 2 years ago

Shadow DOM should be the way to go, because that's literally the purpose of shadow DOM. I didn't investigate, but I do know of successful integration cases.

gpbl commented 2 years ago

Infima is developed for Docusaurus, so asking if Infima is the right choice is asking if theme-classic is the right choice

Infima has tons of quirks, I've spent countless hours trying to workaround them. It is not the right theme for documenting a components library – in this case, it would be nice a note in the docs. Anyway here we are, reporting our issues with it. Hopefully soon we can have an alternative theme!

Josh-Cena commented 2 years ago

Yes, Infima is not designed with component library docs in mind—we do not expect such use-case. Using an alternative theme like Tailwind should be able to fix it.

gpbl commented 2 years ago

Yes, Infima is not designed with component library docs in mind—we do not expect such use-case.

I can spot few use cases in the Showcase gallery - e.g https://ionicframework.com/docs/components I'm sure they just run into our issues, give up and/or solved it using Codesandbox.

The case of react-day-picker.js.org is not even a component library - just a single component.

Josh-Cena commented 2 years ago

Ionic solved it through shadow DOM which is what I've been saying all along:

image

Plus they are a native component library so they have a different battle.

slorber commented 2 years ago

Looks like upcoming CSS @scope this could be a good solution:

CleanShot 2022-06-06 at 13 08 31@2x

https://twitter.com/sebastienlorber/status/1533767305875824641

TimonVS commented 2 years ago

Our team has been using ReactShadow to workaround this issue. But we haven't been able to get it to work with Emotion yet (tried creating a separate cache and having the styles be inserted in the shadow root, but haven't managed to get it to work yet).

stevenpetryk commented 2 years ago

Just reporting here that at Discord we've had some success using shadow DOM as well. We had to tell webpack to split the CSS bundle into three CSS files using splitChunks:

In the head, we load docusaurus-styles.css and discordapp-styles.css (because we want to use our components in doc pages), but in the shadow DOM, we only load discordapp-*.css (because we don't want Docusaurus' global styles to pollute the component playgrounds, which need to look exactly the same as they do in Discord).

The only issue we've faced with Shadow DOM is that some event handlers needed to be changed. For example, for a popup component (think popper, context menus, dropdowns, etc.), we had a mouseup listener on document, and it checked if popupContainer.contains(event.target). With shadow DOM, this doesn't work, because event.target is always the shadow root, so we had to change it to popupContainer.contains(event.composedPaths()[0]).

I'll report back if I see other issues. I agree that it would be great to not have to use Shadow DOM, but it sounds far worse to have to import some <Pre> component rather than just using HTML <pre>. @scope is really the answer. Could just be:

@scope (body.docusaurus) to (.docusaurus-exclude) {
  /* all docusaurus styles */
}
slorber commented 2 years ago

A new interesting solution from @bluwy

Whyframe permits to easily isolate the rendering of a React component inside an iframe.

There's a Docusaurus plugin to try

dcousineau commented 1 year ago

We took an approach similar to @stevenpetryk and wanted to post the rough outline for anyone else who finds this thread below.

On a side note, I think there's a possibility that a contribution to @docusaurus/theme-live-codeblock could be made to have a toggleable Shadow DOM approach, the primary coordination could be that Docusaurus's splitChunks configuration for CSS files is altered such that infima and @docusaurus css files are chunked separately, and "everything else" is in a separate well-named chunk that, if shadow dom is enabled in the live codeblock, could be pulled in whole-sale for a ... "quick and dirty" isolated codeblock experience. Will see if I can carve out some time to put together the skeleton of the idea.


In the theme that we ship, our basic approach was to tweak the splitChunks cacheGroups to isolate our component library styles:

const DEFAULT_LIVE = {
  enableShadowDOM: true,
  includeSplitChunks: c => {
    return /@package-namespace[\\/].+\.s?css/.test(c.identifier());
  }
};

module.exports = function (context, options) {
  const { /* ...snip...*/, live = DEFAULT_LIVE } = options || {};

  return {
    name: 'our-theme',
    // ...snip...
    configureWebpack(config, isServer, utils) {
      const updateConfig = {};

      if (!isServer && live.enableShadowDOM) {
        // Utilize split chunks to ensure CSS assets from Docusaurus are separated from others
        updateConfig.mergeStrategy = updateConfig.mergeStrategy ?? {};
        updateConfig.mergeStrategy['optimization.splitChunks.cacheGroups'] = 'merge';
        updateConfig.optimization = {
          splitChunks: {
            cacheGroups: {
              liveStyles: {
                name: 'styles-live',
                test: c => {
                  // Additionally include any styles that may have been pulled in by ReactLiveScope overrides
                  return /ReactLiveScope[\\/].+\.s?css/.test(c.identifier()) || live.includeSplitChunks(c);
                },
                chunks: 'all',
                // enforce: false feels weird to have, but it DOES result in the CSS outputs we need.
                // @see https://github.com/thecrypticace/webpack-css-chunks-test/blob/master/webpack.config.js
                enforce: false,
                priority: 60 // The default `styles` cacheGroup from Docusaurus is set to priority 50
              }
            }
          }
        };
      }

      return updateConfig;
    },
    // ...snip...
  };
}

And then we ejected the Playground component from @docusaurus/theme-live-codeblock into our custom theme and made some tweaks to mount the <LivePreview /> in a shadow root (there is a lot of snipping below, we have a heavily customized Playground component)

// @see https://github.com/facebook/docusaurus/blob/master/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js
import React, { useState, useRef, useEffect, useMemo } from 'react';
import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import ShadowRoot from 'react-shadow';

export default function Playground({ children, transformCode, ...props }) {
  // ...snip...
  const {
    siteConfig: {
      themeConfig: { live = {} }
    }
  } = useDocusaurusContext();

  // We assume that shadowDOM is enabled by default
  const enableShadowDOM = live?.enableShadowDOM ?? true;

  const sheets = useMemo(() => {
    if (!enableShadowDOM) return [];

    const sheets = typeof document !== 'undefined' && document?.styleSheets?.length && Array.from(document.styleSheets);
    if (!sheets) return [];

    return sheets.filter(sheet => {
      if (!sheet.href) return false;
      const sheetHref = new URL(sheet.href);
      return sheetHref.pathname.match(/styles-live/i);
    });
  });

  return (
    <div className={styles.playgroundContainer}>
      <LiveProvider /* ...snip... */ >
        <div className={styles.playgroundPreview}>
          {enableShadowDOM ? (
            <ShadowRoot.div ref={root} mode='open'>
              <LivePreview />
              {sheets.map(sheet => (
                <style type='text/css'>{Array.from(sheet.cssRules, rule => rule.cssText || '')}</style>
              ))}
            </ShadowRoot.div>
          ) : (
            <LivePreview />
          )}
          {showEditor && <LiveError />}
        </div>
        <LiveEditor className={styles.playgroundEditor} />
        {/* ...snip... */}
      </LiveProvider>
    </div>
  );
}

Note The above approach is currently naïve especially in regards to pulling in the stylesheet for the live playground (effectively only once on mount and maybe a second time if React clears the memoization cache). We'll be spending more effort over time improving / bullet proofing the solution but for now it works "good enough".

gpbl commented 1 year ago

Thanks for the feedbacks! I am still not happy with the workarounds described here.

What we need is the ability to create a blank HTML page in Docusaurus, but without any styles. We could them link this page with a standard iframe. Can we do this without forking the whole classic theme?

slorber commented 1 year ago

What we need is the ability to create a blank HTML page in Docusaurus, but without any styles. We could them link this page with a standard iframe. Can we do this without forking the whole classic theme?

If you don't need SPA navigation, you can add any html file in /static/xyz.html and link to it with pathname:///xyz.html or display it inside an iframe

slorber commented 9 months ago

Note to myself: cascade layers can also be a good alternative to opt-out of the style we apply by default.

This article explains how to use all: revert-layer for that purpose: https://www.mayank.co/blog/revert-layer