emmet
emmet copied to clipboard
Support for css modules in jsx/tsx
Abbreviation | Current Behavior | Desired Behavior |
---|---|---|
.foo |
<div className="foo"></div> |
<div className={styles.foo}></div> |
Sorry for reopening this issue, but #533 was closed by the author and I didn't want it to quietly disappear. I've wanted this for years and haven't found a clean solution yet. I took a look at the emmet source code and I can't think of a clean way to implement this myself.
@sergeche you mentioned you would try to implement this in an upcoming emmet version - did you manage to do it?
In new implementation it’s supported as div[class={styles.foo}]
. I’ll think about new syntax for such case: changing basic .foo
is a very bad decision, maybe .{foo}
?
I was thinking this should be some kind of config, not really messing with the basic abbreviation itself (which I agree would be a disaster). I also think that creating a custom syntax that produces this behavior is undesirable, even if the syntax is extremely simple.
A very simple idea I had would be a user-provided post-expansion function, where it takes the expanded abbreviation as input, and produces an arbitrary string as output, that then replaces the expanded abbreviation. We already have the concept of filters/actions and the .json
files, so there is some precedent
This could be generalized to cover many use cases, not just this one. For instance it would be relatively trivial to convert className="foo"
to className={styles.foo}
with some regex
As an aside, my workaround for this issue is:
- Create a custom regex that replaces
className="foo"
withclassName={styles.foo}
- Create a macro that selects all text and replaces it using the regex above
- Whenever I expand something on emmet, I also run my script to "fix" it
This is obviously an ugly hack that has a bazillion problems (such as trashing your cursor position and needing to be run manually instead of after expansion)
In new Emmet, abbreviation is parsed into AST so you can walk over abbreviation nodes and rewrite it as you like. The problem is that Emmet has multiple implementations: in JS and Python (also unofficial Java implementation in WebStorm) so there’s no single solution. A custom option might work here but for now it will be applied globally to Emmet, not per-project
A custom option that is applied globally would work for me. I mean vscode already has workspace-specific customizability for emmet (AFAIK) so I would still reap the benefits.
Also, even though I need emmet to expand .foo
to <div class="foo"></div>
in one project (django templates) and <div className={styles.foo}></div>
in another (react app), it is very rare that I switch from one project to another in the same programming session, so I could live with having to manually change the setting back and forth.
A way to hook into the AST and modify the nodes myself would also be perfectly fine, I imagine I could write the transformer easily enough
Is the transformation from class
to className
for jsx/tsx in the new emmet using the AST transformer you mentioned, or is it still handled as a special case as in the current implementation?
This setting would affect JSX context only, e.g. your HTML and Django templates would behave as usual. Also, new Emmet will use web-based UI for config, although JSON config in editor might still be possible
This feature will be super useful for me too. thanks
Absolutely need this feature. Also div.{foo}
sounds great 🙌
How about div..foo
? It would be super fast to type.
@svobik7 sounds good. I guess the only possible value here is a literal? You won't type, like, string here? Something like div.{"my class"}
?
@sergeche sure. If multiple classNames are needed it could work same as single dot statement:
-
div..foo
--><div className={styles.foo}>
-
div..foo..bar
--><div className={${styles.foo} ${styles.bar}}></div>
But I think that the second use-case is out of the scope of this issue for now.
@svobik7 sounds good. I guess the only possible value here is a literal? You won't type, like, string here? Something like
div.{"my class"}
?
I guess you could, since kebab class names have to be transformed to <div className={styles["my-class"]}/>
, but that is a bit exotic. I'm fine without it.
The multiple classes would be nice to have but I can live without. I'd also prefer it transformed to <div className={cn(styles.foo, styles.bar)}/>
but at that point you might as well write a plugin
Heres an extension that I found that converts to css modules syntax. Found this to be the best solution until we get emmet support.
https://marketplace.visualstudio.com/items?itemName=jingxiu.CSS-Modules-transform
Excuse me, is this problem being handled?
BTW, is there any way to uses styleName
instead of className
?
The styleName
is from https://github.com/gajus/babel-plugin-react-css-modules.
Abbreviation | Current Behavior | Desired Behavior |
---|---|---|
.foo |
<div className="foo"></div> |
<div styleName="foo"></div> |
Any official progress?
@oychao not yet, but thanks for reminding me, will take a look
@sergeche Any chance I may remind you of this once again? Would really appreciate the feature!
Just imagine how many people are writing react jsx/ tsx on daily basis. How many of them are using module css? Probably a lot.
And the best way I could find thus far in VSCode is to write div.{styles.myClass which turns into the complete <div className={styles.myClass}>.
Now I am also getting a warning when using NX monorepo scaffolding to generate libs in TypeScript that the above should be written as <div className={styles['myClass']}> which I cannot get with emmet at all.. it's a pain :) In order to at least get rid of the lint warning for the time being, we can remove "noPropertyAccessFromIndexSignature": true from the tsconfig of the library.
Hopefully emmet will properly support module css in react at some point.
Would be cool to be able to customize things like this with plugins. Then using styleName prop with react-css-modules would be easy, just replacing className with styleName. Or someone could use other name for css import, not just styles
.
Hi man, have you some news ? 🙄
Any progress??
There’s already some basic JSX support. Let’s sum up what features should be implemented:
- Support new
..
syntax in JSX to producestyleName
instead ofclassName
attribute. - Add new option to configure prefix for
styleName
, e.g.jsx.styleNamePrefix: "style"
. If given, the class name provided after..
will be prefixed with given prefix object notation:div..foo
→<div styleName={style.foo}>
,div..foo-bar
→<div styleName={style['foo-bar']}>
.
That’s right?
and for vue 😄
<div :class="$style.icon">
</div>
I just pushed update (not released yet) which adds the following:
- Option to override attribute name (in any syntax, not just JSX). E.g. you can map
"class": "className"
so when you writediv[class=123]
,<div className="123">
will be outputted. - Option to add value prefix to any attribute. If prefix is specified, it considered as prefix in object notation and automatically converts value type to expression (in JSX-like syntaxes value it will be outputted as
attr={...}
). - Multiple consequent shorthand attributes (e.g.
##
and..
) now has special internal property which can has its own attribute mapping with*
suffix, for example:{ "class": "className", "class*": "styleName" }
. So.foo
→<div className="foo">
,..foo
→<div styleName="foo">
.
Summing up, by default Emmet will work as follows:
equal(expand('.bar', { syntax: 'jsx' }), '<div className="bar"></div>');
equal(expand('..bar', { syntax: 'jsx' }), '<div styleName={styles.bar}></div>');
equal(expand('..foo-bar', { syntax: 'jsx' }), '<div styleName={styles[\'foo-bar\']}></div>');
equal(expand('.foo', { syntax: 'vue' }), '<div class="foo"></div>');
equal(expand('..foo', { syntax: 'vue' }), '<div :class="foo"></div>');
See https://github.com/emmetio/emmet/blob/master/test/expand.ts#L106-L114
And you can customize attribute mapping and value prefixes with markup.attributes
and markup.valuePrefix
options. Here’s how it’s done in default config:
jsx: {
options: {
'jsx.enabled': true,
'markup.attributes': {
'class': 'className',
'class*': 'styleName',
'for': 'htmlFor'
},
'markup.valuePrefix': {
'class*': 'styles'
}
}
},
vue: {
options: {
'markup.attributes': {
'class*': ':class',
}
}
}
https://github.com/emmetio/emmet/blob/master/src/config.ts#L390-L409
If this is the expected behaviour, I’ll release it as next version
@sergeche that looks great. Considering most projects exclusively use one strategy for component classes it might make sense to let folks re-map the default, single period behavior (div.bar
). But honestly, it's one extra keystroke and if the extra configuration doesn't feel right then what you have there is totally great.
With this update, you’ll be able to re-map .
operator for entire JSX
I've always used <div className={styles.bar}></div>
, never seen styleName
before... So in my case I'd do
jsx: {
options: {
'jsx.enabled': true,
'markup.attributes': {
'class': 'className',
'class*': 'className',
'for': 'htmlFor'
},
'markup.valuePrefix': {
'class*': 'styles'
}
}
},
right?
Also it would be cool if we could map ..foo.bar
to <div className={cx(styles.foo, styles.bar)}></div>
as well (for classnames or clsx support) but maybe I'm asking for too much haha
@gabriel-peracio Yes, your example is correct.
As for <div className={cx(styles.foo, styles.bar)}></div>
output – might be possible if it’s a commonly used feature
@sergeche How can i customize attribute where can i find that?
@msobkyy it’s not released yet. Will report here as soon as new version comes out