Built in support for CSS bundling. #1302
Replies: 10 comments 22 replies
-
I like the general idea. I had similar ideas before, especially regarding the split into Duplicated CSSWhat happens to the colocated CSS of components used throughout the entire app, particularly across multiple distinct routes? This will inevitably happen, especially in design-system focused projects where you have your Buttons, Inputs, Selects, ... reused multiple times.
All in all, option 2 seems tricky to implement, when thought through. But it might be possible! Interop with Tailwind, CSS-in-JS, ...Ideally, these things would be solution-agnostic. As long as the solution in question can output a precompiled CSS file and can be statically analyzed (like Tailwind or vanilla-extract), this should be theoretically possible. |
Beta Was this translation helpful? Give feedback.
-
UPDATE: I had a conversation with Michael, Ryan, and Kent and they agreed that supporting CSS Modules would be worthwhile for Remix and they plan to support it in some future release. There was also discussion around supporting "bare" css imports and they are going to explore that a bit further before committing to supporting it. |
Beta Was this translation helpful? Give feedback.
-
FYI, I started a discussion over at Tailwind that introduces the idea of splitting generated CSS files per media |
Beta Was this translation helpful? Give feedback.
-
If I hadn't found this discussion just now, I was ready to create it. I've just started work translating a Next app with CSS Modules + Tailwind directives co-located with components into a Remix app, and I've been struggling to find a good way to keep styles together with components. It just creates so much less mental overhead for me when styles are alongside their components in the file structure, and I can have local scoping of those styles without having to think about it. |
Beta Was this translation helpful? Give feedback.
-
Thanks for writing this up Nick, and thanks for taking the time to meet with us. We will be expanding the feature set around importing CSS files motivated by:
We are going to be adding some sort of support for right away for:
And at some point in the future we're thinking about adding direct support for:
Of course, how things pan out once you get into implementation can always change your plan! But CSS Modules are very important for migration from React Router apps. These features would be behind an unstable feature flags in // remix.config.js
module.exports {
// ...
unstable_cssImports: true,
unstable_cssModules: true,
unstable_tailwind: true,
unstable_vanillaExtract: true
} Bare CSS ImportsYou'd be able to import css files directly into modules. /* app/components/button.css */
.btn {
background: blue;
} // app/components/button.jsx
import "./button.css";
export function Button() {
return <button className="btn" />;
} // app/routes/index.jsx
import { Button } from "~/components/button";
export function Button() {
return (
<div>
<h1>Index</h1>
<Button />
</div>
);
} Remix can automatically include the Because the module graph can change, the order the CSS files are included is unpredictable (or at least difficult to control), you'll need to be careful will CSS selectors. Because of that, it makes sense to add direct support for CSS modules to avoid the issue entirely. CSS ModulesRemix could support CSS Modules directly. Files named /* app/components/button.module.css */
.btn {
background: blue;
} // app/components/button.jsx
import { btn } from "./button.module.css";
export function Button() {
return <button className={btn} />;
} Remix would automatically include the Vanilla Extract
Vanilla extract provides a first-class CSS-in-JS developer experience, with the ability to use TypeScript to create CSS abstractions and definitions without the runtime costs of other CSS-in-JS approaches. Since it produces actual stylesheets that can be generated statically with no browser runtime, it's a great fit for Remix. Stylesheets can be cached, preloaded, and prefetched like other assets. Name your files Tailwind
Remix could support Tailwind CSS directly. Simply add a NotesOne import equals one CSS fileThere is no automatic concatenation or splitting involved. Some CSS bundling techniques will try to add all css imports in a module graph into a single CSS file. We could generate a CSS file for each route, but then there will be a lot of duplication across routes. Attempts at splitting and deduping can also lead to incorrect CSS rule ordering and UI bugs. Additionally, these files can become enormous. Remix will not be attempting to do this. One import equals one CSS file. This is also in alignment with the proposed import assertions where likewise one import equals one file, no concatenation, splitting, etc. At some future day, we'll likely change all of this to import assertions instead of file name conventions. But again, as you get into the weeds, perhaps we will change our thoughts here and just concat it all together. Large Number of RequestsIn the case of component CSS, this means a page that imports a lot of components will load a whole bunch of small stylesheets. While it's better to load 3 10kB files than 30 1kB files, the combination of Remix's resource prefetching/parallel loading and HTTP/2/3, in general, should avoid impacting the UX. Of course, if you determine it is impacting UX, applications have complete control over the CSS files being loaded. The solution would be to categorize your CSS and define your rules together in fewer, but larger CSS files. Apps are in control of the UX/DX tradeoff (lose localization of styles, but gain fewer network requests). Instead of CSS OrderingWhen Remix includes the CSS from css imports and your links exports, the question comes up regarding ordering. At the moment, we think that the right approach is to include CSS in the module graph first, and then the css from the route links exports. This way your rules in "app/components/button.css" can be overridden by a route. Because routes know their imports and buttons don't know their routes, this seems like the right approach. However, we'd like to hear if you anticipate the need for css in the module graph to need to override the css in the route links export. If there are solid use cases, we have some ideas on how give apps control of the ordering. Thanks again, we're really excited to get to work on this stuff! |
Beta Was this translation helpful? Give feedback.
-
@ryanflorence I like everything you've said but there is one bit that I'm confused about.. "One import equals one CSS file". This feels wrong. We should be treating component level css files in the exact same way we handle component level JS files. If you have a page with 80 components, are we adding 80 script tags to the page? If I remember correctly, we are generating a JS bundle per route. Sure, if components exists on several routes then technically those components JS code would be duplicated across the bundles. We should be handling the CSS in the exact same way -- generating a bundle per route. Eventually, it would probably make sense for Remix to add in some smart deduplication techniques (like a commons chunk) but, again, we should be treating CSS in the exact same way so if we someday have a "common" js chunk then we should have a "common" css chunk. |
Beta Was this translation helpful? Give feedback.
-
What is the status of CSS support? I'm looking at the Remix, and current solution for styling does not look very inviting (a lot of boilerplate and cognitive load). Are there any plans to implement support for bare CSS imports and CSS modules? |
Beta Was this translation helpful? Give feedback.
-
Just want to register that whatever happens, it would be a bummer if it requires managing the link tags manually. I know that it does provide the greatest amount of control on how CSS is prefetched and what not, but this seems like the kind of thing that a sensible default ought to be able to handle. Having to export links from each component and then splat them on every route that uses them is just very easy to forget and extra boilerplate that I don't want to subject my team to. The extra coupling makes it harder to do refactors and I want that to "just work". Thanks for the movement here @ryanflorence ! |
Beta Was this translation helpful? Give feedback.
-
Good day!
I think this will result in undesired behavior. I'm always having this picture in my head where I have two routes which share many components. With your proposal the routes have two separate stylesheets, but with almost identical content. This is bad because shared styles aren't cached but only the whole stylesheet per route is. I personally think remix should rely on its surfacing styles strategy: https://remix.run/docs/en/v1/guides/styling#surfacing-styles only the way of how this is done should be different, because the current way is very verbose. I've written my own proposal of how this problem shoud be solved here: #4191 |
Beta Was this translation helpful? Give feedback.
-
I really hope this discussion is not dead. Since the discussion have started, there has been many improvements released long time ago, including css-modules 🎉. However, developers, who use css-modules, still partialy hang on the ceiling. As many users of css-modules felt already, I'm talking about code splitting.. Missing ability to split css-modules files in large project causes longer FCP and LCP times (long blocking time in general). It means poor UX and poor performance experience for large apps built on top of the Remix which use css-modules approach Splitting css-modules is available on most popular javascript SSR frameworks: CRA (using React.lazy - not SSR), Next.js, Gatsby, Nuxt, ... ...except Remix This is how Next.js have solved it:
My question is, why Remix have decided not to apply the possibility by today? |
Beta Was this translation helpful? Give feedback.
-
Intro
In order to give myself a little credibility, and to give context on where my opinions are coming from, allow me to give a brief introduction to myself. For the past several years, I've been a Tech Lead at Bloomberg LP and in that time I've managed many large scale, web based applications with millions of users and large-ish teams (15+ persons) working on them
Problem
Remix does not have great support for co-locating a component's styles along with the rest of the component (or writing component styles in isolation). The exercise is left up to the developer. This is problematic (especially in large code bases) because Remix encourages creating stylesheets based on the route so, as a developer, you need to remember every route that uses your component when you'd like to make a change to the components styles. Alternatively, you have to include everything in a single global stylesheet which is also not ideal for "network-tab" reasons.
There are some solutions to this problem in the Remix community but each has their own downsides. (BTW, I'm not against any of these solutions, I'd just like to provide an alternative solution with different trade offs).
Tailwind
Tailwind allows you to use their composable classes to co-locate styles with components. There are two major down sides to this approach:
CSS-IN-JS
Many solutions exist that allow you to author styles directly in JavaScript, alongside the component. This approach also has downsides:
Solution
Remix should support importing css files directly in components and routes. Then, during the build process, a single, bundled CSS file should built for each route and automatically added to the page via a link tag.
What about cool things like adding the
media="..."
attribute to link tags?This is still possible. I'm imagining a process where after the route's css file is generated, we do one final processing step on it and extract all the media queries used in the file. We can then split the file into several files based on the media queries and add each one to the page with a
<Link media="..." />
. (I have a prototype of this already working, so it's definitely doable).What should happen to the existing links api?
I think it should still exist for adding other types of links to the page (including stylesheets that are hosted somewhere else).
Bonus: CSS Modules (not CSS module scripts)
Once basic support for CSS is implemented, it would be trivial to also include opt-in support for css-modules. The benefit of this approach is that all css selector names are mangled for uniqueness and then exported to the javascript with their original name so that developers get a really nice developer experience, they never have to worry about clashing styles, and there would be no performance cost to the end user.
Final thoughts
While not production ready (yet), I have a fully working prototype of this working in Remix. The biggest downside to this is that it would be a breaking change. Interestingly, I believe most apps would continue to work, unmodified after an upgrade because they were importing the css anyway so the css will still end up on the page. The only thing to migrate would be removing the now empty items from the existing
links()
function.Beta Was this translation helpful? Give feedback.
All reactions