프로젝트 초기 구성 당시 public/assets 폴더 내부의 svg 파일을 경로로 가져와서 사용했다.
ex) <img src='public/assets/….svg'/>
하지만 이 경우 매번 경로를 입력해주어야하는 불편함이 있다.
또한 스타일 가이드의 아이콘이 색상이 바뀌어 사용될 때 다른 색상의 아이콘을 피그마에서 매번 export 해야한다.
장기적일 경우 프로젝트가 무거워질 것이다.
그래서 svgr 라이브러리를 통해 svg 파일을 React components 로 변환하여 프론트엔드 친화적으로 구성할 예정이다.
그리고 svgo 라이브러리를 활용하여 svg 파일을 최적화함과 동시에 한가지 파일을 가지고 다른 색상으로 바꿔서 사용할 수 있도록 해볼 것이다.
svgr 은 svg 를 React components 로 만들어주는 라이브러리이다.
간단하게 svgr install
후 next.config.js
설정을 통해 바로 컴포넌트로 사용할 수 있다. 현재 내 프로젝트는 위 방법으로 세팅되어 있다.
!! 하지만 svg 의 색상을 props 로 주입하여 변경할 수 있도록 하기 위해선
svg 코드 내부 fill stroke 를 포함한 색상에 대한 부분을 currentColor 로 변경시켜야 한다.
그러기 위해서 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>
);
};
아이콘도 잘나온다..
import { IconHome } from '@/assets/icons/line';
import { theme } from '@/styles/theme';
const Team = () => {
return (
<HeaderLayout>
<IconHome color={theme.sementicColors.text.brand} />
</HeaderLayout>
);
};
컬러도 추가해본다..
잘나온다..ㅜㅜ
<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
현재 프로젝트에서 line icon 의 경우 단색 아이콘이기 때문에 currentColor 로 일괄 변환을 적용하여 color 를 변경할 수 있게 설정했으나 다른 폴더의 icon 의 경우 멀티컬러이기 때문에
예전에 프로젝트할 때에도 피그마의 디자인시스템 아이콘이 다른 색상으로 사용될때 개별적으로 currentColor 로 변경시키거나 IconWhite.svg 이런식으로 새로운 파일로 export 했는데.. 오늘에서야 그 불편함을 해소한 듯해서 기분이 너무 좋다...........