Skip to content

[Declarative Shadow DOM Style Sharing] Use fragment URLs instead of global specifiers #1186

@nicolo-ribaudo

Description

@nicolo-ribaudo

I'm worried about the choice of using a global namespace for inline stylesheets. We have been generally regretting the usage of global namespaces, as they make adoption significantly harder because they require global coordination across the whole application. I'm especially worried in the context of https://github.com/WICG/webcomponents/blob/gh-pages/proposals/html-modules-explainer.md, which allows developers to publish self-contained HTML files (different from the "root" one) that contain a template with the corresponding style.

I understand that there are benefits to using global specifiers, since it means you can reach them from anywhere. However, I'd like to suggest an alternative that should retain the same benefit while avoiding the global namespace problem: using URL fragments. They have been designed exactly to refer to a part of a larger document.

For context, this idea also came up in the context of https://github.com/tc39/proposal-module-declarations, which also introduces a single file that internally defines multiple (JavaScript in that case, instead of CSS) modules.

Specifically, https://example.com/index.html#foo would be the fully resolved URL for the <style type="module" specifier="foo"> defined inside https://example.com/index.html#foo. We can make URL resolution work like it does for new URL("#foo", <this file's URL>), so that you can reference that inline stylesheet:

  • using #foo from inside https://example.com/index.html:
    <style type="module" specifier="foo">
      #content {
        color: red;
      }
    </style>
    <my-element>
      <template shadowrootmode="open" shadowrootadoptedstylesheets="#foo">
        ...
      </template>
    </my-element>
  • using, for example, ../index.html#foo from a hypotetical https://example.com/js/bundle.js#foo:
    import styles from "../index.html#foo" with { type: "css" }
  • using absolute URLs whenever you want, like you can do for "external" files

If users do want to have a global alias, they can use an import map for it. But it's opt-in, like for any other module. They could also use imoprt map scopes, to have an absolute-like specifier that is not actually absolute.

<script type="importmap">
{
  "imports": {
    "foo": "#foo"
  }
}
</script>

There is one drawback to this approach: it's that import "./something#foo" is already valid, and it currently resolves to the same module as ./something. There are two possible solutions to this:

  1. When doing import "./something#foo":
    • if ./something exposes a #foo sub-module we return it
    • otherwise we return ./something's whole thing
  2. When doing import "./something#foo":
    • if ./something exposes a #foo sub-module we return it
    • otherwise, if ./something exposes any sub-module, we error
    • otherwise we return ./something's whole thing

EDIT: This approach also suggests a consistent way to directly import "sub-modules exported from a module", which assuming https://github.com/WICG/webcomponents/blob/gh-pages/proposals/html-modules-explainer.md + https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/AtSheet/explainer.md + https://github.com/tc39/proposal-module-declarations can happen with:

  • an HTML module exporting a bunch of CSS modules
  • a CSS module exporting a bunch of CSS modules
  • a JS module exporting a bunch of JS modules

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions