svgr icon indicating copy to clipboard operation
svgr copied to clipboard

How to remove <svg> element?

Open emlai opened this issue 2 years ago • 10 comments

We need to transform our icons to the following format, i.e. without the parent <svg> element:

const HomeIcon = createSvgIcon(
  <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />,
  'Home',
);

By default svgr includes the <svg> element in the output. How can this be done in svgr?

emlai avatar Aug 31 '22 15:08 emlai

@emlai SVGR transforms SVG icons to React components, how you expect you react component to look like in the output?

headfire94 avatar Sep 03 '22 06:09 headfire94

I don't think it's possible with this plugin. you can write your babel-plugin to transform svg to empty tag https://react-svgr.com/docs/custom-transformations/

headfire94 avatar Sep 03 '22 07:09 headfire94

I solved this by defining a custom babel plugin to remove the parent <svg> element (added under jsx.babelConfig.plugins):

const removeSvgElement = (): PluginObj => ({
  visitor: {
    JSXElement(path) {
      if ((path.node.openingElement.name as JSXIdentifier).name === "svg") {
        if (path.node.children.length === 1) {
          path.replaceWith(path.node.children[0]);
        } else {
          path.replaceWith(
            t.jsxFragment(
              t.jsxOpeningFragment(),
              t.jsxClosingFragment(),
              path.node.children
            )
          );
        }
      }
    },
  },
});

emlai avatar Sep 03 '22 14:09 emlai

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Nov 13 '22 00:11 stale[bot]

@emlai Thank you so much! Saved me a ton of time

gajus avatar Feb 17 '23 13:02 gajus

@headfire94 To provide more context, the goal here is to replace top <svg> element with a more flexible React component, e.g.

const iconTemplate: Template = ({ componentName, jsx }, { tpl }) => {
  return tpl`/* Generated file. Do not modify. */
import { IconSvg, type IconSvgProps } from '../../IconSvg';

export const ${componentName} = (props: IconSvgProps) => <IconSvg size={props.size}>${jsx}</IconSvg>;

`;
};

gajus avatar Feb 17 '23 13:02 gajus

Here is a full plugin file to save a few minutes for the next person:

import { type JSXIdentifier, type PluginObj, types as t } from '@babel/core';

/**
 * Babel plugin for svgr that removes svg element.
 *
 * @see https://react-svgr.com/docs/custom-transformations/
 * @author https://github.com/gregberge/svgr/issues/769#issuecomment-1236126769
 */
export const removeSvgElement = (): PluginObj => ({
  visitor: {
    JSXElement(path) {
      if ((path.node.openingElement.name as JSXIdentifier).name === 'svg') {
        if (path.node.children.length === 1) {
          path.replaceWith(path.node.children[0]);
        } else {
          path.replaceWith(
            t.jsxFragment(
              t.jsxOpeningFragment(),
              t.jsxClosingFragment(),
              path.node.children,
            ),
          );
        }
      }
    },
  },
});

gajus avatar Feb 17 '23 13:02 gajus

Just realized there is a better way of doing this: just reference jsx.children

const iconTemplate: Template = ({ componentName, jsx }, { tpl }) => {
  return tpl`/* Generated file. Do not modify. */
import { createElement } from 'react';
import { IconSvg, type IconSvgProps } from '../../IconSvg';

export const ${componentName} = (props: IconSvgProps) => createElement(SvgIcon, props, ${jsx.children});

`;
};

gajus avatar Feb 17 '23 13:02 gajus

gajus' solution using ${jsx.children} doesn't work in my case, I get an error from @babel/template:

@babel/template placeholder "$1": Cannot replace single expression with an array.

emlai avatar Mar 02 '23 18:03 emlai

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar May 21 '23 19:05 stale[bot]