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

Do we need real CSS modules? #10518

Open
mayank99 opened this issue Jul 1, 2024 · 3 comments
Open

Do we need real CSS modules? #10518

mayank99 opened this issue Jul 1, 2024 · 3 comments

Comments

@mayank99
Copy link

mayank99 commented Jul 1, 2024

Now that @sheet, functions and mixins have all been greelit, I think it's a great time to visit the idea of "CSS modules".

There is currently such a thing as "CSS module scripts", but that's not really CSS modules. It's a purely JS thing (hence the name "scripts"). I expect real CSS modules to bring a module system into CSS itself, enabling CSS files to properly import other CSS files.

A CSS module would be guaranteed to only be evaluated once. So if a CSS file is imported in two places, they would both get the same module instance. (There was some discussion about this in #6130).

With @sheet, it would also be possible to write CSS files that do not have any side-effects and are therefore safe to import in multiple places.

CSS modules will open up the possibility of lexical scoping and private module members, as well as the ability to rename things when importing them.

Hypothetical example
/* utils.css */

@mixin --visually-hidden {…}
@mixin --whatever {…}
@function --my-private-fn {…}

@sheet classes {
  .visually-hidden {
    @apply --visually-hidden;
  }
  .whatever {
    @apply --whatever;
  }
}

@export(--visually-hidden, --whatever, classes);
/* styles.css */

@import module("utils.css")
  sheets(classes as utilities)
  mixins(--visually-hidden);

@layer reset, page, utilities;

@layer utilities {
  @apply-sheet utilities;
}

@layer page {
  .skip-to-content:not(:focus) {
    @apply --visually-hidden;
  }
}
<style type="module" src="styles.css"></style>
@romainmenke
Copy link
Member

Although I am sympathetic to the challenges and issues I don't think that having an equivalent of JS modules in CSS would be a good thing.

(1) Having multiple ways CSS can behave depending on "is a module" vs "is not a module" is bad on its own. This makes everything more complex to use and understand.

This is not a short term pain. Such a CSS modules system would not be successor to what exists today. We would be stuck with two parallel mental models indefinitely.

(2) Naming conflicts are a real issue, let's solve that specifically. You should be able to handle naming conflicts without needing to switch to a radically different way CSS is applied. Making the ability to handle naming conflicts exclusive to CSS modules wouldn't help all CSS authors.

(3) With cascade layers we already have a tool to determine the order in which CSS is applied. Being able to determine the order makes it possible to solve most issues that could happen by having multiple imports of the same file.

(4) Existing tooling to simulate isolation of styles is very invasive. It needs to dynamically generate all user provided names (class names, custom props, ...) and apply those correctly to JavaScript, HTML and CSS. Any module system for CSS would lack those properties. You would not be able to have two button styles each with .button in their own stylesheet file.

  • the HTML would still have <button class="button">...
  • either of the two files would still "win" and be applied last
  • you could still have some applied declarations from the earlier applied file.

(5) CSS has effects. These are not side-effects, it's just CSS. Maybe this is my primary concern with this (and all similar proposals). CSS has a specific domain and works in a specific way. It might have sharp edges but it does work correctly and intuitively for its intended purpose. Transferring mental models and context from other domains (JavaScript) to CSS doesn't always work well. Making CSS into something else will likely make it worse, not better, even if such behavior is an opt-in.


It would be good to have an example use case where:

  • the issue isn't naming conflicts
  • the issue can't be solved by cascade layers
  • doesn't demand/assume total isolation
@mayank99
Copy link
Author

mayank99 commented Jul 3, 2024

@romainmenke I appreciate you giving this thing some quality thought. That's exactly why I opened this issue. 💜

In my mind, the main use case for "module"-like system is for using pure values (e.g. mixins, functions) in multiple files without producing more CSS.

Today, if you @import "mixins.css", it is equivalent to inlining the contents of mixins.css at the call-site (alternatively, it's kinda lika a data-uri). If you do this in 20 places, you will have 20 instances of everything from mixins.css. I guess it still "works" because whatever comes last will override previous instances of the same name, but hopefully it's clear that this is a problem.

In comparison, when you import a module, you get to properly use things from it without repetition and without unintentionally polluting to global namespace (e.g. the mixins and functions will only be available to the importer and not to other files). Each of those 20 importers will get to import members from the exact same shared module.


(1) Having multiple ways CSS can behave depending on "is a module" vs "is not a module" is bad on its own. This makes everything more complex to use and understand.

I agree it's making things more complex, but it might be a necessary thing to do? Today, nobody wants to use @import in production because its behavior is so undesirable. At best, today's @import is a marker for build tools to concatenate CSS.

It's possible that @import could be "fixed" to behave in a more sensible way. Said differently, maybe @import could be turned into CSS modules? It doesn't have to be a totally new system, especially if you're not using any new features.

(2) Naming conflicts are a real issue, let's solve that specifically.

I agree naming conflicts (or lexical scoping, more generally) is something that should be solved and would love to see other proposals to tackle this problem. I just also thought you should get it for "free" if using CSS modules.

(3) With cascade layers we already have a tool to determine the order in which CSS is applied. Being able to determine the order makes it possible to solve most issues that could happen by having multiple imports of the same file.

I'm a huge fan of cascade layers and I've been using it heavily ever since the day it shipped in all three browsers. But it solves a different problem. I don't consider "order of CSS" to be a major problem anymore.

(4) Existing tooling to simulate isolation of styles is very invasive. … Any module system for CSS would lack those properties.

I don't see why this is relevant here. The purpose of a module system is not to solve DOM scoping or to replace JS-based tooling (despite the confusingly overlapping "CSS modules" name). For those problems, I believe @scope and shadow DOM encapsulation are the correct solutions.

(5) CSS has effects. These are not side-effects, it's just CSS.

This is exactly the problem, imo. The default behavior of CSS is to "style something on the page". This is fine as a default, but it makes it hard to reference a CSS file without instantly "using" it. A module system splits the "import" and "use" part into two stages.

Earlier I mentioned mixins and functions as being "pure" values, but there's also @sheet, which allows for CSS to exist in an inert state until it is specifically applied. This makes it possible to write CSS that might potentially be loaded ahead-of-time but not used until later. I believe if we get a proper module system, we'd also be able to tap into more powerful features like modulepreload.

Transferring mental models and context from other domains (JavaScript) to CSS doesn't always work well.

You don't necessarily have to look at JavaScript. There's precedent for something like this in the CSS world (e.g. Sass modules). And one day we'll hopefully also have HTML modules.

@romainmenke
Copy link
Member

romainmenke commented Jul 6, 2024

Today, if you @import "mixins.css", it is equivalent to inlining the contents of mixins.css at the call-site (alternatively, it's kinda lika a data-uri). If you do this in 20 places, you will have 20 instances of everything from mixins.css. I guess it still "works" because whatever comes last will override previous instances of the same name, but hopefully it's clear that this is a problem.

Can you specify what the problem exactly is?
To me it isn't clear from context.

(browsers optimize multiple imports of the same file)


Today, if you @import "mixins.css", it is equivalent to inlining the contents of mixins.css at the call-site (alternatively, it's kinda lika a data-uri). If you do this in 20 places, you will have 20 instances of everything from mixins.css. I guess it still "works" because whatever comes last will override previous instances of the same name, but hopefully it's clear that this is a problem.

In comparison, when you import a module, you get to properly use things from it without repetition and without unintentionally polluting to global namespace (e.g. the mixins and functions will only be available to the importer and not to other files). Each of those 20 importers will get to import members from the exact same shared module.

This sequence of arguments seems forced.

I don't see how having the (still unclear) problem of importing the same stylesheet 20 times relates to isolation and naming conflicts.

Are you sure that you aren't trying to solve naming conflicts?


(1) Having multiple ways CSS can behave depending on "is a module" vs "is not a module" is bad on its own. This makes everything more complex to use and understand.

I agree it's making things more complex, but it might be a necessary thing to do? Today, nobody wants to use @import in production because its behavior is so undesirable. At best, today's @import is a marker for build tools to concatenate CSS.

@import does have an unsolvable problem.
It introduces a waterfall pattern to the network requests to display your page.

Browsers can't know ahead of time which @import's will exist in a stylesheet without first fetching and parsing that stylesheet. If you already do know all the stylesheets referenced with @import ahead of time, then you can simply use <link href="foo.css" rel="stylesheet" /> for each. This conundrum makes it hard (impossible?) to use @import and still have a fast website without needing to rely on complex tooling.

But that doesn't seem to be the issue you are referring to, right?


I've spend a great deal of time creating a test suite for @import behavior in the past year and that this is used to improve interop between dev tools: https://github.com/romainmenke/css-import-tests?tab=readme-ov-file#current-state

postcss-import, @csstools/postcss-bundler and esbuild all support creating bundles with @import that very closely mimic native behavior. (lightningcss also aims to follow this behavior, but it has more open bugs) That means that all dedicated CSS bundlers support @import as specified and implemented by browsers.

As far I know, no one opened an issue about this behavior with one of those tools.

Saying that no one uses @import because its behavior is undesirable is simply untrue.
Both tools and CSS authors use it extensively.


(4) Existing tooling to simulate isolation of styles is very invasive. … Any module system for CSS would lack those properties.

I don't see why this is relevant here. The purpose of a module system is not to solve DOM scoping or to replace JS-based tooling (despite the confusingly overlapping "CSS modules" name). For those problems, I believe @scope and shadow DOM encapsulation are the correct solutions.

Ok, but that leaves me more confused what problem you are actually trying to solve.


(5) CSS has effects. These are not side-effects, it's just CSS.

This is exactly the problem, imo.

🤔


Can you provide a demo that is somewhat realistic and explains the issue better?
Maybe a github repo with a bunch of stylesheets and an html page?

It would be good to know what CSS you are writing now and how the outcome isn't want you want or expect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants