“ 당신은 소프트웨어 품질을 추구할 수도 있고, 포인터 연산을 할 수도 있다. 그러나 두 개를 동시에 할 수는 없다. ”
nuxt에서 svg파일을 사용할 때 아래 라이브러리를 활용하여 래핑 해서 사용을 했습니다.
예를 들어서 import CloseIcon from '~/asset/images/icon/icon-close.svg?inline' 이런 식으로 컴포넌트에서 사용했습니다.
@nuxtjs/svg: "^0.4.1",
위에 라이브러리를 사용하여 svg파일을 컴포넌트로 사용하는데는 문제가 없었는데
vuetify2에 v-text-field 컴포넌트에서 close 아이콘을 변경하려면 내부적으로 스타일 클래스를 찾아서 css파일로 변경하여 사용해야 하고 이게 생각해 보니 재사용성도 부족하고, 하드코딩으로 되는 거 같아서 svg파일을 vuetify2 icon에 mdi 아이콘을 사용하는 것처럼 등록하여 사용하는 게 더 나을 거 같다는 생각을 했습니다. 그래서 공식문서에서 확인해 봤는데 아래처럼 svg파일을 등록하면 된다고 하여 진행해 봤습니다.
// src/plugins/vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import myIconSvg from 'myIcon.svg'
Vue.use(Vuetify)
export default new Vuetify({
icons: {
iconfont: 'fa',
values: {
customIconSvg: myIconSvg,
customIconSvgPath: 'M14.989,9.491L6.071,0.537C5.78,0.246,5.308,0.244,5.017,0.535c-0.294,0.29-0.294,0.763-0.003,1.054l8.394,8.428L5.014,18.41c-0.291,0.291-0.291,0.763,0,1.054c0.146,0.146,0.335,0.218,0.527,0.218c0.19,0,0.382-0.073,0.527-0.218l8.918-8.919C15.277,10.254,15.277,9.784,14.989,9.491z',
},
},
})
svg파일 커스텀 등록 문제?
공식문서에 나온 것처럼 등록해 보니 아이콘이 제대로 나오지 않는 현상이 있었습니다. 그래서 다른 방법으로 svg파일을 연결하는 것이 아니라 svg 컴포넌트를 넣는 방법 또한 있어서 이 방법으로도 진행해 봤습니다.
// src/plugins/vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import myIconSvg from '~/asset/images/icon/icon-close.svg?inline' //@nuxtjs/svg 활용
Vue.use(Vuetify)
export default new Vuetify({
icons: {
iconfont: 'fa',
values: {
close:{
component: myIconSvg,
props:{
name:'close'
}
},
},
})
Maximum call stack size exceeded 에러
위에 방법대로 @nuxtjs/svg로 svg파일을 컴포넌트로 래핑 하여 등록해 봤는데 아래처럼 $close로 사용하니 처음에는 icon이 잘 나왔습니다. 그런데 새로고침 3번 정도 하니 Maximum call stack size exceeded 에러로 vuetify icon 쪽 mergeDeep 함수에서 문제가 발생했습니다.
<v-icon>$close</v-icon>
그래서 vuetify icon 기본으로 등록된 거와 충돌이 나는 건가? 무슨 문제인지 공식문서를 또 확인해 봤습니다.
아래가 기본적으로 등록된 icon key값인데 같은 값을 키로 지정해서 그런가? 그런데 그러면 변경되야지 안될 이유가 없지 않을까 생각을 했습니다.
vuetify 컴포넌트 props 보면 아래 키값이 default로 설정된 것을 확인할 수 있습니다.
const MY_ICONS = {
complete: '...',
cancel: '...',
close: '...',
delete: '...', // delete (e.g. v-chip close)
clear: '...',
success: '...',
info: '...',
warning: '...',
error: '...',
prev: '...',
next: '...',
checkboxOn: '...',
checkboxOff: '...',
checkboxIndeterminate: '...',
delimiter: '...', // for carousel
sort: '...',
expand: '...',
menu: '...',
subgroup: '...',
dropdown: '...',
radioOn: '...',
radioOff: '...',
edit: '...',
ratingEmpty: '...',
ratingFull: '...',
ratingHalf: '...',
loading: '...',
first: '...',
last: '...',
unfold: '...',
file: '...',
}
Vuetify custom icon 등록 해결
그래서 하나씩 고민해 보고 찾아본 결과 문제는 @nuxtjs/svg로 svg파일을 래핑 한 것을 등록한 게 원인이었습니다.
그래서 생각해 봤는데 @nuxtjs/svg 라이브러리 자체가 레거시이기도 하고 보안이슈도 있었는데 귀찮아서 사용했지만 이번 기회에 svg파일을 컴포넌트로 만들어서 사용하자 라는 생각으로 svg파일을 직접 컴포넌트로 만들었습니다.
icon-close.svg 내용을 아래처럼 컴포넌트로 만들고 이제 vuetify에 등록하면 됩니다.
/component/svg/CloseIcon.vue
<template>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M16 16L8 8" stroke="white" stroke-width="1.2" />
<path d="M8 16L16 8" stroke="white" stroke-width="1.2" />
</svg>
</template>
<script setup lang="ts">
</script>
vuetify에 등록할 svg파일이 많은 경우를 생각해서 foreach로 키값을 등록해서 동적으로 컴포넌트 경로에서 가져와서 등록하게 구현해 봤습니다. svg 컴포넌트 이름 뒤에 Icon을 붙인 이유는 혹여나 중복된 이름이 있을 경우가 있을 수 있어서 제 나름대로 규칙정해서 넣어본 겁니다.
아래 코드를 잠깐 설명하자면
- IconComponentNames 배열에 컴포넌트이름 지정
- forEach로 배열을 돌면서 custom icon key value 구성
- 컴포넌트 이름 뒤에 Icon 스트링 제거
- 첫 번째 대문자 말고 두 번째부터 대문자사이에 하이픈(-) 추가
- 소문자로 변경
v-icon을 사용할 때 mdi-close 이렇게 사용하는 것처럼 키값이 구성되게 설정해 봤습니다.
/utils/vutifyIcon.ts
import { Component } from 'vue'
type Iconfont = 'mdi' | 'mdiSvg' | 'md' | 'fa' | 'faSvg' | 'fa4'
interface IconType {
iconfont: Iconfont
values: {
[key: string]: {
component: Component
}
}
}
const Icons: IconType = {
iconfont: 'mdi',
values: {}
}
const iconComponentNames = [
'CloseIcon',
'AddIcon'
]
iconComponentNames.forEach((componentName) => {
const iconKey = componentName
.replace('Icon', '') // "Icon" 제거
.replace(/([A-Z])/g, (match, p1, offset) => {
//대문자인 경우 앞에 하이픈 추가
return offset > 0 ? `-${match.toLowerCase()}` : match.toLowerCase()
})
// eslint-disable-next-line import/no-dynamic-require
const component = require(`~/components/svg/${componentName}.vue`)
.default as Component
Icons.values[iconKey] = { component }
})
export { Icons }
위에 내용이 완료되었으면 plugins/vuetify 파일로 가서 등록하면 됩니다.
import Vue from 'vue'
import Vuetify from 'vuetify'
import { Icons } from '~/utils/vuetifyIcon.ts'
Vue.use(Vuetify)
export default new Vuetify({
icons: Icons
})
테스트로 star라는 별 svg 아이콘을 등록해서 v-text-field clear-icon을 변경해 확인해 봅니다. 아래처럼 변경되는 것을 확인할 수 있습니다.
<v-text-field
clearable
clear-icon="$star"
background-color="#4376fb"
>
</v-text-field>