-
Notifications
You must be signed in to change notification settings - Fork 702
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
[css-scoping] Scoping of functions, other name-defining at-rules and custom idents #11798
Comments
Interesting. I think an at-rule fits this better, given with |
I believe |
Ideally, the mechanism for both would be the same, no? If name-defining at-rules could be allowed within selectors, The example @kizu gave could then be rewritten to the following, which I believe is easier to understand as you don’t have to connect a few things. .foo {
contain: names;
@function --bg() { result: lightgreen }
}
.bar {
contain: names;
@function --bg() { result: pink }
}
.foo, .bar, .baz {
background: --bg();
} <div class="foo">lightgreen</div>
<div class="bar">pink</div>
<div class="baz">transparent (because there is no --bg available)</div>
<div class="foo">
<div class="baz">lightgreen (because the one from .foo is used)</div>
</div> Using @scope (.foo) {
contain: names;
@function --bg() { result: lightgreen }
}
@scope (.bar) {
contain: names;
@function --bg() { result: pink }
} |
Some thoughts about the 1. @scope namespace (.foo) {
@function --bg() { result: lightgreen }
}
@scope namespace (.bar) to (.bar-end) {
@function --bg() { result: pink }
}
.test { background: --bg(); } <div class="foo">
<div class="test">I am lightgreen</div>
<div class="bar">
<div class="test">I am pink</div>
<div class="bar-end">
<div class="test">I am lightgreen again</div>
</div>
</div>
</div> Would it be possible to do this with 2. With 3. My initial idea for this was to not even use 4. Semantically, I prefer But even if I prefer Now we have two options:
I wonder if there are more options we don't yet see. |
One other option is a Sass-like module system. #10518 But module scoping is very different from DOM scoping, and I suspect there will be good use-cases for both. |
Is the problem you are describing mostly name clashes? Authors will want to mix and match things defined in different sources on the same element. They will not want to use only idents from library A on element B and only idents from library V on element W. There will never be a neat line. Some ideas I've been considering to improve how authors can work around name collisions: Add a block syntax to
|
I mention that named scopes could cover this use case, although it could be not ideal. Handling this with imports is an option, but I think it should be only as an additional option, like with an ability to add a layer to an import. There was a proposal for being able to use Why I don't think imports-only way is good: there are use cases for doing this inside a single file. Even aside from bundled stylesheets, you could want to have something like this for two components in a single file. There is no reason to restrict this to an import-level definition. That said, maybe What if we could do this with block-level scoping, somehow? Any idents that share their name inside one block are treated as the same, but idents in different blocks are not. I wonder if we could somehow adopt the The current syntax of What if we override it with a @namespace --a {
@function --foo() { result: lightgreen }
.a {
background: --foo(); /* lightgreen */
}
}
@namespace --b {
@function --foo() { result: pink }
.b {
background: --foo(); /* pink */
}
}
.foo {
background: --foo(); /* transparent — not defined outside a namespace */
}
.bar {
background: --a|--foo(); /* lightgreen: uses --foo() from --a namespace */
outline: 2px solid --b|--foo(); /* pink: uses --foo() from --b namespace */
} Maybe the I also considered reusing the So far, the round up of options:
|
I've been hoping for some sort of name scoping for a while but I'd rather have lexical scoping where a name is local to a file or block of CSS code, rather than certain parts of the document. In its most basic form, this could be implemented by just prefixing any names found within a scope: @layer has-local-name {
@local --color; /* scoped to the layer */
/* gets turned into --internal-prefix-color */
:root { color: var(--color) }
}
:root {
--color: red; /* does nothing because plain `--color` isn't used anywhere */
} A neat side effect here is that this could be done entirely by a preprocessor so it could be used in production a lot sooner. |
Hmm, this is an interesting idea. It can be expanded to something like: adding I see two issues with this:
|
|
Yes, my One could think that with many different at-rules, you could want to sometimes combine them, like for every component use |
For reference: I found back the issue/discussion: #8915 (comment) |
I am very skeptical of introducing any more complexity to name-based lookups than the substantial amount of complexity we already get from ShadowDOM.
This is not really implementable.
I'm fairly certain (tree-)scoped custom properties is off the table forever. It will likely cause unacceptable performance regressions. |
@andruud Do you think the
Is this true for the more simple namespacing approaches as well? If so, could you briefly point to what would be the performance bottleneck? |
Isn't tree-scoping names for things that are inherited through cascade practically pointless anyway? The way I understand it, the options are lexical scoping to help with both functions+mixins and variables, as the scoping is independent of the cascade, or tree-scoping which mostly affects functions+mixins which are global. Tree-scoping functions and mixins would, in practice, achieve about the same as just letting them cascade the same way custom properties do, but with a separate syntax and global-by-default semantics. This sounds like it could be very cool in more ways than just avoiding name clashes, but wouldn't fully address clashes at all, and still require some other mechanism like lexical scoping to achieve that. Maybe splitting the discussions into lexical vs. tree scoping would help keep those two threads more on-topic and avoid confusion? |
Context
There are many name-defining at-rules in CSS.
Examples:
@keyframes
,@property
,@counter-style
,@position-try
, and, soon,@function
, etc.There are also many custom idents, regular or dashed.
Examples: custom properties, counters, anchors, timelines, etc.
A common problem for them all: currently, most of them (all? I did not check) are tree-scoped.
Problem
Current “CSS Scoping Module Level 1” defines how things are scoped in Shadow DOM vs Light DOM.
However, when we're purely in a Light DOM context (or inside a single shadow root), all the custom idents exist in a single global scope. This means that it is very easy to have a name clash between libraries, components, and other CSS things, where it is very easy to accidentally reuse the same name.
The definition that wins in the cascade will be used, and it is relatively easy to break something with it. Custom property can be registered with an incorrect type, an animation could use an incorrect set of keyframes, anchored element will be attached to something unexpected, and custom functions could result in a lot of IACVT.
Authors have to use naming conventions that help reduce clashes, or external libraries like CSS Modules that guarantee the uniqueness of all these idents.
A convention is brittle and results in lengthy idents, and anything that fails to follow it could lead to an issue. External tools are, well, external.
Native CSS, by itself (without Shadow DOM), does not provide a mechanism like this.
Proposal
When I think about what we need: we need proper and explicit scoping in the light DOM for all these idents and definitions.
What if we reused
@scope
?Even though it did not yet ship in Firefox, I don't think it is a good idea to change its behavior today, as it was for a long time in Chrome and Safari. We don't want to break backwards compatibility. Add to this — not always you want to scope all the idents, so it might be inconvenient for simple projects to have
@scope
with this strong scoping as the default behavior.What I suggest: adding some kind of a keyword when we define an
@scope
. Name TBD and requires bikeshedding, but something like@scope namespace (.foo) {}
(this is similar to adding anisolated
keyword in github.com//issues/11002 — but I don't see what I propose in this issue as isolation, but more of an extension of a cascade for the custom idents).When you then define any idents inside — they will be scoped to this particular scope.
Meaning: when we use some ident on an element, we will find the closest scoping rule to this element, and use the named ident that is defined in that scope. If not found, we will go up the scope, and so forth until we will reach the global scope (what we have now without scopes at all).
This can be seen a bit similar to how container queries, or container query length units work: by default we will look at the closest container. But we also have named containers that allow us to choose which one to use if multiple of them match.
Scopes cannot be named just yet, but there is a proposal to do so: #9742
With an ability to name scopes, we could expand this feature, and allow to somehow override from which scope we want to use an ident. Maybe something like a
from-scope(<scope-name>, <ident>)
? One problem could be custom properties — not for their usage (var(from-scope(--outer, --foo))
could potentially work), but for definition: it might be tricky to find a good syntax for defining a custom property for a certain scope.This can be a bit similar to the #10808 — an ability to break encapsulation from inside Shadow DOM for idents.
That's it for now. This is not a very concrete proposal, but something I was thinking about for a very long time, and something I saw others have issues as well.
Possibly, there were already issues about something like this, and this is a duplicate: if so, point me to them, and it would be great to pick this up again.
In general, I think
@scope
is the best place for something like this, with its proximity rules, and, literally, with its name.The text was updated successfully, but these errors were encountered: