오늘은 typescript + react + webpack을 활용하고 있는 저희 팀에서 SVG를 컴포넌트로 어떻게 분리하였는지와 컴포넌트화를 진행하는 과정에서 발생한 트러블 슈팅을 공유하려고 합니다.
📕 목차
- SVG를 컴포넌트화 시키게 된 이유
- SVG 컴포넌트 분리 및 적용
- webpack 충돌 해결하기
기존에 로그인뿐만 아니라 많은 컴포넌트에서 SVG를 활용하고 있었습니다. 그런데 로그인 컴포넌트를 타입스크립트로 마이그레이션을 진행하는 도중 SVG가 컴포넌트의 가독성을 저하시키고 있다는 생각이 들었습니다.
<form className='login-form' method='post' onSubmit={onLogin}>
// ...
<div className='id-save-container'>
<button
type='button'
className={`id-save-btn ${isSave && 'id-save-btn__active'}`}
onClick={isSaveToggle}
>
{isSave && (
<svg
width='10'
height='10'
viewBox='0 0 13 13'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M0.773438 5.52287L5.00052 11.2129L12.154 1.13281'
stroke='white'
stroke-width='1.5'
/>
</svg>
)}
</button>
<p className='id-save-message'>{FORM_PLACEHOLDERS[userType].save}</p>
</div>
<p className='login-error-message'>{errors?.errorMessage}</p>
<button
type='submit'
className='login-submit-btn'
disabled={isSubmitting}
>
{isSubmitting ? <LoadingSpinner /> : '로그인'}
</button>
</form>
이런 svg 코드가 한 컴포넌트 내부에 1개만 들었갔음에도 이렇게 JSX 코드가 지저분해지는데, 2개 혹은 그 이상이 들어가게 된다면 추후 유지 보수 단계에서 컴포넌트 관리가 정말 어려워질 것이라고 예상을 해 이를 컴포넌트화시켜 분리하는 방법에 대해 고려하게 되었습니다.
SVG를 컴포넌트로 분리하는 과정은 어렵지 않았습니다. 우선 SVG를 assets에 따로 분리하였습니다.
<svg
width='10'
height='10'
viewBox='0 0 13 13'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M0.773438 5.52287L5.00052 11.2129L12.154 1.13281'
stroke='white'
stroke-width='1.5'
/>
</svg>
이후 해당 프로젝트에서는 webpack을 이용해 dev Server를 가동시키고 있어 webpack의 module
에 @svgr/webpack
라이브러리를 추가해 주었습니다.
// 로더
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: 'asset/resource',
},
{
test: /\.svg$/,
use: ['@svgr/webpack'],
},
],
}
위와 같이 한 후 SVG를 컴포넌트로 불러오니 이전보다 컴포넌트의 가독성이 향상된 것을 볼 수 있었습니다.
<form className='login-form' method='post' onSubmit={onLogin}>
// ...
<div className='id-save-container'>
<button
type='button'
className={`id-save-btn ${isSave && 'id-save-btn__active'}`}
onClick={isSaveToggle}
>
{isSave && <SaveCheck />}
</button>
<p className='id-save-message'>{FORM_PLACEHOLDERS[userType].save}</p>
</div>
<p className='login-error-message'>{errors?.errorMessage}</p>
<button
type='submit'
className='login-submit-btn'
disabled={isSubmitting}
>
{isSubmitting ? <LoadingSpinner /> : '로그인'}
</button>
</form>
하지만 위와 같이 설정한 후, 프로젝트를 확인해 보니 SVG가 로딩이 되지 않았고 에러가 발생하고 있었습니다.
에러 문구에서 SVG 컴포넌트가 로딩이 되고 있지 않음을 확인할 수 있었고, ChatGPT를 통해 이를 확인해 본 결과 웹팩 모듈 내부를 확인해보니 SVG에 대한 규칙 충돌이 발생하고 있는 것을 확인할 수 있었습니다.
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: 'asset/resource',
},
{
test: /\.svg$/,
use: ['@svgr/webpack'],
},
// 기타 규칙들...
]
}
규칙 간의 충돌 해결: 두 규칙 중 하나만 .svg 파일에 적용되도록 설정을 조정합니다. 예를 들어, .svg 파일을 리액트 컴포넌트로 사용하려면 asset/resource 규칙에서 .svg 확장자를 제외하고, @svgr/webpack 규칙을 유지합니다.
규칙의 순서 변경: 웹팩은 규칙을 배열의 순서대로 처리합니다. @svgr/webpack 규칙을 asset/resource 규칙보다 먼저 배치하여, .svg 파일이 먼저 @svgr/webpack에 의해 처리되도록 할 수 있습니다.
SVG를 assets/resource
규칙에서 제외하고, svgr/webpack
에만 규칙이 적용되도록 수정한 후에 정상적으로 적용이 되는 것을 확인할 수 있었습니다.