svgr을 활용하여 svg 파일 유연하게 사용하기

hyunwoo Jin·5일 전
0

배경

프로젝트 초기 구성 당시 public/assets 폴더 내부의 svg 파일을 경로로 가져와서 사용했다.

ex) <img src='public/assets/….svg'/>

하지만 이 경우 매번 경로를 입력해주어야하는 불편함이 있다.

또한 스타일 가이드의 아이콘이 색상이 바뀌어 사용될 때 다른 색상의 아이콘을 피그마에서 매번 export 해야한다.

장기적일 경우 프로젝트가 무거워질 것이다.

그래서 svgr 라이브러리를 통해 svg 파일을 React components 로 변환하여 프론트엔드 친화적으로 구성할 예정이다.

그리고 svgo 라이브러리를 활용하여 svg 파일을 최적화함과 동시에 한가지 파일을 가지고 다른 색상으로 바꿔서 사용할 수 있도록 해볼 것이다.

svgr

svgr 은 svg 를 React components 로 만들어주는 라이브러리이다.
간단하게 svgr installnext.config.js 설정을 통해 바로 컴포넌트로 사용할 수 있다. 현재 내 프로젝트는 위 방법으로 세팅되어 있다.

!! 하지만 svg 의 색상을 props 로 주입하여 변경할 수 있도록 하기 위해선
svg 코드 내부 fill stroke 를 포함한 색상에 대한 부분을 currentColor 로 변경시켜야 한다.

그러기 위해서 svgr/cli 를 사용할 수 있다.

svgr/cli

svgr/cli 를 통해 프로젝트의 svg 파일을 별도의 폴더에 자동적으로 ReactComponents 로 생성해준다. 또한 .svgrrc 을 통해 커스터마이징이 가능하다.

npx @svgr/cli --out-dir src/assets/icons --ext tsx --typescript public/assets/icons/line/*.svg

위는 .tsx 로 public/assets/icons/line/ 경로의 모든 .svg 파일을 src/assets/icons 폴더에 ReactComponents 로 내보내는 명령어이다.

정상적으로 reactComponents 가 생성되는 것을 확인했다.

하지만 size 와 color 를 props 로 받을 수 있게 커스터마이징을 해야한다.
프로젝트 루트에 template.js 를 구성하여 커스터마이징할 수 있다.
SVGR custom template 공식문서

// template.js
const template = (variables, { tpl }) => {
  return tpl`
    ${variables.imports};

    const ${variables.componentName} = (
      { size = 24, ...props }: SVGProps<SVGSVGElement> & { size?: number | string },
      ref: Ref<SVGSVGElement>
    ) => (
      ${variables.jsx}
    );

    ${variables.exports}
  `;
};

module.exports = template;

그리고 .svgrrc 파일에 svgProps 를 통해 width 와 height 에 대한 props 사용을 정의한다.

{
  ...
	"svgProps": {
	    "width": "{size}",
    	"height": "{size}"
	},
}

그리고 명령어에 template 옵션을 추가하여 실행해본다.
npx @svgr/cli --out-dir src/assets/icons --ext tsx --typescript --template template.js public/assets/icons/line/*.svg

import * as React from 'react';
import type { SVGProps } from 'react';
import { Ref, forwardRef } from 'react';
const SvgAddCircle = (
  {
    size = 24,
    ...props
  }: SVGProps<SVGSVGElement> & {
    size?: number | string,
  },
  ref: Ref<SVGSVGElement>,
) => (
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" width={size} height={size} ref={ref} {...props}>
    <path
      fill="currentColor"
      fillRule="evenodd"
      clipRule="evenodd"
	  ...
    />
  </svg>
);
const ForwardRef = forwardRef(SvgAddCircle);
export default ForwardRef;

위와 같은 파일이 생성되었다.

하지만 욕심이 있었다.

export { default as IconAddCircle } from './AddCircle';

해당형태로 Icon 이라는 접두사가 포함된 index.ts 파일을 만들어줬으면 했다.
이 역시 svgr 공식문서에 있다. (Custom index template 참고)
index-template 파일을 생성한다.
나는 svgr-index-template.js 란 이름으로 프로젝트 루트에 생성했다.

// svgr-index-template.js
const path = require('path');

function defaultIndexTemplate(filePaths) {
  const exportEntries = filePaths.map(({ path: filePath }) => {
    const basename = path.basename(filePath, path.extname(filePath));
    const exportName = /^\d/.test(basename) ? `Icon${basename}` : basename;
    return `export { default as Icon${exportName} } from './${basename}'`;
  });
  return exportEntries.join('\n');
}

module.exports = defaultIndexTemplate;

그리고 아래 명령어를 통해 index-template 과 커스텀한 컴포넌트인 template 을 모두 옵션에 넣어 실행한다.

npx @svgr/cli --out-dir src/assets/icons/line --index-template svgr-index-template.js --template svgr-template.js  public/assets/icons/line

나는 public/assets/icons/line 위치한 파일을 src/assets/icons/line 에 컴포넌트로 만들어 주라는 내용

나는 명령어 순서가 익숙치 않아서 한참을 헤맸다...

잘만들어졌다..........

import { IconHome } from '@/assets/icons/line';

const Team = () => {
  return (
    <HeaderLayout>
      <IconHome />
    </HeaderLayout>
  );
};

아이콘도 잘나온다..

color

import { IconHome } from '@/assets/icons/line';
import { theme } from '@/styles/theme';

const Team = () => {
  return (
    <HeaderLayout>
      <IconHome color={theme.sementicColors.text.brand} />
    </HeaderLayout>
  );
};

컬러도 추가해본다..

잘나온다..ㅜㅜ

size

<IconHome size={30} color={theme.sementicColors.text.brand} />

사이즈를 추가했지만

보다시피 감싸는 영역만 30x30 이 된 것을 볼 수 있다.

// .svgrrc
{
  	...
	"svgo": true,
}

svgr 이 svg를 컴포넌트로 만드는 과정에서 svgo(svg optimizer) 를 통해 최적화를 진행하는 데 최적화 과정에서 viewbox 를 지우는 것이 기본 설정으로 들어가 있다.
이에 대해서는 말이 예전부터 꾸준히 나오고 있고 viewbox 를 왜 지우도록 하냐는 의견이 많다. 하지만 개선이 되질 않고 있다고..
https://mingyu.kim/svgo-viewbox/
이분 글에 재밌게 나와있다.

결론적으로

// .svgrrc
{
  	...
	"svgo": false,
}

svgo 를 포기하고 npm run icon 하면

import * as React from 'react';
import type { SVGProps } from 'react';
import { Ref, forwardRef } from 'react';
const MenuLineHorizontal = (
  {
    size = 24,
    ...props
  }: SVGProps<SVGSVGElement> & {
    size?: number | string,
  },
  ref: Ref<SVGSVGElement>,
) => (
  <svg
    viewBox="0 0 24 24"  <<<<<<<<<<<<<<<<<< !!
	...
  >
  ...

viewBox 가 돌아온 것을 확인할 수 있다.

이로인해 size 도 정상적으로 동작하는 것을 확인할 수 있다.

추가적으로 export 시 default template 에 컴포넌트명에 prefix로 Svg 가 붙는 게 싫었다.

slice 로 Svg 를 잘라주었고. export default 문을 직접 해줌.

const template = (variables, { tpl }) => {
  const IconComponentName = variables.componentName.slice(3);
  return tpl`
    ${variables.imports};

    const ${IconComponentName} = (
      { size = 24, ...props }: SVGProps<SVGSVGElement> & { size?: number | string },
      ref: Ref<SVGSVGElement>
    ) => (
      ${variables.jsx}
    );
    
    const ForwardRef = forwardRef(${IconComponentName});
    export default ForwardRef;
  `;
};

module.exports = template;

이제 위 작업을 script 에 추가하여 새로운 아이콘이 추가되었을 때 효율적으로 컴포넌트화 해보자!

// package.json

"scripts": {
	...
    "icon": "npx @svgr/cli --out-dir src/assets/icons/line --index-template svgr-index-template.js --template svgr-template.js  public/assets/icons/line"
},

package.json 에 해당 script 를 추가해준다.

그리고

npm run icon 

폴더별로 다른 config 세팅하기

현재 프로젝트에서 line icon 의 경우 단색 아이콘이기 때문에 currentColor 로 일괄 변환을 적용하여 color 를 변경할 수 있게 설정했으나 다른 폴더의 icon 의 경우 멀티컬러이기 때문에

마치며

예전에 프로젝트할 때에도 피그마의 디자인시스템 아이콘이 다른 색상으로 사용될때 개별적으로 currentColor 로 변경시키거나 IconWhite.svg 이런식으로 새로운 파일로 export 했는데.. 오늘에서야 그 불편함을 해소한 듯해서 기분이 너무 좋다...........

profile
꾸준함과 전문성
post-custom-banner

0개의 댓글