개발자는 JSX에 SVG를 직접 삽입하는 경우가 많습니다. 이는 사용하기 편리하지만 JS 번들 크기가 늘어납니다. 최적화를 추구하면서 나는 번들을 복잡하게 만들지 않고 SVG 아이콘을 사용할 수 있는 다른 방법을 찾기로 결정했습니다. 우리는 SVG 스프라이트가 무엇인지, 어떻게 사용하는지, 작업에 사용할 수 있는 도구에 대해 이야기할 것입니다.
이론부터 시작하여 단계별로 SVG 스프라이트를 생성하는 스크립트를 작성하고 vite 및 webpack 용 플러그인에 대해 논의하여 결론을 내릴 것입니다.
이미지 스프라이트는 단일 이미지에 배치된 이미지 모음입니다. SVG Sprite는 <svg />
에 배치되는 <symbol />
로 래핑된 SVG 콘텐츠 모음입니다.
예를 들어 간단한 SVG 펜 아이콘이 있습니다.
SVG 스프라이트를 얻으려면 <svg />
태그를 <symbol />
로 바꾸고 외부적으로 <svg />
로 래핑합니다.
이제 SVG 스프라이트이며 내부에 id="icon-pen"
아이콘이 있습니다.
좋습니다. 하지만 이 아이콘을 HTML 페이지에 배치하는 방법을 알아내야 합니다. href
속성과 함께 <use />
태그를 사용하여 아이콘의 ID를 지정하고 SVG 내부에 이 요소를 복제합니다.
<use />
어떻게 작동하는지 예를 살펴보겠습니다.
이 예에는 두 개의 원이 있습니다. 첫 번째는 파란색 윤곽선을 가지고 있고, 두 번째는 첫 번째 것과 중복되지만 빨간색으로 채워져 있습니다.
SVG 스프라이트로 돌아가 보겠습니다. <use />
를 사용하면 다음과 같은 결과를 얻을 수 있습니다.
여기에는 펜 아이콘이 있는 버튼이 있습니다.
지금까지 우리는 <button />
에 코드 없이 아이콘을 사용했습니다. 페이지에 버튼이 두 개 이상 있는 경우 모든 아이콘은 SVG 스프라이트에서 가져오고 재사용이 가능하므로 HTML 레이아웃의 크기에 두 번 이상 영향을 미치지 않습니다.
index.html
파일을 복잡하게 만들 필요가 없도록 SVG 스프라이트를 별도의 파일로 이동해 보겠습니다. 먼저 sprite.svg
파일을 만들고 그 안에 SVG 스프라이트를 넣습니다. 다음 단계는 <use/>
의 href
속성을 사용하여 아이콘에 대한 액세스를 제공하는 것입니다.
아이콘 사용에 많은 시간을 절약하기 위해 이 프로세스에 대한 자동화를 설정해 보겠습니다. 아이콘에 쉽게 접근하고 원하는 대로 관리하려면 각 아이콘을 자체 파일로 분리해야 합니다.
먼저 모든 아이콘을 동일한 폴더에 넣어야 합니다. 예를 들면 다음과 같습니다.
이제 이러한 파일을 가져와 단일 SVG 스프라이트로 결합하는 스크립트를 작성해 보겠습니다.
프로젝트의 루트 디렉터리에 generateSvgSprite.ts
파일을 만듭니다.
글로브 라이브러리를 설치합니다:
npm i -D glob
globSync
사용하여 각 아이콘의 전체 경로 배열을 가져옵니다.
이제 각 파일 경로를 반복하고 Node의 내장 라이브러리 fs를 사용하여 파일 콘텐츠를 가져옵니다.
훌륭합니다. 각 아이콘의 SVG 코드가 있으므로 이제 이를 결합할 수 있습니다. 하지만 각 아이콘 내부의 svg
태그를 symbol
태그로 바꾸고 쓸모 없는 SVG 속성을 제거해야 합니다.
DOM 표현을 얻으려면 일부 HTML 파서 라이브러리를 사용하여 SVG 코드를 구문 분석해야 합니다. node-html-parser를 사용하겠습니다.
우리는 SVG 코드를 분석하여 마치 실제 HTML 요소인 것처럼 SVG 요소를 얻었습니다.
동일한 파서를 사용하여 빈 symbol
요소를 생성하여 svgElement
의 하위 항목을 symbol
로 이동합니다.
svgElement
에서 하위 항목을 추출한 후에는 svgElement에서 id
및 viewBox
속성도 가져와야 합니다. id
로 아이콘 파일의 이름을 설정해 보겠습니다.
이제 SVG 스프라이트에 배치할 수 있는 symbol
요소가 생겼습니다. 따라서 파일을 반복하기 전에 symbols
변수를 정의하고, symbolElement
문자열로 변환하고, symbols
로 푸시하면 됩니다.
마지막 단계는 SVG 스프라이트 자체를 만드는 것입니다. "루트"에 svg
있고 하위 항목으로 기호가 있는 문자열을 나타냅니다.
const svgSprite = `<svg>${symbols.join('')}</svg>`;
그리고 아래에서 설명할 플러그인 사용을 고려하고 있지 않다면 생성된 스프라이트가 포함된 파일을 정적 폴더에 넣어야 합니다. 대부분의 번들러는 public
폴더를 사용합니다.
fs.writeFileSync('public/sprite.svg', svgSprite);
그리고 이것이다. 스크립트를 사용할 준비가 되었습니다.
// generateSvgSprite.ts import { globSync } from 'glob'; import fs from 'fs'; import { HTMLElement, parse } from 'node-html-parser'; import path from 'path'; const svgFiles = globSync('src/icons/*.svg'); const symbols: string[] = []; svgFiles.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const svgElement = parse(code).querySelector('svg') as HTMLElement; const symbolElement = parse('<symbol/>').querySelector('symbol') as HTMLElement; const fileName = path.basename(file, '.svg'); svgElement.childNodes.forEach(child => symbolElement.appendChild(child)); symbolElement.setAttribute('id', fileName); if (svgElement.attributes.viewBox) { symbolElement.setAttribute('viewBox', svgElement.attributes.viewBox); } symbols.push(symbolElement.toString()); }); const svgSprite = `<svg>${symbols.join('')}</svg>`; fs.writeFileSync('public/sprite.svg', svgSprite);
이 스크립트를 프로젝트 루트에 넣고 tsx 로 실행할 수 있습니다.
npx tsx generateSvgSprite.ts
사실 저는 어디에서나 TypeScript로 코드를 작성했기 때문에 여기서는 tsx를 사용하고 있습니다. 이 라이브러리를 사용하면 TypeScript로 작성된 노드 스크립트를 실행할 수 있습니다. 순수 JavaScript를 사용하려면 다음을 사용하여 실행할 수 있습니다.
node generateSvgSprite.js
이제 스크립트가 수행하는 작업을 요약해 보겠습니다.
.svg
파일에 대한 src/icons
폴더를 조사합니다.
<svg />.
public
폴더에 sprite.svg
파일을 생성합니다.자주 발생하는 중요한 사례 중 하나인 색상을 다루겠습니다! 아이콘이 스프라이트로 들어가는 스크립트를 만들었지만 이 아이콘은 프로젝트 전체에서 다른 색상을 가질 수 있습니다.
<svg/>
요소는 채우기 또는 획 속성뿐만 아니라 path
, circle
, line
등의 속성도 가질 수 있다는 점을 명심해야 합니다. 우리에게 도움이 될 매우 유용한 CSS 기능인 currentcolor 가 있습니다.
이 키워드는 요소의 색상 속성 값을 나타냅니다. 예를 들어, background: currentcolor
인 요소에 color: red
사용하면 이 요소는 빨간색 배경을 갖게 됩니다.
기본적으로 모든 획이나 채우기 속성 값을 currentcolor
으로 변경해야 합니다. 수동으로 수행되는 것을 보지 않기를 바랍니다. 그리고 SVG 문자열을 대체하거나 구문 분석하는 일부 코드를 작성하는 것조차 매우 유용한 도구인 svgo 에 비해 그다지 효율적이지 않습니다.
이는 색상뿐만 아니라 SVG에서 중복된 정보를 제거하는 데도 도움이 되는 SVG 최적화 프로그램입니다.
svgo를 설치해 봅시다:
npm i -D svgo
svgo
에는 플러그인이 내장되어 있으며 그 중 하나는 currentColor: true
속성을 갖는 convertColors
입니다. 이 SVG 출력을 사용하면 색상이 currentcolor
으로 대체됩니다. convertColors
와 함께 svgo
를 사용하는 방법은 다음과 같습니다.
import { optimize } from 'svgo'; const output = optimize( '<svg viewBox="0 0 24 24"><path fill="#000" d="m15 5 4 4" /></svg>', { plugins: [ { name: 'convertColors', params: { currentColor: true, }, } ], } ) console.log(output);
그리고 출력은 다음과 같습니다:
<svg viewBox="0 0 24 24"><path fill="currentColor" d="m15 5 4 4"/></svg>
이전 부분에서 작성한 마법의 스크립트에 svgo
추가해 보겠습니다.
// generateSvgSprite.ts import { globSync } from 'glob'; import fs from 'fs'; import { HTMLElement, parse } from 'node-html-parser'; import path from 'path'; import { Config as SVGOConfig, optimize } from 'svgo'; // import `optimize` function const svgoConfig: SVGOConfig = { plugins: [ { name: 'convertColors', params: { currentColor: true, }, } ], }; const svgFiles = globSync('src/icons/*.svg'); const symbols: string[] = []; svgFiles.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const result = optimize(code, svgoConfig).data; // here goes `svgo` magic with optimization const svgElement = parse(result).querySelector('svg') as HTMLElement; const symbolElement = parse('<symbol/>').querySelector('symbol') as HTMLElement; const fileName = path.basename(file, '.svg'); svgElement.childNodes.forEach(child => symbolElement.appendChild(child)); symbolElement.setAttribute('id', fileName); if (svgElement.attributes.viewBox) { symbolElement.setAttribute('viewBox', svgElement.attributes.viewBox); } symbols.push(symbolElement.toString()); }); const svgSprite = `<svg xmlns="http://www.w3.org/2000/svg">${symbols.join('')}</svg>`; fs.writeFileSync('public/sprite.svg', svgSprite);
그리고 스크립트를 실행하세요:
npx tsx generateSvgSprite.ts
결과적으로 SVG 스프라이트에는 currentColor
아이콘이 포함됩니다. 그리고 이러한 아이콘은 원하는 색상으로 프로젝트의 모든 곳에서 사용할 수 있습니다.
스크립트가 있어서 원할 때마다 실행할 수 있지만 수동으로 사용해야 한다는 점이 약간 불편합니다. 따라서 이동 중에도 .svg
파일을 보고 SVG 스프라이트를 생성할 수 있는 몇 가지 플러그인을 추천합니다.
vite-plugin-svg-spritemap ( vite 사용자용)
이것은 기본적으로 이 기사에서 방금 만든 스크립트를 포함하는 플러그인입니다. 플러그인에는 기본적으로 currentColor
교체가 활성화되어 있으므로 플러그인을 매우 쉽게 설정할 수 있습니다.
// vite.config.ts import svgSpritemap from 'vite-plugin-svg-spritemap'; export default defineConfig({ plugins: [ svgSpritemap({ pattern: 'src/icons/*.svg', filename: 'sprite.svg', }), ], });
svg-spritemap-webpack-plugin ( 웹팩 사용자용)
저는 Vite로 전환하기 전까지 이 Webpack 플러그인을 사용했습니다. 하지만 Webpack을 사용하는 경우 이 플러그인은 여전히 좋은 솔루션입니다. 색상 변환을 수동으로 활성화해야 하며 다음과 같이 표시됩니다.
// webpack.config.js const SVGSpritemapPlugin = require('svg-spritemap-webpack-plugin'); module.exports = { plugins: [ new SVGSpritemapPlugin('src/icons/*.svg', { output: { svgo: { plugins: [ { name: 'convertColors', params: { currentColor: true, }, }, ], }, filename: 'sprite.svg', }, }), ], }
React 로 예제를 제공하겠지만 대부분 HTML에 관한 내용이기 때문에 원하는 곳에 구현하시면 됩니다. 따라서 빌드 폴더에 sprite.svg
있으므로 스프라이트 파일에 액세스하여 기본 Icon
구성 요소를 만들 수 있습니다.
const Icon: FC<{ name: string }> = ({ name }) => ( <svg> <use href={`/sprite.svg#${name}`} /> </svg> ); const App = () => { return <Icon name="pen" />; };
따라서 모든 것을 요약하면 아이콘으로 인한 많은 수동 작업을 방지하기 위해 다음을 수행합니다.
개발 효율성은 단지 시간 절약에만 국한되지 않습니다. 그것은 우리의 창의적 잠재력을 발휘하는 것입니다. 아이콘 관리와 같은 핵심 작업을 자동화하는 것은 단순한 지름길이 아닙니다. 이는 보다 원활하고 영향력 있는 코딩 경험을 위한 관문입니다. 그리고 이러한 일상적인 작업에 시간을 절약하면 더 복잡한 작업에 집중하고 개발자로서 더 빠르게 성장할 수 있습니다.