0kku / destiny

A reactive UI library for JavaScript and TypeScript
Open Software License 3.0
53 stars 6 forks source link

Support global CSS #15

Open 0kku opened 3 years ago

0kku commented 3 years ago

A way of defining CSS that pierces shadow roots of components. I'm thinking of injecting a common stylesheet during component instantiation.

0kku commented 3 years ago

While this would technically be somewhat trivial to implement, after some deliberation, I'm not convinced it's necessarily a good idea. Encouraging globally declared rules is a little orthogonal to the design of the rest of the library, and kind of defeats the whole point of styles being scoped by the components.

Since this issue was first raised, support for static styles has been added, which allow you to reuse styles:

class Foo extends Component {
  static styles = [
    myReusedStyles,
    css`
      /* styles specific to the component */
    `,
  ];
}

It may be less convenient to have to explicitly opt into using the styles you intend to be global in every component, but explicitness tends to make debugging things easier and tends to be less bug prone.

ebebbington commented 3 years ago

Whilst I didn't know we could do something like [reusableStyles, css...]

Here's my initial thoughts:

Why would people want to use global styles?

  1. They have a global.css stylesheet, that may give a common style to all input element, buttons etc. This isn't really achievable with scoped css because you'd have to rewrite the same CSS (stay with me here)

  2. Pulling in libraries, eg <link rel="/public/css/boostrap.min.css"> or pulling it through a CDN

I personally think these are very valid usecases. As of now, they cannot be done. (stay with me...). Which leads me on to...

How can we address those?

  1. We can address the first point by using the example @0kku has shown in the description of this issue. this could be along the lines of:
// ./public/components/styles.ts
// Instead of your view linking to the css file, that css file is instead converted to a JS exported variabled
import { html } from ...
export const globalStyles = css`
   input { color: red }
`

// ./public/components/Chat.ts
import { globalStyles } from ...
class Chat extends Component {
  static styles = [
     globalStyles,
     css`
       .some-scoped-elem { ... }
     `
  ]
}

Now I guess this bring a slight problem: every component will have to add this same line. Now is that a bad thing? Maybe it's subjective. So maybe it's down to @0kku as to whether they implement some logic to apply 'global styles' to components, which from what I understand, is considered bad practice: the whole idea of the shadow dom is only scoped css applies.

  1. I don't believe we can address this already. I believe the only way this would be supported is extra work done by @0kku. I would not know what that work would be,, but the idea would be that global available styles would apply to components. Maybe this can be strict: global available styles imported from a url are only supported? eg bootstrap
ebebbington commented 3 years ago

I'm guessing there must some some projects (company software, pet projects etc) that use a shadow dom and have a common UI/styling, where styles are shared across components. I'm just curious how they handle it?

0kku commented 3 years ago

Importing CSS from URLs is not yet supported directly by the static styles property. There are ways you could convert a URL to a style sheet object, but none of the ways are particularly ergonomic. Import assertions may in the future help with statically importing CSS files, but I agree that there's going to have to be some stop-gap solution while we wait for the aforementioned feature to stabilize.

That being said, the code style I originally wanted to encourage was a pattern where you write components that come with styles and import them where they're being used. That way, styles are not decoupled from the structure of the component, and the component is always accompanied by the intended styles. So, for example, if you wanted to have a special button that's styled in a particular way, you'd create a StyledButton and import where it's being used:

export class StyledButton extends Component {
  static captureProps = true;
  forwardProps = new Ref;

  static styles = css`
    button {
      background: green;
    }
  `;

  template = html`
    <button destiny:ref=${this.forwardProps}>
      <slot/>
    </button>
  `
}

and then use it like this:

import { StyledButton } from "./StyledButton.js";

html`
  <!-- Other content -->
  <${StyledButton}>Click me</${StyledButton}>
`;

However, there are a couple of problems with this:

  1. This isn't very ergonomic to do. I could work on making it more ergonomic if this is the pattern we want to ultimately encourage. Perhaps something similar to styled-components?
  2. This doesn't work well for form elements because form-associated custom elements are not yet supported by browsers (planned with P3 in Firefox, implemented in Chromium, planned with P2 in Webkit). Until the feature is supported in all major browsers, there needs to be some other workaround for this use-case.
0kku commented 3 years ago

I'm guessing there must some some projects (company software, pet projects etc) that use a shadow dom and have a common UI/styling, where styles are shared across components. I'm just curious how they handle it?

I have to admit that I'm not sure what the generally agreed-on way to do this is, but I would guess that the most common pattern is to include a <link rel="stylesheet" href="global-styles.css" /> tag in the component's template. This approach will work with Destiny as well, should you want to use it.

ebebbington commented 3 years ago

@0kku Wow that is something i've done in other porjects, but completely overlooked regarding this topic for Destiny...

I retract my original opinions and would 100% agree with you, on the fact that buttons, lists etc (any common element) should be a component and used as a 'sub component'.

To be honest, i'd argue it can be ergonomic - after all be it react or destiny, we use components right? You have a 'custom slider component'? or a button? or a form? Thats a component that can accept props (if needed).

Example:

GET /user/new will display a UsersNew component. That component will use FormComponent as a 'child'. You pass in a click ahndler for the submit button. That form component will consist of using input components too

I have to admit that I'm not sure what the generally agreed-on way to do this is, but I would guess that the most common pattern is to include a tag in the component's template. This approach will work with Destiny as well, should you want to use it.

I guess that is a way, but it doesn't feel right, you know? Seems like it does go against the premise of scoped components?

Regarding general styles eg on the body, or helper classes eg .flex or .text-align-c, they could be a reusableStyles variable

The only thing i'm left thinking about is 'external' styles, again going back to bootstrap (just an example, could be tailwind, literally anything), whether it's in a lib dir, or you're using a cdn to serve it. And from what you've said, that seems near impossible or not a very ergonomic approach

Maybe this is just a matter discussing how an application would go about using external/third party stylesheets?

ebebbington commented 3 years ago

After a long convesation with @0kku, re've discovered that using global stylesheets is possible:

abstract class BaseComponent extends Component {
    protected html (input: TemplateResult) {
        return xml`
          <link rel="stylesheet" href="styles.css" />
          ${input}
        `
    }
}
class CustomButton extends BaseComponent {
    override template = this.html(xml`<button>hello:)</button><p>hi as well</p>`)
}
class AppRoot extends BaseComponent {
    override template = this.html(xml`<p>hello</p><${CustomButton} />`)
}
register(AppRoot)

I suggest the above and the notes from https://github.com/0kku/destiny/issues/15#issuecomment-864301877 (eg styles = [css'${globalStyles}, css'${thisComponentStyles}'])

@0kku I'm happy to write work on a write up for this and you could add it to the wiki? afaik outsider contributors can't edit the wiki