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

Support fragment references in the <link> tag's href attribute #11019

Open
KurtCattiSchmidt opened this issue Feb 11, 2025 · 6 comments
Open

Comments

@KurtCattiSchmidt
Copy link

KurtCattiSchmidt commented Feb 11, 2025

What is the issue with the HTML Standard?

The problem

There are currently several options for sharing styles with Declarative Shadow DOM, but all of them rely on external files or dataURI's:

  1. <link rel="stylesheet" href="foo.css"> this requires an external file.
  2. <link rel="stylesheet" href="data:text/css;..."> this is not technically an inline style definition, but it doesn't generate a network request, so it's as close as you can get to an inline style today. This must be re-parsed and duplicated in memory for each instance, and the dataURI syntax has poor developer ergonomics.
  3. adoptedStyleSheets via Javascript - using Javascript somewhat defeats the purpose of Declarative Shadow DOM, and this approach still only supports entire files (or a dataURI).

Use cases

  • CSS @sheet - this will enable CSS @sheet to work with inline CSS. CSS @sheet will only work in external CSS files unless there's a mechanism for referencing inline style blocks as mentioned in this proposal. For more details (including examples), see this @sheet explainer.
  • Minimizing time to First Contentful Paint (FCP) metrics - by not relying on external files, inline styles can be parsed once and reused many times
  • Lowering style computation costs with Declarative Shadow DOM - by structuring styles so that a base set of styles can be selectively applied to Declarative Shadow DOM instances, developers can optimize their site's style computation performance and reduce duplicated CSS rules.
  • Custom Elements - Custom Elements often rely on Shadow DOM for ID scoping, but they lose access to most of the light DOM's style information and need to pick a least-bad option from the current solutions listed above.

Proposed Solution

Allow the <link> tag's href attribute to support same-document references to corresponding fragment identifiers for <style> tags.

<style id="inline_styles">
  p { color: blue; }
</style>
<p>Outside Shadow DOM</p>
<template shadowrootmode="open">
  <link rel="stylesheet" href="#inline_styles">
  <p>Inside Shadow DOM</p>
</template>

Prior Art

  • SVG xlink:href syntax is very similar, although it allows cross-document references. For HTML, this might not be desirable.
  • Reference Target expands behavior of Shadow DOM via node ID's
@KurtCattiSchmidt KurtCattiSchmidt changed the title Add sheet attribute to the <link> tag's for CSS @sheet support Support fragment references in the <link> tag's href attribute Feb 11, 2025
@dandclark dandclark added the agenda+ To be discussed at a triage meeting label Feb 12, 2025
@past past added stage: 1 Incubation and removed agenda+ To be discussed at a triage meeting labels Feb 13, 2025
@mayank99
Copy link

mayank99 commented Feb 15, 2025

This feature could tie neatly into "declarative adopted stylesheets" (as an alternative to #10673). Since the whole purpose of adopted stylesheets is to reference the original stylesheet instance, it makes sense to me that a <link> with a fragment reference would use the same mechanism.

Example code using a new rel value:

  <style id="inline_styles">
    p { color: blue; }
  </style>
  <p>Outside Shadow DOM</p>
  <template shadowrootmode="open">
-   <link rel="stylesheet" href="#inline_styles">
+   <link rel="adopted-stylesheet" href="#inline_styles">
    <p>Inside Shadow DOM</p>
  </template>

(This would prepopulate .shadowRoot.adoptedStyleSheets before JS runs.)

Keeping in mind that the "constructed" limitation on adopted stylesheets is likely going to be lifted (w3c/csswg-drafts#10013), does this sound feasible?

Using adopted stylesheets would be more performant and also avoid some of the harder questions such as "what happens when the original stylesheet contents change?" (changes propagate automatically).

This does not yet solve the problem of wanting to "disable" a stylesheet in light DOM, but that's a slightly different use-case, and @scope and @sheet or disabled="" can help with that.

@annevk
Copy link
Member

annevk commented Feb 17, 2025

Can someone remind me why the style element can't be used here?

I also don't think it's a good idea to mix URLs and same-document references due to base URLs and such. It's rather messy.

@KurtCattiSchmidt
Copy link
Author

KurtCattiSchmidt commented Feb 18, 2025

Can someone remind me why the style element can't be used here?

@annevk - do you mean duplicating style tags for every element in a Shadow DOM? This works, but it's not efficient or ergonomic for developers to copy-paste styles (or always rely on a bundler to do this for them), particularly in a Web Components scenario where each element on the page is a Custom Element in its own Shadow DOM. That is the main use case we're trying to solve with this proposed functionality.

I also don't think it's a good idea to mix URLs and same-document references due to base URLs and such. It's rather messy.

I agree that base URL's add some complexity here. This is a great call out. I can think of a few ways to solve this. One option is to use a different attribute than href, which would take base out of the picture and clarify that it's an ID reference and not a URL. Another option is to special-case the behavior of base tags for local references in this scenario, but that option also seems a bit messy.

@KurtCattiSchmidt
Copy link
Author

KurtCattiSchmidt commented Feb 18, 2025

This feature could tie neatly into "declarative adopted stylesheets" (as an alternative to #10673). Since the whole purpose of adopted stylesheets is to reference the original stylesheet instance, it makes sense to me that a <link> with a fragment reference would use the same mechanism.

@mayank99, this could be another good option. I have a few thoughts here:

  1. Is this actually how adoptedStyleSheets works without constructable objects? Or is it making a copy internally? @keithamus worked on this in Firefox and may have some insight.

  2. An adoptedStyleSheets attribute that takes (a list of?) ID's isn't symmetrical with the DOM API for adoptedStyleSheets, which takes an array of objects. I'm not sure how significant of an issue this is.

@robglidden
Copy link

I also don't think it's a good idea to mix URLs and same-document references due to base URLs and such. It's rather messy.

I agree that base URL's add some complexity here. This is a great call out. I can think of a few ways to solve this. One option is to use a different attribute than href, which would take base out of the picture and clarify that it's an ID reference and not a URL. Another option is to special-case the behavior of base tags for local references in this scenario, but that option also seems a bit messy.

When would a base element cause a need in the first place to disambiguate an existing URL use case from a (now-unsupported) reference to a document element?

<base href="http://a-url/" />
<style id="inline_styles">
    /* ... */
</style>
<!-- ... -->
<link rel="stylesheet" href="#inline_styles" />

now just produces an error:

Refused to apply style from 'http://a-url/#inline_styles' 
because its MIME type ('text/html') is not a supported
stylesheet MIME type, and strict MIME checking is enabled.

If there is a need to disambiguate an element reference, perhaps a new link type attribute, say like "element"?:

 <link rel="stylesheet element" href="#inline_styles" />

@noamr
Copy link
Collaborator

noamr commented Feb 20, 2025

I don't think this can work with a new rel, and also it won't solve the issue of @import in older browsers that don't support sheet. Also, this would only work for stylesheet, and this problem would not be solved when we want to use the "import from inline element" feature for scripts.

An alternate proposal for this could be to use a new URL scheme, similar to data: and blob:, that only targets same-document elements, where the path of the URL can walk up the shadow ancestry:

<style id="root-bundle">...</style>
<link rel=stylesheet href="element:root-bundle">

<my-element>
  <template shadowrootmode=open>
    <style id=inner-theme>...</style>
    <link rel=stylesheet="element:/root-bundle">
    <!-- or -->
    <link rel=stylesheet="element:../root-bundle">
    <link rel=stylesheet="element:inner-theme">
  </template>
</my-element>

Regarding mutability, I think this should work the same way as links and imports today and not change their semantics - once the URL is imported, it's immutable and doesn't track changes. To have an imported thing that tracks changes is something that needs to be done with JS, as it's done today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

7 participants