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

Support for css modules in jsx/tsx #589

Open
gabriel-peracio opened this issue Jun 17, 2020 · 45 comments
Open

Support for css modules in jsx/tsx #589

gabriel-peracio opened this issue Jun 17, 2020 · 45 comments

Comments

@gabriel-peracio
Copy link

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?

@sergeche
Copy link
Member

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}?

@gabriel-peracio
Copy link
Author

gabriel-peracio commented Jun 17, 2020

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

@gabriel-peracio
Copy link
Author

As an aside, my workaround for this issue is:

  1. Create a custom regex that replaces className="foo" with className={styles.foo}
  2. Create a macro that selects all text and replaces it using the regex above
  3. 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)

@sergeche
Copy link
Member

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

@gabriel-peracio
Copy link
Author

gabriel-peracio commented Jun 17, 2020

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?

@sergeche
Copy link
Member

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

@dimagimburg
Copy link

This feature will be super useful for me too. thanks

@tusharsadhwani
Copy link

Absolutely need this feature. Also div.{foo} sounds great 🙌

@svobik7
Copy link

svobik7 commented Aug 8, 2020

How about div..foo? It would be super fast to type.

@sergeche
Copy link
Member

sergeche commented Aug 8, 2020

@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"}?

@svobik7
Copy link

svobik7 commented Aug 8, 2020

@sergeche sure. If multiple classNames are needed it could work same as single dot statement:

  1. div..foo --> <div className={styles.foo}>
  2. 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.

@gabriel-peracio
Copy link
Author

@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

@DivMode
Copy link

DivMode commented Aug 20, 2020

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

@hzmming
Copy link

hzmming commented Oct 5, 2021

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>

@oychao
Copy link

oychao commented Dec 13, 2021

Any official progress?

@sergeche
Copy link
Member

@oychao not yet, but thanks for reminding me, will take a look

@Freakspot
Copy link

@sergeche Any chance I may remind you of this once again? Would really appreciate the feature!

@snoop088
Copy link

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

.

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.

@lunavod
Copy link

lunavod commented May 18, 2022

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.

@n1ckfedorov
Copy link

Hi man, have you some news ? 🙄

@HappyMajor
Copy link

Any progress??

@sergeche
Copy link
Member

sergeche commented Aug 9, 2022

There’s already some basic JSX support. Let’s sum up what features should be implemented:

  1. Support new .. syntax in JSX to produce styleName instead of className attribute.
  2. 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?

@kikyous
Copy link

kikyous commented Aug 16, 2022

and for vue 😄

<div :class="$style.icon">
</div>

@sergeche
Copy link
Member

sergeche commented Aug 17, 2022

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 write div[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

@vpicone
Copy link

vpicone commented Oct 5, 2022

@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.

@sergeche
Copy link
Member

sergeche commented Oct 6, 2022

With this update, you’ll be able to re-map . operator for entire JSX

@gabriel-peracio
Copy link
Author

gabriel-peracio commented Nov 10, 2022

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

@sergeche
Copy link
Member

@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

@msobkyy
Copy link

msobkyy commented Dec 2, 2022

@sergeche
How can i customize attribute where can i find that?

@sergeche
Copy link
Member

sergeche commented Dec 5, 2022

@msobkyy it’s not released yet. Will report here as soon as new version comes out

@sergeche
Copy link
Member

Just released v2.4.0 of Sublime Text plugin which features CSS modules support: https://github.com/emmetio/py-emmet/blob/master/CHANGELOG.md#120-2023-01-19

JS version (for VSCode etc.) coming soon. ST users, please check if this feature works as expected or can be improved

@iahu
Copy link

iahu commented Feb 21, 2023

Hi, thanks your great work.

I found that, the current way to use CSS Module on jsx is a little complex.

<div.{styles.foo} // <div className={styles.foo}></div>

and it's seem that, there is no way to customize for that use case, can we?

Just want to be able to config ..foo to work with className with a custom prefix.
e.g.

<div..foo  // <div className={styles.foo}></div>

Thanks.

@sergeche
Copy link
Member

@iahu did you used CSS module feature in Sublime Text editor? It should work as expected if you configure it properly

@iahu
Copy link

iahu commented Feb 21, 2023

@sergeche Thanks your replay.

Yes, I use it in ST.

after your prompt and read this document , I add two configures on my editor.

{
    "config": {
                "markup.attributes": {
			"class*": "className"
		},
		"markup.valuePrefix": {
			"class*": "styles"
		},
    }
}

the valuePrefix works, but attributes name mapping not work. can you give me a more tips for this, thanks very much.

@iahu
Copy link

iahu commented Feb 21, 2023

finally find the way to config it, put the markup.attributes configure under config.jsx.options should works.

{
  "config": {
    "markup.valuePrefix": {
      "class*": "styles"
    },
    "jsx": {
      "options": {
        "markup.attributes": {
          "class*": "className"
        },
        "output.selfClosingStyle": "xhtml",
      },
    },
  },
}

@Jack-Castify
Copy link

@sergeche was just wondering, is this in vscode yet?

@sergeche
Copy link
Member

@Jack-Castify just published v2.4.0 of Emmet npm package which includes this feature. It’s up to VSCode devs to update package and support this feature

@scottwillmoore
Copy link
Contributor

Please note, I have used * to denote where the cursor is placed after expansion.

At the moment .foo expands to <div className="foo">*</div> and ..foo expands to <div styleName={styles.foo}>*</div>, but can be configured to expand to <div className={styles.foo}>*</div>. Also, . expands to <div className="*"></div>.

Do you think we adjust .. such that it expands to <div styleName={*}></div>, and can be configured to expand to <div className={*}></div>. At the moment it expands to <div styleName="*"></div>, and can be configured to expand to <div className="*"></div>.

I like the idea that . will expand to a string ("") while .. will expand to an expression ({}), at least in the context of JSX. I'm happy to submit a PR for this feature, but the exact semantics may need to be clarified.

I'm a very happy user of Emmet and I didn't even know .. was a feature until the Visual Studio Code update, which led me to find this issue!

@sergeche
Copy link
Member

sergeche commented Jul 9, 2023

@scottwillmoore you want Emmet to output {} instead of "" by default for empty class names?

@scottwillmoore
Copy link
Contributor

scottwillmoore commented Jul 10, 2023

Yep!

For "" I still want to use .. At the moment for {} I do . which places my cursor in-between the "", but I then delete them and replace them with {}. With adjust, I could just use .. instead.

.              <div className="*"></div>
.foo           <div className="foo">*</div>
.foo.bar       <div className="foo bar">*</div>

..             <div styleName={*}></div>
..foo          <div styleName={styles.foo}>*</div>
..foo..bar     <div styleName={classNames(styles.foo, styles.bar)}>*</div>

The ..foo..bar suggestion is not required, but I think it makes the most sense.

In addition, provide configuration to adjust styleName and classNames.

..             <div className={*}></div>
..foo          <div className={styles.foo}>*</div>
..foo..bar     <div className={cx(styles.foo, styles.bar)}>*</div>

What do you think?

EDIT: These edge cases should probably be considered... I guess just expand to "" or {} based on the first . or ..?

.foo..bar      <div className="foo bar">*</div>
..foo.bar      <div styleName={styles.foo, styles.bar}>*</div>

@sergeche
Copy link
Member

I guess it will require a separate config in jsx.* namespace to force abbreviation attribute to expression type. Currently, quote type is inherited from abbreviation, e.g. you can type [foo=bar] or [foo={bar}] to get either quoted value or expression. A good place is to add this check in pushAttribute of HTML formatter, which takes incoming abbreviation node and pushes a formatted string into output state.

Also note that for JSX, a special abbreviation type is supported: you can type .{foo} to forcibly create an expression for class attribute.

As for styleName={classNames(styles.foo, styles.bar)} output, where class names should be an arguments of a function, I suggest to upgrade logic that handles markup.valuePrefix option. Currently, if this value is set, it forcibly marks attribute as expression and adds option value as class prefix. You can detect if given value has a shape of classNames(styles.), which provides hint that a) it’s a function call and b) first argument is a prefix for provided class name in abbreviation, which then can be transformed into classNames(styles.foo, styles.bar).

@scottwillmoore
Copy link
Contributor

Okay awesome, will look into it. Is everyone happy with this suggestion if I move forward on it?

Also, I am aware of .{foo}, except it places the cursor (as expected) between the ><, not the {}.

I think .{ should be considered as an alternative syntax for .., to place the cursor between the {}, except most editors have auto-closing brackets which make this difficult to type.

.{}            <div className={}>*</div>
.{foo}         <div className={foo}>*</div>
.{styles.foo}  <div className={styles.foo}>*</div>

@sergeche
Copy link
Member

I think in .{} case a cursor should be inside className value since it clearly states that user wants to type something

@jcnevess
Copy link

jcnevess commented Dec 4, 2023

Hello, sorry if this is the wrong place, but there is there any configuration to make this work with vanilla className and multiple classes?
I am using VSCode and did the configuration described here, but it only works for elements with a single class.

Current Behavior:
..for..bar => <div className={styles['foo bar']}></div> (Error, does nothing)

Expected Behavior:
..for..bar => <div className={`${styles.foo} ${styles.bar}`}></div>

@sergeche
Copy link
Member

sergeche commented Dec 4, 2023

@jcnevess no, such output is not supported

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

No branches or pull requests