boopathi / react-svg-loader

A loader for webpack, rollup, babel that loads svg as a React Component
MIT License
638 stars 86 forks source link

feature: provide option to choose how className should be transformed (to support styling using CSS Modules). #138

Closed stsiarzhanau closed 6 years ago

stsiarzhanau commented 7 years ago

When styling components using CSS Modules our code looks something like this.

import React from 'react';
import st from './styles.css'; // or import styles from './styles.css' if we like to type :)
import Icon from './icon.svg'; // it's expected that we have react-svg-loader already installed

function MyComponent(props) {
  return (
    <div>
      <h1 className={st.heading}>Hello, world!</h1>
      <Icon />
    </div>
  );
}

export default MyComponent;

Let's assume our icon.svg looks something like this (simplified):

<svg class="icon">
  <path class="outer" .... />
  <path class="inner" .... />
</svg>

In our styles.css we can write cool things like...

.icon .outer:hover {
  fill: cyan;
}

To get the expected result our transformed SVG markup should look like...

<svg className={st.icon}>
  <path className={st.outer} .... />
  <path className={st.inner} .... />
</svg>

But this loader will transform it like this:

<svg className="icon">
  <path className="outer" .... />
  <path className="inner" .... />
</svg>

And our styles will not work anymore.

So, it would be nice to add an option like transformClassAttrValueTo: <pattern> in order we could specify how our class="foo" should be transformed.

At the moment the only solution I've found is to create manually separate component for every icon...

 function Icon(props) {
  return (
    <svg className={st.icon}>
      <path className={st.outer} .... />
      <path className={st.inner} .... />
    </svg>
  );
}

... and then use it. It works, but it's very tedious, especially when we have a lot of icons.

So, I hope you will find time to add this useful feature ASAP. Thanks in advance.

boopathi commented 7 years ago

Not sure if this will be too specific - using it just for classnames and not other properties. You can of course fork the project and just add this particular transform and use it though.

PR / API suggestions welcome anyway πŸ˜ƒ.

stsiarzhanau commented 7 years ago

I've found a workaround to achieve my goals. There's relatively new package called babel-plugin-react-css-modules. Using it i can just write <h1 styleName="heading">Hello, world!</h1> instead of <h1 className={st.heading}>Hello, world!</h1>. styleName will be converted to class during compilation.

So, to have this work

<svg class="icon">
  <path class="outer" .... />
  <path class="inner" .... />
</svg>

.icon .outer:hover {
  fill: cyan;
}

I just need to create component that will render SVG like this:

<svg >
  <path styleName="outer" .... />
  <path styleName="inner" .... />
</svg>

When I create such a component manually, everything works as expected. But when I use react-svg-loader it removes that styleName attribute from source SVG. I am aware that SVGO includes removeUnknownsAndDefaults plugin which removes unknown attributes like styleName. I disable this plugin into SVGO options. But anyway my styleName attibute is being removed. Could you help me to find out what's going on?

stsiarzhanau commented 7 years ago

When I go to react-svg-loader/lib/loader.js and disable SVGO completely by changing this line

  Promise.resolve(String(content)).then(optimize(query.svgo)).then(transform({

to

  Promise.resolve(String(content)).then(transform({

My styleName="foo" attribute is in place in created component.

But when I disable just removeUnknownsAndDefaults plugin in CLI

 node_modules\.bin\svg2react --svgo.plugins.removeUnknownsAndDefaults false browsersync.svg

or in my webpack config

  {
    test: /\.svg$/,
    include: SRC,
    loaders: [
      {
        loader: 'babel-loader',
        options: {
          presets: ['es2015'],
        },
      },

      {
        loader: 'react-svg-loader',
        options: {
          svgo: {
            plugins: [
              { removeXMLNS: true },
              { removeUnknownsAndDefaults: false },
            ],
            floatPrecision: 2,
          },
        },
      },
    ],
  },

My styleName="foo" attribute is removed.

P.S. I also tried

            plugins: [
              { removeXMLNS: true },
              { removeUnknownsAndDefaults:
                {
                  unknownAttrs: false,
                },
              },
            ],

It didn't help too.

unleashit commented 7 years ago

Also trying to figure out a css modules + automatic inline svg solution.

I have found that svg-css-modules loader will work together with react-svg-loader, but of course the generated classes won't match with an imported css/scss file. It sounds right that some sort of transformClassAttrValueTo: /pattern/ available to the loader is what would be needed.

mritzman-dg commented 7 years ago

I agree, hoping for a way to use css modules with this module as well.

boopathi commented 6 years ago

πŸ‘ Done.

before,

<svg class="foo bar">
// to
<svg className="foo bar">

Now, it is transformed to

<svg className={ (styles["foo"] || "foo") + " " + (styles["bar"] || "bar") }>

So, you can define custom classes for each of the individual classes and also use it with css-modules.