Open zhmushan opened 2 years ago
This would be an awesome built-in addition. Does anybody have a good workaround configuration for now?
I can get postcss-modules
to create the JSON mapping while tsup
generates a stylesheet, but I can't find a good way to connect those mappings to id and className selectors while building / watching.
It seems like most folks use rollup + @egoist's https://github.com/egoist/rollup-plugin-postcss for this.
This is my modified approach
esbuildPlugins: [
{
name: "css-module",
setup(build): void {
build.onResolve(
{ filter: /\.module\.css$/, namespace: "file" },
(args) => ({
path: `${args.path}#css-module`,
namespace: "css-module",
pluginData: {
pathDir: path.join(args.resolveDir, args.path),
},
})
);
build.onLoad(
{ filter: /#css-module$/, namespace: "css-module" },
async (args) => {
const { pluginData } = args as {
pluginData: { pathDir: string };
};
const source = await fsPromises.readFile(
pluginData.pathDir,
"utf8"
);
let cssModule = {};
const result = await postcss([
postcssModules({
getJSON(_, json) {
cssModule = json;
},
}),
]).process(source, { from: pluginData.pathDir });
return {
pluginData: { css: result.css },
contents: `import "${
pluginData.pathDir
}"; export default ${JSON.stringify(cssModule)}`,
};
}
);
build.onResolve(
{ filter: /\.module\.css$/, namespace: "css-module" },
(args) => ({
path: path.join(args.resolveDir, args.path, "#css-module-data"),
namespace: "css-module",
pluginData: args.pluginData as { css: string },
})
);
build.onLoad(
{ filter: /#css-module-data$/, namespace: "css-module" },
(args) => ({
contents: (args.pluginData as { css: string }).css,
loader: "css",
})
);
},
},
],
@zhmushan @krailler You guys are amazing, thank you for the assist!
Any official support updates on this @egoist?
CSS modules are pretty much the only way to bundle css safely when developing a component library because the only way to include regular CSS is to inject the styles which can easily clash (think of a button class).
@krailler @mdarche could you share which postcss and postcssModule packages you're using? There appears to be a fair few in the community channels
how about scss/sass ?? need some guide
For anyone still having issues with this, I had success with this:
// tsup.config.js
import { defineConfig } from "tsup";
import cssModulesPlugin from "esbuild-css-modules-plugin";
export default defineConfig({
esbuildPlugins: [cssModulesPlugin()],
});
krailler Thanks, this was the only solution that worked for me.
I've implemented it in a basic library using only tsup if anyone wants to look over it for help (I couldn't find a basic working example anywhere). repo-link:monthpicker-lite-js
@wilkinsonjack1993 using esbuild-css-modules-plugin
my output dist
folder got its files located in dist/src
instead of directly in dist. I did not find any related configuration options on the plugin.
The @zhmushan / @krailler solution worked for me 👍
I have published this plugin based on discussion here. - https://github.com/mayank1513/esbuild-plugin-css-module
Hope it helps!
Hey y'all, esbuild has native css module support now, but it looks like tsup clobbers it by default, if you add
loader: {
'.css': 'local-css',
},
To your tsup config, this outputs css modules correctly.
@figmatom do you have an example tsup.config.ts file you could share?
@TheeMattOliver, fwiw, this is the tsup config in my package.json
after applying the workaround @figmatom mentioned:
"tsup": {
"format": [
"esm"
],
"loader": {
".css": "local-css"
},
"dts": true,
"sourcemap": true,
"clean": true
}
This correctly imports the class names into the bundled .js. Before, I was getting (unminified) output like this in the bundled file for an imported CSS file:
// src/components/InputButtons.css
var InputButtons_default = {};
But with the change above, I'm getting this:
// src/components/InputButtons.css
var InputButtons_default = {
inputButton: "InputButtons_inputButton",
selected: "InputButtons_selected"
};
@fwextensions what is "local-css" in this instance?
I'm getting Error: Invalid loader value: "local-css"
It's probably an npm package?
Thanks
@paulm17
Hi I was having the same problem.
But i upgraded the tsup
version and it was solved.
"tsup": "^5.10.1",
to
"tsup": "^8.0.1",
I've tried all the options here. The problem is what @mdarche said last year.
// src/Badge.module.css
var Badge_default = {};
That's the result of the build process. There's no css to latch onto.
What I should be seeing is something like:
import classes from './Badge.module.mjs';
Finally got a working solution for me. As I said before there was no connection between the js and css. But I finally figured out a way.
Using @krailler example.
let cssModule = {};
I kept wondering why this was blank and found out that this is connection.
Here is an example. Let's say I have the following classes:
.root {
.root--dot {
.label {
.section {
The following code in the build script:
const newSelector = generateScopedName(name, filename);
translates this to:
.m-347db0ec {
.m-fbd81e3d {
.m-5add502a {
.m-91fdda9b {
for the new index.css stylesheet in the dist folder.
My new code appends to the cssModules array with the class name and new selector. Which results in:
// css-module:./Badge.module.css#css-module
var Badge_module_default = {
"root": "m-347db0ec",
"root--dot": "m-fbd81e3d",
"label": "m-5add502a",
"section": "m-91fdda9b"
};
I also found out the config params needed to be in a certain order and indeed v8.0.1 at a minimum needed to be used.
Full code here:
import { defineConfig } from "tsup";
import path from "path";
import fsPromises from "fs/promises";
import postcss from "postcss";
import postcssModules from "postcss-modules";
import { generateScopedName } from "hash-css-selector";
export default defineConfig({
entry: ["src/index.ts"],
format: ["esm", "cjs"],
loader: {
".css": "local-css",
},
dts: true,
sourcemap: true,
clean: true,
esbuildPlugins: [
{
name: "css-module",
setup(build): void {
build.onResolve(
{ filter: /\.module\.css$/, namespace: "file" },
(args) => {
return {
path: `${args.path}#css-module`,
namespace: "css-module",
pluginData: {
pathDir: path.join(args.resolveDir, args.path),
},
};
},
);
build.onLoad(
{ filter: /#css-module$/, namespace: "css-module" },
async (args) => {
const { pluginData } = args as {
pluginData: { pathDir: string };
};
const source = await fsPromises.readFile(
pluginData.pathDir,
"utf8",
);
let cssModule: any = {};
const result = await postcss([
postcssModules({
generateScopedName: function (name, filename) {
const newSelector = generateScopedName(name, filename);
cssModule[name] = newSelector;
return newSelector;
},
getJSON: () => {},
scopeBehaviour: "local",
}),
]).process(source, { from: pluginData.pathDir });
return {
pluginData: { css: result.css },
contents: `import "${
pluginData.pathDir
}"; export default ${JSON.stringify(cssModule)}`,
};
},
);
build.onResolve(
{ filter: /\.module\.css$/, namespace: "css-module" },
(args) => ({
path: path.join(args.resolveDir, args.path, "#css-module-data"),
namespace: "css-module",
pluginData: args.pluginData as { css: string },
}),
);
build.onLoad(
{ filter: /#css-module-data$/, namespace: "css-module" },
(args) => ({
contents: (args.pluginData as { css: string }).css,
loader: "css",
}),
);
},
},
],
});
All that's left, is to import the index.css and it all works. 😄
Hey y'all, esbuild has native css module support now, but it looks like tsup clobbers it by default, if you add
loader: { '.css': 'local-css', },
To your tsup config, this outputs css modules correctly.
How can I make the js file to have an import statement for the css? I don't want to add css import in my consumer app.
CSS modules work properly using local-css, but this makes tailwind classes stop working, because it tries to obfuscate them too, but the reference in the code is not obfuscated. Tried using :local and :global flags as mentioned in esbuild docs, but they are not being detected.
Created another ticket for this here #1176
Upvote & Fund