-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[MVP] Bundle CSS modules #20
Comments
Would this include font files, or is that a separate task? |
Binary resources that don't need to be parsed such as PNG, JPEG, and font files are all the same to a bundler. Fonts don't need to be special cased as far as I'm aware. I think the existing loaders such as |
I'm starting to work on CSS support. I just landed a basic parser and AST, and will be working on integrating it into the build system next. Note that I'm not planning to support the full CSS ecosystem with this feature. Today's CSS is a very diverse ecosystem with many non-standard language variants. This is possible in part due to the fault-tolerant nature of CSS. I'm imagining this feature as mainly a "CSS linker" that is responsible for the critical parts of joining CSS files together and removing unused CSS code. The CSS parser will be expecting real CSS for use with browsers. Non-standard extensions may still be parsed without syntax errors but may be passed through unmodified (e.g. not properly minified or renamed) since they aren't fully understood by the parser. It should be possible to use syntax extensions (e.g. SASS) with a plugin that transforms the source code before esbuild reads it. |
@evanw that's great to hear! There are different kinds of CSS modules out there, and there is also ongoing for a web standard for CSS modules (see whatwg/html#4898 and https://github.com/tc39/proposal-import-assertions). WIll esbuild allow specifying which variant to use? |
@evanw will the CSS feature inline the minified CSS in the javascript bundle or emit it as a separate file? While inlining is a good fit for client rendered applications, it is not ideal for server or static rendered applications. Ideally, esbuild would emit a number of CSS chunks, and the metafile would detail which CSS chunks correspond to each javascript entrypoint. A server or build tool would then be able to add references to the CSS for each page using |
Import assertions seem unrelated to CSS to me. They just look like a way to tell the runtime what the file type is. Bundlers already have a way of doing this: they just use the file extension without needing an inline annotation, so this information is not necessary for bundlers. This seems like an API to inject normal CSS inside a shadow DOM element? It looks like it would just be normal CSS as far as the bundler is concerned. The CSS module implementation I'm thinking of adopting is the original one: https://github.com/css-modules/css-modules. Specifically, the use of local-by-default names, the
It will be in a separate file. I believe this is the current expected behavior. It's how Parcel works, for example. I was not considering inlining it in the JavaScript code. I am also planning on having code splitting work the same way across JavaScript and CSS files. |
I listed import assertions just to indicate that there will be a standard way to import non-JS files. The idea for the standard CSS modules is that they will return an instance of My main concern is about what's enabled by default in esbuild, so far most (all?) things require an explicit opt-in which I think is important. |
Interesting, thanks for the clarification. FWIW I think this should be trivial to do with a plugin. That will let you experiment with this proposal.
My current plan:
|
Thanks, I think that default makes sense 👍 |
That behavior sounds ideal. Can't wait for it! |
The newly-released version 0.7.7 now has experimental CSS support. From the release notes:
There's still a long way to go but this feels like a good point to publish what's there so far. It should already be useful for a limited set of use cases, and then I will expand use cases over time. This feature will be especially useful with the addition of the plugin API (see issue #111) because then esbuild's bundler can be a "CSS linker" that runs on the output of whatever CSS post-processor you're using. The plugin API hasn't been released yet because I wanted to get basic CSS support in first so that there are at least two different core file types for the API to abstract over. |
FYI for people following this issue: #415 was recently implemented making it possible to bundle |
Hey there, good work on this. Just wanted to share my experience of trying this out. I was hoping that I'm aware that there is a gazillion ways of handling styles on the web. I'm also aware that the expectations for how this might work varies greatly from web developer to web developer. I'm also aware that many tools do the module-returning-class-names thing, probably the vast majority. But I would argue strongly in favor of
Web developers who work with Shadow DOM are relying on being able to append stylesheets to a root at any point in the DOM tree, and won't benefit from, say, the imported styles being appended to the document root. |
@wessberg Can you confirm if the plugin API lets you write a plugin to enable the The form of CSS modules where class names are local to the file and are exported to JavaScript seems better for building scalable web applications to me. That enables tree shaking and code splitting for CSS, which other solutions don't support AFAIK. Tree shaking of CSS isn't really possible to build as an esbuild plugin because it requires deep integration with the JavaScript layer. So I am still planning to explore this in esbuild itself. |
I will test it out and get back to you :-) And again, while I realize some other bundlers are doing something similar to what you're planning for with CSS support in Importantly, it leaves out everyone working with Shadow DOM where styles are applied at roots across the DOM tree rather than globally at the document root, and where styles are already local to the tree from the Shadow root they are being applied to. These users will either need to declare the styles in JS/TS and either inline them in a I think that the more "standards-compliant" (I put this in quotes, because standardization efforts are still ongoing here) approach should be default, and the more application-specific behavior should come from plugins. But I of course respect your decision. I come from Rollup, which is pretty founded in being as close to native ES module semantics as practically possible, but there are obvious advantages to |
Promised I would get back to you. The plugin system works great! :-) It works as expected. Would be neat if plugins could get ahold of the |
Hey Evan! I was just curious if you have any roadmap for CSS, by chance. I saw mentions of features that are still lacking (#468 (comment)) plus mentions that a CSS rewrite is on the to-do list (#519 (comment)), but there’s no full picture at the moment, as far as I’m aware. I’m writing this primary because we at Framer are migrating to ESBuild, and there’re two CSS issues we’re currently facing:
So I’m trying to figure out how much we should invest into workarounds for these on our side. (Like, what if the CSS rewrites comes out in a week and fixes both issues?) |
Yeah CSS support isn't fully baked yet. My first priority right now is to fix JavaScript code splitting since that's a more widely-used feature. After that, getting CSS to a better state will be my first priority. I'm currently working on a rewrite of JavaScript code splitting to address a number of related features: #399, splitting for I have limited time to work on this at the moment due to the holidays, so all of this is not going to come out next week. I'm hoping for it to land in the next few months. |
That was mostly what i wanted to get out of my comment... i would not expect either to get anything else out other than a |
^ ooo we finally have CSS modules / local class names implemented 👀 |
Thanks ❤️ for the native implementation. In case you need support for |
Amazing! But unfortunately it's only useful for use with JS imports. And that is no good for use within server rendered HTML. Step in the right direction, but quite limited - at least for me. |
Wait, the renaming is happening in the build step and imports are just references to string values of css classes. Am I missing something? |
Yes, but the exported class names will get renamed if and when collisions occur. |
An initial implementation of the local CSS name transform has now shipped in version 0.18.14. It's off by default for now and must be manually enabled with either the In addition, esbuild's support for CSS source maps was overhauled to be closer to parity with JS. There are now per-token source mappings in esbuild's CSS output, and esbuild uses the If anyone wants to try it out, it would be interesting to hear what you think. Please consider the implementation experimental and subject to change as I plan to improve it more in the future (which is why this issue is still open). |
👍 I could expect that this css preprocessor tech becomes less relevant the more ppl start to use custom elements (web components) It's unfortunately that the term "CSS Module" became overloaded. |
I tested my existing plugin for native CSS modules and because plugins are resolved first, it's not an issue even if default is turned on. Though right now we can't differentiate between code that loads import { button } from './button.css';
import globalStyleSheet from './globals.css' assert { type: 'css' }; Also, files only load once meaning you can't opt into using import globalStyleSheet from './globals.css' assert { type: 'css' };
import { button } from './globals.css'; Nothing major, but just edge cases. Maybe something in |
Using the Import assertions are potentially going to be replaced with something else called import attributes, which will hypothetically use the Given that the standards committee has already changed their mind and "unshipped" this proposal after it reached Stage 3, I’m planning to wait to add support for import attributes to esbuild until after there’s an implementation of it shipping in a real JavaScript runtime. This also means waiting to add support for it to any built-in CSS behavior as well as to esbuild’s plugin API. |
This comment was marked as off-topic.
This comment was marked as off-topic.
As I mentioned earlier, this is only useful for use with JS imports. And that is no good for use within server rendered HTML. So is quite limited. Do you have plans to expand this to expose the renamed class names, perhaps as part of the metafile? |
@jimmywarting This issue is about implementing https://github.com/css-modules/css-modules, not about import attributes. We can discuss how to integrate import attributes into esbuild after they are shipped in a real browser. Please leave the discussion of import attributes out of this issue. @joelmoss I saw your comment but I'm confused. By server-rendered HTML I'm imagining something like this: import { button } from './button.css'
console.log(`<div class="${button}"></div>`) This should already works with what was shipped, as you can see here. Can you elaborate about why that doesn't work in your case? |
The problem is that you are assuming that I will only be using JS and importing the CSS, which is not always true. Sometimes I'm including CSS with To make local-css work outside of JS, esbuild will need to expose the exported CSS classes perhaps as part of the metafile, or support a way of making the exported class names deterministic, similarl to how webpack's CSS-loader does it using Right now it is not a huge issue for me, as I have already built my own naive implementation of CSS modules, which works so far. But I would much prefer an esbuild native solution. Hope that helps explain things a little. thx |
I think including the CSS with a To answer your deeper question: I'm open exposing the name mapping somehow at some point, but I haven't figured out how yet. Having specific example use cases for this data to think through will be helpful when adding that feature so thanks for describing what you want to achieve. |
My specific use case is with Ruby on Rails, so the server generated HTML is from Ruby, not JS. But sometimes I use React, which is why I have a mix of JS and server (Ruby) generated HTML. When the HTML is generated from the server, one or more stylesheets are also included as Now because these renamed class names are deterministic, and I already know the I have a little helper that I can call on the server by simply passing it the name of the class and the path of the CSS file. So as an example, I have a CSS file at .myclass-abcd1234 {
color: red;
} <div class="myclass-abcd1234" /> The above would actually be far better than reading any exposed name mapping, as I do not not need to know anything about the mapping. It would also be a lot less work to implement. There is also no chance of conflicts, as it is safe to assume that all classes within each stylesheet are unique. Hope that helps explain my use case. |
Can’t wait for built-in implementation! The existing plugins don’t work well in some cases because they all seem to be based on lightningcss, which requires native Node modules and appears to be poorly compatible with Yarn’s zero-install mode. |
+1 for the provided impl in "last" release, fulfills my expectations and needs for now. Thanks a lot. |
Question about the (highlighted) last sentence above: I tried setting |
I believe you need a plug-in to transform SCSS to the custom CSS syntax with |
@clshortfuse Yeah, I'm using |
@asbjorn-brevio Yes. If a plugin wants to make use of the // build.mjs
import { build } from "esbuild";
import { compile } from "sass";
var sass_module_plugin = {
name: "local-sass",
setup({ onLoad }) {
onLoad({ filter: /\.module\.scss$/ }, (args) => {
const { css } = compile(args.path);
return { contents: css, loader: "local-css" };
// ^^^^^^^^^^^
});
},
};
await build({
entryPoints: ["index.js"],
bundle: true,
outdir: "dist",
plugins: [sass_module_plugin],
}).catch(() => process.exit(1)); // index.js
import { foo } from "./foo.module.scss";
console.log(foo); /* foo.module.scss */
$name: "foo";
.#{$name} {
color: red;
} |
@hyrious Got it, thanks! I've postponed migrating to esbuild awaiting this feature so I haven't internalized how this all works yet. Sorry for the noise. |
FWIW, |
I'm considering this to be implemented now that support for |
//index.module.less .roleAuthorization {
:global {
.header::after,.content::after{
content: '88888888';
}
}
} after local-css loader, turn css like: .index_module_roleAuthorization .header::after,
.roleAuthorization .content::after {
content: "88888888";
} the right css should be: .index_module_roleAuthorization .header::after,
.index_module_roleAuthorization .content::after {
content: "88888888";
} is local-css can't support ',' in css? |
I want esbuild to demonstrate that it's possible for a normal web development workflow to have high-performance tools. I consider bundling CSS modules a part of my initial MVP feature set for esbuild since some form of CSS modularity is essential for a large-scale web app and I find CSS modules an elegant solution. This issue tracks the CSS module bundling part of my MVP.
At the bare minimum, importing a CSS file should ensure it's included in the bundle. I plan to also implement CSS modules, which makes CSS selector names local to the file and allows JavaScript to import the selector names as strings.
There are interesting extensions to think about once I get everything working. Tree shaking of unused CSS is an optimization that seems worth exploring, for example. That should be possible with CSS modules since unused imports correspond to unused CSS.
The text was updated successfully, but these errors were encountered: