react-native-css-modules icon indicating copy to clipboard operation
react-native-css-modules copied to clipboard

Using `className` as a string

Open zcallan opened this issue 6 years ago • 20 comments

Hey! Love your work on this repo.

One suggestion I'd love to see and I'd be happy to help with is to use strings in place of objects or arrays when using react-native-classname-to-style and react-native-platform-specific-extensions.

I find it a bit awkward to write

<View
  className={[styles.wrapper, disabled ? styles.grey : styles[color].join( ' ' )}
  ...

especially when there's a ternary involved or there's many configurable styles to apply.

An related example of a similar transformation can found here:

https://github.com/gajus/babel-plugin-react-css-modules

I was wondering if you have any concerns or know any limitations regarding making this into a Babel plugin, and if you would be interested in this?

Cheers :)

zcallan avatar Apr 10 '18 00:04 zcallan

Thanks for the suggestion @zcallan!

The reason why objects are used instead of strings is that this way the code is both valid CSS module syntax and works with both Native and Web.

Using object's is the simplest way to add support for CSS modules.

If we would use strings, then it would be hard in React Native to create a link between the class used inside the className property with the CSS class selector that is used inside your CSS file.

I'm also a bit worried that adding additional looser syntax will make the source code for the babel plugin more complex.

Maybe you have something in mind that would make it easy to support the string syntax? Let me know and let's see if it is doable!

kristerkari avatar Apr 10 '18 07:04 kristerkari

Thanks for the reply!

My naive solution, and I don't have too much experience writing Babel transforms so I couldn't comment on the complexity of this, would be to convert the strings on the className property to the object notation referencing the style in the corresponding stylesheet.

So this:

import './Button.css';

<View className="wrapper red">
...

would convert to the following:

import styles from './Button.css';

<View style={[styles.wrapper, styles.red]}>
...

(obviously the styles var name will need to be obfuscated to avoid naming conflicts)

From there, the usual transformation code you have written would take place (I assume).

I'm not sure how compatible this setup would be with CSS modules, and as you said implementing this may make the Babel plugin much more complex - however, the tradeoff is having less complex application code, which in scale is a huge benefit in my opinion!

zcallan avatar Apr 10 '18 07:04 zcallan

Thanks! That syntax looks reasonable to implement. The only problem I see with it is that it can not support multiple css imports.

With this we would not know which file maps to which class:

import './Button.css';
import './TextInput.css';

<View className="wrapper red">
...

I'm not sure if the users would understand that? Maybe there is something that could be done to avoid that issue.

kristerkari avatar Apr 10 '18 07:04 kristerkari

It seems that in the library that you linked, an error gets thrown if there are more than 1 imports: https://github.com/gajus/babel-plugin-react-css-modules/blob/54316b0e6046ca99d8eb15fe4c08646863db289c/src/getClassName.js#L90

kristerkari avatar Apr 10 '18 08:04 kristerkari

so, as babel-plugin-react-css-modules is using a custom styleName property, what about if we would create a new plugin react-native-stylename-to-style to support that syntax?

kristerkari avatar Apr 10 '18 08:04 kristerkari

One workaround that the library I linked to uses is to name each of the stylesheet imports, and reference them as foo.red and bar.wrapper, like:

import foo from './foo1.css';
import bar from './bar1.css';

<div styleName="foo.a"></div>;
<div styleName="bar.a"></div>;

(https://github.com/gajus/babel-plugin-react-css-modules#named-stylename-resolution)

It does seem like they are open to suggestions for alternate behaviour, so I'm not sure if they're sold on this setup, but it is a viable approach we could adopt too (although it's not super intuitive).

Otherwise, importing 2 unnamed stylesheets would throw an error like you mentioned.

zcallan avatar Apr 10 '18 08:04 zcallan

I like the idea of using the styleName prop for all of this as it still allows people to access the natural behaviour of className on .web files if they choose to have global styles in their project!

zcallan avatar Apr 10 '18 08:04 zcallan

Alright thanks @zcallan! I'm open for implementing a new Babel plugin to support the different string syntaxes from babel-plugin-react-css-modules.

Would it be possible for you to list all the different uses for the syntax? That way it would be easier to get started with the implementation.

kristerkari avatar Apr 10 '18 09:04 kristerkari

Thanks, sure thing!

Here's a fairly comprehensive list:

--

Unnamed import with single class hardcoded

import './Button.css';

<View styleName="wrapper">Foo</View>

Unnamed import with single class as variable

import './Button.css';

const color = 'red';

<View styleName={color}>Foo</View>

Unnamed import with multiple classes hardcoded

import './Button.css';

<View styleName="wrapper red disabled">Foo</View>

Unnamed import with multiple classes with some variables

import './Button.css';

const color = 'red';

<View styleName={`wrapper ${color}`}>Foo</View>

Unnamed import with multiple classes with all variables

I'm not sold on this one being a feature, but it's a thought

import './Button.css';

const color = 'red';
const size = 'large';

<View styleName={[color, size]}>Foo</View>

Unnamed import with multiple classes with inline ternary

import './Button.css';

<View styleName={`wrapper ${disabled ? 'grey' : 'red'}`}>Foo</View>

Unnamed import with single class and extra style

import './Button.css';

<View styleName="wrapper" style={{ margin: 20 }}>Foo</View>

Named import with single class

import foo from './Button.css';

<View styleName="foo.wrapper">Foo</View>

Named import with multiple classes

import foo from './Button.css';

<View styleName="foo.wrapper foo.red">Foo</View>

Named import with one class from each multiple stylesheet

import foo from './Button.css';
import bar from './Grid.css';

<View styleName="foo.wrapper bar.column">Foo</View>

Named import with multiple classes from multiple stylesheets

import foo from './Button.css';
import bar from './Grid.css';

<View styleName="foo.wrapper foo.disabled bar.column bar.row">Foo</View>

Unnamed import with classNames as variable

import './Button.css';
import cx from 'classnames';

const styles = cx( 'wrapper', {
  disabled,
  [color]: !disabled,
  small: false,
  'snake_case': true,
});

<View styleName={styles}>Foo</View>

Unnamed import with classNames inlined, single line

import './Button.css';
import cx from 'classnames';

<View styleName={cx( 'wrapper', { [color]: !disabled })}>Foo</View>

Unnamed import with classNames inlined, multi line

import './Button.css';
import cx from 'classnames';

<View
  styleName={cx( 'wrapper', {
    disabled,
    [color]: !disabled,
    small: false,
    'snake_case': true,
  })}
>
  Foo
</View>

--

The addition of transforming classNames may be quite more complex than just transforming a string but I'd like to try make this work too.

That's the dream 👌

zcallan avatar Apr 10 '18 12:04 zcallan

Great list, thanks a lot!

The addition of transforming classNames may be quite more complex than just transforming a string but I'd like to try make this work too.

It would be nice to support it, but there is a problem.

The problem is that classnames transforms objects into strings. That will break our transformations as React Native would get strings instead of objects for style property.

kristerkari avatar Apr 10 '18 12:04 kristerkari

So it would be a runtime vs compilation time issue? So, the classnames fn will return to us a string when it's called in the browser/phone instead of on build time where Babel transformations are applied, which we cannot predictively parse to make work with Native...

Perhaps (I know this is adding yet another abstraction, when does it all end?) to remedy the classnames issue we can make another package called react-native-classnames or something of the like.

That in turn could have a Babel plugin as well (so that you can have the option of having a web and native shared codebase) that transforms classnames imports to react-native-classnames imports (similar to how the react-native-web Babel plugin does it).

When the react-native-classnames fn is called, it could return an object that is compatible with React Native's own styling architecture, since I don't really see why it would ever need to be transformed into a string.

Perhaps this is all its own set of packages in itself as well.

zcallan avatar Apr 10 '18 13:04 zcallan

Yeah exactly like you said, we would have to create react-native-classnames to handle the issue and then have another babel plugin to change the import from classnames to react-native-classnames.

There's quite a lot work ahead :)

kristerkari avatar Apr 10 '18 13:04 kristerkari

I started working on the babel-plugin: https://github.com/kristerkari/babel-plugin-react-native-stylename-to-style

it's currently missing almost everything, but I'll be adding features to it one-by-one and I'll release when everything is implemented.

kristerkari avatar Apr 21 '18 14:04 kristerkari

Just want to note quickly that I haven't forgotten about this and plan to do some work but haven't been able to get around to it yet!

zcallan avatar May 03 '18 09:05 zcallan

Great! Let me know if you start working on anything. Also feel free to contribute any fixes or features to the work-in-progress https://github.com/kristerkari/babel-plugin-react-native-stylename-to-style

I was planning on implementing named imports support next.

kristerkari avatar May 03 '18 10:05 kristerkari

As an update I added a PR to implement named imports: https://github.com/kristerkari/babel-plugin-react-native-stylename-to-style/pull/1

Now I have implemented the basic support without any special cases, so the plugin should be quite useful already.

I will merge that PR, add some documentation and release a first version of the plugin. Then after that I can think of adding support for the trickier stuff, like inline ternaries etc.

kristerkari avatar May 25 '18 20:05 kristerkari

First version is now released and there's also documentation on how to set it up: https://github.com/kristerkari/react-native-css-modules/blob/master/docs/setup-stylename.md

kristerkari avatar May 26 '18 09:05 kristerkari

I have now also created an example app for both RN and Web, so that testing new styleName related features will be easier: https://github.com/kristerkari/react-native-css-modules-stylename-example

kristerkari avatar Jun 14 '18 20:06 kristerkari

Thanks for opening this great topic. Can you please update about the progress @kristerkari? Can we have string classNames 🙌🏻

DavitVosk avatar Oct 12 '20 16:10 DavitVosk

@DavitVosk yes, there is the styleName property https://github.com/kristerkari/babel-plugin-react-native-stylename-to-style

kristerkari avatar Oct 15 '20 06:10 kristerkari