-
Notifications
You must be signed in to change notification settings - Fork 672
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 Import Once] @import-once, or some syntax for importing a stylesheet only once #6130
Comments
I would appreciate this syntax as it is polyfillable: |
Here is @nuxodin's polyfill: https://twitter.com/tobiasbu/status/1374489633325674500 |
Here's the issue described in ESBuild's "CSS caveats" section: |
This would import a style sheet once in the document where it is used. If used in a |
Why is there not much attention to this issue? This is the single most notable feature of native CSS preventing everyone from writing highly re-usable modular vanilla CSS code. This has long been solved in non-native CSS-alternative languages like Less (see |
Some questions:
|
Cascade layers.
I don't see how this would be solved by Can you give more detailed examples where vanilla CSS could have been more re-usable and modular but that this was prevented by this specific aspect of I feel like there is some step or aspect that I am missing. |
Hi Emilio, good questions.
I think this is exactly the purpose of this proposal.
It also worked with es-modules.
Only the first appearance of @import_once should be taken into account. |
I don't think this is necessarily true.
I don't think we can say : "tools do this, so it's needed". |
I agree with you, but I was already bothered by |
For reference. Arguments for “once” in the comments: |
Thank you for linking that @nuxodin I think it would be better if someone makes a structured case for This thread also seems to contain part of the examples and reasoning : |
It is no accident! To reproduce the main problem with today's Now, user needs features from /*my-feature-1.css*/
@import '/node_modules/css-lib/b.css'
/* override things from b, use variables from b or a */ /*some-page.css*/
@import './my-feature-1.css' For now, it works fine. There's no problem yet. Later, the user learns that they want to use feature /*my-feature-1.css*/
@import '/node_modules/css-lib/b.css'
/* override or use things from b */ /*my-feature-2.css*/
@import '/node_modules/css-lib/c.css'
/* override or use things from c */ /*other-page.css*/
@import './my-feature-1.css'
@import './my-feature-2.css' Now what the user did not realize while trying to modularize their code is that If they try to use Another problem is, in order to solve the above issue, Today's Imagine if with JavaScript modules that multiple imports of the same Imagine if with JS we did not get modules, but instead got Basically the ask here is to have something more aligned with modern modules concepts and automatic dependency graph resolution, making it possible to easily modularize code and share libraries. Perhaps what we need is to start aligning with ES Modules. We already have CSS Modules. Maybe we could expand on that by adding new a new |
That forum has been taken down. Have the threads been migrated elsewhere? |
In the comment here: I've shown an idea for importing things in a more module like way. For example, import a mixin from somewhere: @import --foo from './my-mixins.css'
/* --foo is usable only within this file, not in any other file, not global. */
.some-class {
@apply --foo;
} The idea there is it behaves more like ES Modules: we import what we need into our files, the engine determines the dependency graph for us, and modules do not re-evaluate more than once. Some things like functions and mixins may perhaps always be module-scoped (even if the module that executed created global styles) and imported into other files explicitly (just like a JavaScript file that both creates globals and exports things). We could potentially then also add features like module variables which are scoped to modules, and usable only where imported (current CSS vars/properties would still exist as a "global" feature). |
Not sure if it’s anywhere else, but there is https://web.archive.org/web/20220703110804/https://discourse.wicg.io/t/importing-css-only-once/1933 |
Now that we already have cascade layers, is there still any broken use case without Duplicate imports are unfortunate, but if no functionality is broken, then we just need some browser-internal performance optimizations (like deduplicating consecutive imports in the same layer). Edit: typo |
Yes, I think it is important to separate the mental model issues from other potential uses cases for I see the value in having a shared mental model for imports between JS and CSS but I am not sure that this alone is worth having two different ways imports can be done in the same language. We see how much issues this is causing in JS/Node with commonjs and es modules. The other mentioned issues can be solved with cascade layers, scoping, ... |
Yeah, they are different languages after all. If there's no use case, I suggest we close this issue? |
@trusktr Can you clarify how CSS Modules have the same behavior as the proposed If I am reading the relevant specifications correctly and after some testing I don't see how they are similar. <script type="module">
import a from './a.css' assert { type: "css" };
import b from './b.css' assert { type: "css" };
document.adoptedStyleSheets = [a, b, a];
</script> This applies Full example: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="module">
import a from './a.css' assert { type: "css" };
import b from './b.css' assert { type: "css" };
document.adoptedStyleSheets = [a, b, a];
</script>
<div class="box">
Layered (green when a is applied before b)
</div>
<div class="other">
Unlayered (green when a is applied after b)
</div>
<i>Only both are green when a is applied twice</i>
</body>
</html> /* a.css */
@layer a, b;
@layer a {
.box {
color: red;
}
}
@layer b {
.box {
color: green;
}
}
.other {
color: green;
} /* b.css */
@layer b, a;
.other {
color: red;
} |
Using layers as a workaround is by far not the ideal dev experience. Layers are a global feature, and they'd simply be something that any CSS module could define. Plus having a proper module system also allows additional features like module-scoped features to be added. Layers are like namespaces. You can make namespaces in JavaScript, but that's not exactly the same as sharing things via JS modules. A JS namespace can be defined and augmented by any JS module. So the idea here is to provide a proven design pattern for CSS developer to write expandable module code with (regardless if they use layers or not). |
They currently don't for code written in CSS, because It makes a lot of sense though, for an This is the future of CSS development that is at stake here. Settling for "just use namespaces" (i.e. layers) is doomed to fail, once developers working in a specific namespace start to clobber things within that namespace. Modules are the next proven evolution after namespaces.
This is certainly not ideal, a pattern to be avoided. That is also somewhat tangential to A CSS import feature would operate in a better way, and that would be the best practice to follow. Same thing in JavaScript: import a from './a.js'
import b from './b.js'
a()
b()
a() Apply a, then b, then a. It is tangential to a proper import mechanism. If a developer wants to apply stylesheets multiple times, they can do that, while a new import feature would work properly when a root level style sheet is applied only once. There is no way to fix existing APIs, we can only provide a new API to create a new best practice, and existing APIs should not prevent us from doing so. We can only create the new way to do things well, but we can't erase existing features (well, it is possible, but unlikely). |
I don't see layers as a workaround but the canonical solution, unless I see a use case where layer-based solutions are fundamentally broken. And layers are not global. With concepts like sublayers and layered imports, layers work pretty much the same way as modules. For example: /* base.css */
@layer a { ... }
@layer b { ... } /* main.css */
@import url('base.css') layer base;
@layer my-own-business { ... } The styles in In fact, there's no such a thing as a global layer namespace -- you can always import things into another layer. Btw there are still open issues where CSS modules and layers don't work well with each other (like #7002). They still seem fixable within the scope of layers, though. |
There is one thing that a JS modules can, that cascade layers cannot mimmic. This implies that two modules can export different things under the same name and that conflict resolution can be handled by the consumer. This isn't possible in CSS. But it is relatively rare for such naming conflicts to occur in such a way that it cannot be resolved by the author. |
Hmm, actually I found a case where layers can't fix (modified from https://esbuild.github.io/content-types/#css-import-order): Say we are using two libraries @import url(a.css) layer(a);
@import url(b.css) layer(b); However, @import url(reset.css) layer(reset);
/* real business*/ Then the import order is still
I feel like this is a more general issue that we can't arbitrarily re-order imported sheets, and have limited ways to tackle it if these sheets do overlapping things. One way is I don't have a good idea how this can be fixed from the CSS language side. Maybe it's much simpler if CSS authors make sure one sheet has one unique purpose, for example, a sheet doing actual styling should not perform a reset on its own. In other words, each style sheet should be targetting a particular layer in the overall business -- which perfectly matches the purpose of
That's unfortunate. But CSS modules can't help here, either. It's a general design issue of CSS that everything is the global namespace (except layers), and the current solution/relief is to scope things by shadow DOM. |
I don't think this is correct. Any imports in an already layered stylesheet would be further layered. Layer names are global but you cannot define layers outside your own layer and you cannot place styles in another layer that is not your own. |
Sorry if my previous comment was a bit sloppy. The final layer ordering should be
Here |
@romainmenke That's indeed the chat that I heard regarding CSS Modules. EDIT: I don't recall where that was. Maybe it was in WICG/webcomponents#759. |
Looks like I overlooked a behavior of Layers with respect to To make the topic easier to understand, I decided to make a working reproduction of the original issue on CodePen. See this set of pens:
We can see that in the final result, the text is pink, although we expect it to be cyan. This is because Now, here's the same thing ported to Layers:
In the layers example, the final result is cyan as we wanted, not pink, although it seems like this will get confusing because we still have all the duplicate sheets, but now we have more layers than is obvious across all of them, and this may cause other issues if I'm seeing things correctly. What exactly is happening with the layers version? |
Hi @trusktr, Can you specify your question? |
This question?
Can you explain how we end at the cyan result in the layers version? What is happening with the layers? Can you describe the structure of the stylesheets and the layers as they relate to those sheets? |
Also just read through most of WICG/webcomponents#759 and if I am reading that correctly there are a lot of voices who want to align JS and CSS dependency graph behavior. But there is also very good info on why they are different. Most notable WICG/webcomponents#759 (comment)
I am assuming you are referring to : @layer some-lib.some-lib.some-lib {
h1 {
text-decoration: underline;
}
} Where When writing Any cascade layers inside the loaded stylesheet would become nested cascade layers. Do this a few times and you end up with This is surprising when coming at this from a JS module graph mental model but important to keep in mind that cascade layers are a different feature and they do not need to fit that mental model. They there are different doesn't meant that they aren't useful for conflict resolution between loaded styles. I don't want to go into too much detail here because there are good docs for cascade layers and they aren't the focus of this thread.
But this is a really artificial example and I think that it isn't a good demonstration of the utility of cascade layers. That all the nested cascade layers share the same name part also doesn't help in discussing it :)
See the first note :) I think this specific example mainly demonstrates that CSS becomes a mess when you import a complex dependency graph with nested cascade layers while re-using the same name part for each cascade layer. Trying to coherently describe its structure would be very hard. I want to try to do that, but not sure how helpful that would be to the overal thread :) |
I think this would create a very unpredictable mental model when it comes to predicting the outcome of the cascade. Even in JS, it's not exactly how modules behave, since you still get the order of dependencies and exports, it's only the side effects and HTTP requests that are skipped. I think we need a discussion about the use cases this is trying to solve. |
We all agree that this isn't about optimizing HTTP requests. I think comparing it to JS modules is very apt:
Perhaps |
What does "executed only once" mean in terms of the cascade? |
Cascade Layers (#4470) does give us the ability to manage duplication caused by files
@import
ing the same shared files: it allows us to place the (duplicate) imports in a high-cascade layer so that duplicates will not override the overrides of files that import the shared code to override the shared values.(If that's not clear, let me know and I'll make a live example of the unexpected problems it causes.)
Although this will help, it won't actually prevent the duplication.
Adding something like
@import-once
would solve the duplication problem regardless of cascade layers, but would still be complimentary to cascade layers.@import-once
would do what the name implies: it only does what@import
normally does when a fully-qualified URL has not been imported before.@import-once "/some.css"
it behaves just like@import
.@import-once
will have a link to the sameCSSStyleSheet
in theirimport-once
rule object.Only one stylesheet with content from
some.css
will be loaded. The behavior is based on the fully-resolved URLs, otherwise differing relative paths pointing to the same file would still load, causing duplication.So it's not completely a no-op after the first occurrence: it still needs to resolve the URL, and it determines whether to continue or not based on the fully-qualified URL.
Sidenote, CSS ES Modules will behave like
@import-once
. They also talked about making@import
in CSS Modules behave like the@import-once
idea here for various reasons (but that would be a hack, better to let the CSS work as expected, and give CSS@import-once
in addition).Also see the
@import (once)
syntax from Less. It is the default behavior for@import
in Less.@import-once
syntax is bike-sheddable.The text was updated successfully, but these errors were encountered: