VueJS: Impor SVG sebagai Komponen menggunakan SVG-To-Vue-Component

SVG as Vue Components with all native DOM events attached.

SVG vs PNG | JPE?G

Salah satu peran ikon dalam laman web adalah memberikan visual cue, sehingga user dapat menyerap informasi dengan cepat dan tepat, tanpa harus dibombardir dengan the wordiness of texts. Ada kalanya ikon juga bersifat interaktif, dimana ikon memiliki initial, hover, focus, dan/atau active state. Setiap state umumnya ditandai dengan variasi pada ukuran dan/atau warna ikon yang berbeda.

Masalahnya, ikon dengan ekstensi PNG atau JPE?G kurang bersahabat dengan perubahan ukuran atau warna, karena masing-masingnya adalah raster image dimana informasi ukuran dan warna sudah ter-encode di dalamnya. Mengubah ukuran raster image melalui CSS transform: scale akan mengurangi ketajaman gambar. Adapun perubahan warna hanya bisa dilakukan dengan menyuplai ikon berikut warna yang berbeda-beda atau melalui CSS filter β€” yang mana CSS Property tersebut belum di-support oleh semua browser (lihat Gambar 1).

Gambar 1

Permasalahan di atas dapat terjawab dengan menggunakan ikon bertipe SVG (Scalable Vector Graphics), yang mana browser support untuk SVG jauh lebih baik ketimbang CSS filter (lihat Gambar 2).

Gambar 2

Dengan menggunakan SVG, kita dapat meminimasi jumlah ikon yang perlu disuplai yang sekaligus akan mengurangi payload yang harus diunduh oleh user. Contohnya kurang lebih seperti ini:

Di bawah asumsi bahwa kita tidak menggunakan CSS filter, maka untuk menyuplai filled-icon dan stroked-icon dengan dua variasi warna masing-masingnya, diperlukan 4 buah file PNG/JPE?G dengan total kapasitas 13 KB. Di sisi lain, jumlah variasi yang sama dapat diraih cukup oleh 1 file SVG berukuran 1.3 KB dengan memanfaatkan CSS Property stroke dan fill. Artinya, kita dapat memangkas jumlah payload hingga 90%! 😱

Lantas, bagaimana cara menggunakan SVG di dalam komponen Vue?. Jawabannya ada pada NPM Package svg-to-vue-component.

SVG-to-Vue-Component

npm i svg-to-vue-component -D

Package svg-to-vue-component akan mentransformasi setiap file SVG menjadi Vue Component saat project kita mengalami proses building oleh Webpack. Webpack? Ya, betul. Transformasi tersebut hanya mungkin dilakukan jika kita berada dalam environment yang menginkorporasi Webpack sebagai module bundler.

Webpack Module Loader

Karena package tersebut berperan sebagai module loader, maka kita perlu mengatur konfigurasi webpack.config.js terlebih dahulu.

Sehingga, kita dapat menggunakan SVG dengan cara seperti ini:

Kok SVG yang sudah ada sebelumnya jadi gagal load?

Hal yang sangat disayangkan dari package tersebut adalah semua file SVG yang ada dalam project folder kita akan diubah menjadi file berekstensi JS (*.js). Sehingga, bila kita sebelumnya sudah sempat menggunakan SVG baik melalui src attribute elemen img :

<img src="path/to/your/svg/file.svg">

… atau melalui module import:

<template>
<img src="mySvg">
</template>
<script>
import svgIcon from 'path/to/your/svg/file.svg'
export default {
data(){
return {
mySvg: svgIcon
}
}
}
</script>

… atau terdapat package pada node_modules yang mengimpor SVG dalam stylesheet:

<style>.icon-library .some-icon {
background-image: url('path/to/some-icon.svg')
}
</style>

… maka elemen SVG tersebut tidak akan berhasil di-render oleh browser karena file SVG sudah di-compile oleh Webpack menjadi file JS. Hal ini akibatkan oleh module loader rules yang didefinisikan dalam webpack.config.js, dimana setiap file dengan ekstensi *.svg hanya dan hanya akan di-load oleh svg-to-vue-component/loader untuk kemudian diproses oleh vue-loader.

Solusi dari permasalahan tersebut adalah jangan menggunakan panduan instalasi dari author package svg-to-vue-component. 😱

Webpack Resolve Loader

Untuk mencegah masalah di atas, biarkan file SVG di-load secara normal oleh Webpack. Umumnya, file gambar seperti PNG | JPE?G | GIF | WEBP | SVG di-load menggunakan url-loader atau file-loader. Artinya, jangan mengubah module rules yang ada dalam webpack.config.js, biarkan saja sebagaimana awalnya.

Untuk tetap me-resolve file SVG sebagai Vue Component, yang perlu kita lakukan adalah meng-override loader yang akan digunakan secara inline pada import command. Guna mencapai hal tersebut, definisikan terlebih dahulu loader alias pada webpack.config.js.

Lalu, nyatakan bahwa kita ingin menggunakan kedua loader tersebut saat mengimpor file SVG yang dimaksud.

Adanya prefix !vue!svg-to-vue! sebelum path menandakan bahwa kita ingin agar file SVG tersebut di-load oleh svg-to-vue-component terlebih dahulu untuk kemudian diproses oleh vue-loader.

NOTE: Tanda seru β€œ!” sebelum vue!svg-to-vue! sangat penting. Mengapa? Karena jika tidak dituliskan, maka loader rules terkait SVG yang ada dalam webpack.config.js akan tetap diaplikasikan.

Dengan β€œ!”
Tanpa β€œ!”

Sayangnya, dokumentasi pada laman resmi Webpack tidak mencantumkan terkait pentingnya prefix β€œ!” tersebut, sehingga saya sempat terjebak dalam lingkaran setan untuk beberapa lama terkait SVG ini. 😑 Penjelasan terkait prefix tersebut hanya dapat ditemukan pada GithubWiki milik Webpack.

Optimasi Loading dengan Async Dynamic Component

Langkah selanjutnya adalah memastikan proses loading SVG dapat dilakukan dengan mudah, dalam artian kita tidak perlu mengimpor setiap file SVG yang akan digunakan secara manual. Syntax yang kita inginkan adalah seperti ini:

<template>
<div>
<MySvgIcon name="call-answer"/>
<MySvgIcon name="close-circle/>
</div>
</template>

Guna mencapai hal tersebut, lakukan langkah berikut ini.

Kumpulkan file SVG dalam satu folder, contohnya /assets/svg seperti ini:

Lalu, definisikan Vue Component yang bertugas untuk mengimpor file SVG dari assets folder tersebut untuk kemudian disuplai sebagai argument dari v-bind:is, seperti ini:

Proses impor dilakukan oleh fungsi importSvgFile, dimana fungsi akan dipanggil setiap kali name props disuplai ke dalam MySvgIcon. Mengapa repot-repot menggunakan methods dan watch? Tidakkah hal itu serupa dengan computed property?. Jawabannya adalah pada saat ini, yakni Vue 2.6.2+, computed property belum men-support fungsi asynchronous. Di sisi lain, async import syntax akan mengembalikan Promise.

import('path/to/your/module')
.then((module) => {
// ...
}).catch((err) => {
// ...
})

Jika dipaksakan untuk dimuat dalam computed property, maka value dari iconComponent akan berisi Promise yang belum ter-resolve sebagai module.

Penggunaan async import syntax juga memiliki kelebihan lain, yaitu adanya automatic code splitting oleh Webpack. Modul JS hasil compile dari ikon SVG akan berada terpisah di luar vendor.js dan hanya akan diunduh serta diproses oleh browser tepat saat digunakan. Hal tersebut tentunya sejalan dengan keinginan kita di awal, yaitu untuk mengoptimasi payloads yang harus diunduh oleh user saat memasuki laman web milik kita.

Komponen di atas masih dapat dikembangkan lebih jauh, karena pada saat ini tugasnya hanya mengimpor file SVG sebagai komponen tanpa disertai modifikasi lebih jauh, misalnya mengatur ukuran, warna, atau fill-rule dari ikon tersebut. Modifikasi tersebut dapat dilakukan dengan mendefinisikan sejumlah props yang kemudian diproses untuk disuplai sebagai argumen dari v-bind:class atau v-bind:style.

Tentunya tidak selamanya SVG akan menjadi pilihan yang lebih baik ketimbang PNG | JPE?G untuk urusan gambar atau ikon. Grafik dengan konten warna yang kompleks akan sulit diterjemahkan ke dalam SVG, sehingga ekstensi gambar PNG | JPE?G lebih tepat dipergunakan. Adapun untuk urusan ikon, yang pada umumnya tidak memiliki konten warna yang rumit, SVG tentu menjadi pilihan yang utama.

See you on next article. Semoga bermanfaat dan semangat coding! 😎

--

--

Front-End Developer. Bandung, Indonesia.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store