-
Notifications
You must be signed in to change notification settings - Fork 3.1k
specifier: URL protocol for retrofitting specifiers in places designed for URLs #12163
Description
We keep seeing efforts to extend specifiers to additional types of resources, for example Declarative adoptedStyleSheets for Sharing Styles In Declarative Shadow DOM which extends the concept of specifiers to refer to CSS stylesheets:
<style type="module" specifier="my-component-styles">...</style>
<my-element>
<template shadowrootmode="open" shadowrootadoptedstylesheets="my-component-styles">
<div>...component content...</div>
</template>
<my-element>Essentially, specifiers are an abstraction over URLs, and a quite useful one at that when multiple packages from different sources are coordinating. In some ways, they provide the benefits of a relative path, without having to figure out the right relative path.
Additionally, the motivation behind many of these efforts is the way the JS module graph circumvents excessive HTTP roundtripping.
Neither of these benefits are fundamentally tried to JS, and I suspect we will see more of these efforts over time.
However, while ESM import syntax was designed with specifiers in mind and syntactically disambiguates between the two, the vast majority of the platform was designed to be URL-first and cannot be retrofitted to accept specifiers. For example, this means that <link rel=stylesheet> or the CSS @import syntax would not be able to read these CSS specifiers, making something like CSS modules an ad hoc feature specifically for the benefit of web components. Even for JS, <script type="module" src> cannot accept specifiers. Yes, we can invent ad hoc syntax to enable specifiers to be used in every one of these cases (e.g. @import specifier(foo)), but given the places resources can be imported, that seems like a lot of (likely inconsistent) API surface.
A potential solution could be to fully embrace specifiers as a high-level abstraction over URLs and do something along the lines of:
- A new
specifier:URL protocol. Specifier URLs resolve to an actual URL before being fetched, but which URL they resolve to also depends on the request'sOriginheader (so that import map scopes function properly) - In certain contexts (such as ESM imports or this new
shadowrootadoptedstylesheetsattribute) thespecifier:protocol is implied - HTTP caching would work differently; basically in a way that emulates the current behavior of the JS module cache.
- This proposal is not concerned about how specifiers resolve at all, this is the subject of other technologies/proposals. This is about hooking in to the existing resolution mechanisms, whatever those are (import maps, HTTP headers, etc).
Obviously this is an early sketch/strawman and very handwavy, not a detailed, fleshed out proposal.
Architecturally, this has the advantage that it bridges the gap between specifiers and URLs. Rather than having two entirely separate primitives for linking to a resource, it makes specifiers the high-level primitive and URLs the low-level primitive that explains it, making the mental model around resource fetching more coherent and easier to explain.
In terms of use cases, it can be incredibly powerful: ultimately specifiers could be used anywhere a URL can, including images, videos, import map URLs (alias specifiers to other specifiers!), <iframe src>, SVG <use>, CSS url() etc. Though it doesn't have to ship to all of these at once (see below).
Implementation-wise, implementing this properly would be a big task. URLs are handled all over the place and it would likely break assumptions all over the codebase. However! What could work is prototyping it with ad hoc handling, but in a way that creates the illusion of general URL handling. E.g. instead of having an ad hoc CSS syntax like @import specifier(blah) which will be ad hoc forever, implement @import url("specifier:blah") but resolve to a URL as part of @import handling (based on the existing mechanisms to do so, e.g. import maps), then propagate a URL for any further handling. Then later down the line once more parts of the codebase have been handling specifier: URLs, explore refactoring to move it under general URL handling. That way, implementation effort would be the same as ad hoc syntax, but the author-facing API surface is the same everywhere. The downside is that for a while we'd be in the awkward middle ground where specifier: URLs work in some places but not others, but IMO swapping a permanent problem for a temporary one is a win.
I’ve run the above by @yoavweiss who agreed (Yoav please correct me I misrepresented anything!) and added:
OK, implementation-wise I think it can work if you define some
SpecifierOrURLhandling class and use it in a small number of places, expanding over time