Skip to content

Declarative CSS Module Scripts #939

@justinfagnani

Description

@justinfagnani

Declarative shadow roots and constructible stylesheets need to be made to work together so that stylesheets added to shadow roots can be round-tripped through HTML serialization and deserialization.

As of now, SSR code will have to read a shadow root's adopted stylesheets and write them to HTML as <style> tags. While this will style they shadow roots correctly on first render, it results in bloated a HTML payload and breaks the shared stylehseet semantics. Hydration code would have to deduplicate <style> tags and create and attach constructed stylesheets in order to reconstruct the origin DOM structure.

To solve this we need a serialized form of constructible stylesheets. These are stylesheets that do not automatically apply to any scope, but can be associated and applied by reference.

Requirements:

  1. Ability to serialize a constructed stylesheet to HTML (edit to clarify: on the server)
  2. Styles don't automatically apply to the document or any shadow root
  3. Styles are able to be associated with declarative shadow root instances (edit to clarify: which populate the shadow root's adoptedStyleSheets)
  4. To support streaming SSR, the styles must be able to be written with the first scope that uses it and referenced in later scopes.

One idea is to add a new type for <style> tags that accepts CSS text and creates a new constructed stylesheet:

  <style type="adopted-css" id="style-one">
    :host {
      color: red
    }
  </style>

This style can then be associated with a declarative shadow root:

  <my-element>
    <template shadowroot="open" adopted-styles="style-one">
      <!-- ... -->
    </template>
  </my-element>

Having a type other than text/css means that the styles won't apply to the document scope even in older browsers. It's also what allows it to have an adoptable CSSStyleSheet .sheet, which replace() works on as well.

One problem that immediately come up is scopes and idrefs. If a declarative stylesheet is only addressable from within a single scope, it's no better than a plain <style> tag because it would have to be duplicated in every scope that uses it. We need some sort of cross-scope idref mechanism. With that we can write the styles with the first scope that uses them, and reference them in later scopes in the document:

  <my-element>
    <template shadowroot="open" adopted-styles="x61h8cys">
      <style type="adopted-css" xid="x61h8cys">
        :host {
          color: red
         }
      </style>
      <!-- ... -->
    </template>
  </my-element>
  <my-element>
    <template shadowroot="open" adopted-styles="x61h8cys">
      <!-- ... -->
    </template>
  </my-element>

Here xid is a stand-in for some kind of cross-scope ID. "x61h8cys" is a stand-in for a GUID or other globally unique value (such as a hash of the style text) that the SSR code would be responsible for generating.

There are other instances where we need cross-scope references, like ARIA and label/input associations. I'm not sure if these cases are similar enough to use the same mechanism or not.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions