फ्रेम को लगाएं, तस्वीर को नहीं।
एक आइकन को प्रदर्शित करने के लिए एक फ्रेम की आवश्यकता होती है, और उस फ्रेम में एक चित्र होना चाहिए। `@web-portfolio/icons` और `@web-portfolio/icons-sanity` इसलिए मौजूद हैं ताकि एक डेवलपर केवल फ्रेम को ही माउंट करे — चित्र कहीं से भी आ सकता है, यह निर्णय कि वह कहां से आएगा, वहीं पर लिया जाना चाहिए।

यह एक योजना नहीं, बल्कि एक प्रश्न के रूप में शुरू हुआ: क्या एक आइकन सामग्री होना चाहिए या कोड? किसी पोर्टफोलियो की कौशल सूची उसके घटकों की तुलना में अधिक बार बदलती है - एक नया कौशल का मतलब एक नया आइकन होता है - इसलिए आइकनों को ऐसी चीज़ के रूप में मानना जिसे सैनिटी (Sanity) रख सके, जिसे बिना फिर से तैनात किए संपादित किया जा सके, यह एक स्वाभाविक विकल्प प्रतीत हुआ। लेकिन यह लंबे समय तक स्पष्ट नहीं रहा।
'आइकन को सामग्री के रूप में उपयोग करने' की अवधारणा ने वास्तव में कहाँ तक पहुंचाया।
पहले संस्करण में, एक आइकन को सीधे एसवीजी मार्कअप के रूप में एक साधारण टेक्स्ट फ़ील्ड में संग्रहीत किया जाता था, क्योंकि यह "आइकन को सामग्री के रूप में" प्रदर्शित करने का सबसे स्पष्ट तरीका था: संपादक जो भी सामग्री रखता है उसे पेस्ट करता है, और फ्रंटएंड इसे प्रस्तुत करता है। यहीं पर चीजें स्पष्ट होना बंद हो गईं।
हर पेस्ट किए गए आइकन का स्रोत अलग-अलग था—एक में दो रंगों वाला सादा पथ था, दूसरे में एक निश्चित रंग भरा हुआ था और उसमें `<g>` नामक एक जटिल संरचना थी जिसमें कई अंतर्निहित शैलियाँ थीं जिन्हें कोई भी संपादक देख या हटा नहीं सकता था। कुछ में 24x24 पिक्सेल का दृश्य क्षेत्र (viewBox) था, कुछ में 512x512 पिक्सेल, और कुछ में बिल्कुल भी नहीं। किसी भी एक आकार के आधार पर कोई घटक बनाना संभव नहीं था।
इसलिए, घटक एक निश्चित आकार, रंग या रेखा की मोटाई को लागू करने की कोशिश करता था, और यही वह पहलू था जिसने एकरूपता को बेहतर बनाने के बजाय बदतर बना दिया। `currentColor` केवल तभी काम करता है जब स्रोत कोड में पहले से ही कोई विशिष्ट रंग निर्धारित न किया गया हो, और `stroke-width` भी तब लागू नहीं होता जब आइकन स्वयं अपनी रेखा की मोटाई सेट कर लेता है। हर नए आइकन को पेस्ट करने पर, उसे एक अलग सेटिंग की आवश्यकता होती थी, जो कि उस कंटेंट फ़ील्ड के उद्देश्य को विफल करता था जिसे किसी भी कोड परिवर्तन की आवश्यकता नहीं होनी चाहिए.
एक ऐसा कंटेंट फ़ील्ड जो हर बार किसी व्यक्ति द्वारा उपयोग किए जाने पर कोड में बदलाव की आवश्यकता होती है, वास्तव में एक कंटेंट फ़ील्ड नहीं होता।
इसके पीछे एक वास्तविक खतरा था: वह फ़ील्ड `dangerouslySetInnerHTML` के साथ प्रस्तुत किया गया था, जो सीधे किसी भी चीज़ को DOM में इंजेक्ट कर देता था—यह सिर्फ़ एक फ़ॉर्मेटिंग समस्या नहीं थी, बल्कि एक गंभीर सुरक्षा जोखिम था। इस समस्या को ठीक करने की कोशिशों से केवल कोड बढ़ता रहा, लेकिन वास्तविक समस्या का समाधान नहीं हुआ: फ़ील्ड का मान अभी भी वही था जो उसमें डाला गया था।
केवल कोड में किए गए बदलाव भी इसका समाधान नहीं करते।
हर कोई कोड में आइकन को कैसे हैंडल करता है, उसी तरीके का पालन करने से भी कंटेंट संबंधी समस्या का समाधान नहीं होता। आइकन फ़ॉन्ट फ्रेम और चित्र को एक क्लास नाम में जोड़ते हैं — `<i class="fa fa-docker">` — और एक छोटी सी टाइपिंग की गलती होने पर वह खाली दिखाई देता है। स्प्राइट शीट भी इसी तरह काम करते हैं, लेकिन इसमें एक आईडी का उपयोग किया जाता है। इनलाइन SVG कंपोनेंट सबसे मुश्किल होते हैं: चित्र आयात ग्राफ में एम्बेडेड होता है, इसलिए कोई रजिस्ट्री या फ़ील्ड नहीं होती है। थर्ड-पार्टी लाइब्रेरी जैसे कि lucide-react डेवलपर अनुभव को बेहतर बनाते हैं, लेकिन `import { Docker }` अभी भी कोड में बदलाव करना पड़ता है, बस यह थोड़ा आसान होता है।
प्रत्येक तरीका कोड में एक आइकन लिखने का एक पूरी तरह से उचित तरीका है। इनमें से किसी भी तरीके से, बिना कोड बदले, यह नहीं बदला जा सकता कि कौन सा चित्र प्रदर्शित होता है - और यही तो मूल उद्देश्य था।
इसे लगाएं: एक फ्रेम जो सिर्फ एक तस्वीर के लिए ही बना है।
@web-portfolio/icons यही काम करता है। `<Icon>` एक फ्रेम है - इसका `name` गुण रजिस्ट्री से एक चित्र लेता है, जिसे `packages/core` द्वारा `devicon`, `Material Symbols` और `Simple Icons` को `SVGO` के माध्यम से संसाधित करके बनाया जाता है। `<Icon>` वास्तविक मान और चर के बीच अंतर नहीं कर सकता है, इसलिए यह समान रूप से काम करता है चाहे आप किसी भी तरह से इसका उपयोग करें:
import { Icon } from '@web-portfolio/icons'
<Icon name="docker" size={32} /> // a literal picture
<Icon name={skill.icon} size={32} /> // a picture from a CMS fieldवह प्रारंभिक आवश्यकता थी जिसे @web-portfolio/icons-sanity को पूरा करने की आवश्यकता थी: एक चित्र केवल एक स्ट्रिंग है, इसलिए यह एक डेवलपर द्वारा टाइप किए जाने के बजाय, किसी संपादक द्वारा चुना जा सकता है। इसका `iconRef` स्कीमा प्रकार सैनिटी स्टूडियो में एक फ़ील्ड को खोज योग्य ग्रिड में बदल देता है - `IconPickerInput` हमेशा केवल उसी रजिस्ट्री से पाए गए चित्र को वापस लिखता है जिससे `<Icon>` पढ़ता है। एक डेवलपर का काम कभी भी चित्र चुनना नहीं था, बल्कि बस फ्रेम को माउंट करना था।
यह लागत का मूल्यांकन करना भी महत्वपूर्ण है: चूंकि दोनों पैकेज एक ही रजिस्ट्री (registry) को शामिल करते हैं, इसलिए सैनिटी (Sanity) से फ्रंटएंड तक केवल वही स्ट्रिंग भेजी जाती है—औसतन लगभग 7 बाइट्स, जबकि एक सामान्य आइकन के वास्तविक मार्कअप के लिए लगभग 955 बाइट्स की आवश्यकता होती है (एक जटिल लोगो के लिए यह कई KB तक भी हो सकता है)। इसके बजाय, यदि आप रॉ SVG को CMS फ़ील्ड में संग्रहीत करते हैं—यही वह समस्या है जिससे यह प्लगइन बचने का प्रयास करता है—तो प्रत्येक आइकन के लिए लागत 130-285 गुना अधिक होती है। आइकन फ़ॉन्ट और स्प्राइट शीट भी इसी समस्या से बचते हैं, क्योंकि वे केवल एक नाम का संदर्भ देते हैं; इनलाइन SVG घटक और इम्पोर्ट-आधारित लाइब्रेरी ऐसा नहीं कर सकतीं, जब तक कि वे पहले उस सटीक रजिस्ट्री को न बनाएं जो यह पैकेज पहले से ही है।
वही बदलाव एक आम समस्या को हल करता है: डिज़ाइन टीम द्वारा किसी आइकन को स्वीकृत करने से पहले ही उसका उपयोग करना। आज ही सबसे नज़दीकी चित्र जोड़ें—नाम="लिंक"—और जब वास्तविक लोगो उपलब्ध हो जाए तो केवल वही मान बदलें, बिना फिर से इम्पोर्ट किए या नए कंपोनेंट के। इसके बजाय, इसे एक CMS फ़ील्ड के माध्यम से रूट करें, और जो भी उस समय ज़िम्मेदार है—डिज़ाइन, मार्केटिंग, या इस सप्ताह का अभियान चलाने वाला कोई भी व्यक्ति—वे स्टूडियो में स्वयं चित्र बदल सकते हैं, जिसके लिए किसी PR की आवश्यकता नहीं होगी।
यह अस्थायी छवि उसी कोड परिवर्तन के साथ आती है जो नई सुविधा को जोड़ता है। बाद में केवल चित्र बदलता है।
यह वास्तव में कहाँ बेहतर साबित होता है।
एक ही चार मानदंडों के आधार पर मूल्यांकन किया गया है, जिसे तीन प्रश्नों में विभाजित किया गया है: इसे लिखने का अनुभव कैसा होता है, रनटाइम पर इसकी लागत क्या है, और जब आइकन को बदलने की आवश्यकता होती है और उसे बदलने वाला व्यक्ति डेवलपर नहीं है तो क्या होता है।
Writing it
icons + icons-sanity | @web-portfolio/icons | Third-party library | Inline SVG | SVG sprite | Icon font | |
|---|---|---|---|---|---|---|
| Adding an icon in code | एक ही कॉल—यह स्ट्रिंग किसी फ़ील्ड से आ सकती है। | `<Icon नाम="docker"/>` - एक प्रॉपर्टी. | इम्पोर्ट { डॉकर } — एडिटर ऑटो-कंप्लीट सुविधा प्रदान करता है। | प्रत्येक आइकन के लिए एक नई घटक फ़ाइल, जिसे कॉपी-पेस्ट किया गया है। | #डॉकर - समान प्रकार की टाइपिंग त्रुटियों का जोखिम। | fa-डॉकर — यह जांच नहीं की जाती कि ग्लाइफ़ मौजूद है या नहीं। |
| Type safety / autocomplete | पिकर केवल वास्तविक कुंजियों को ही लिखता है - यह स्टूडियो की गारंटी है, किसी भी प्रकार का समझौता नहीं। | नाम एक साधारण स्ट्रिंग है - केवल टाइपो की चेतावनी दी जाती है। | नाम वाले एक्सपोर्ट्स - एक टाइपो के कारण बिल्ड विफल हो जाता है। | प्रत्येक आइकन एक अलग नाम से इम्पोर्ट किया जाता है। | एक आईडी स्ट्रिंग - वही समस्या। | एक क्लास का नाम—यह जांचा नहीं जाता कि वह मौजूद है या नहीं. |
| Styling — color, stroke, size | समान गुणधर्म, समान घटक। | `<Icon>` पर आकार/रंग/रेखा-मोटाई गुण। | टाइप किए गए आकार/रंग/रेखा-चौड़ाई गुण। | यह केवल उतना ही सटीक है जितना कि इसे किसने डाला है। | फिल: करंट कलर साफ-सुथरा काम करता है। | रंग विरासत में मिलता है, लेकिन अक्षर का आकार तय होता है। |
Running it
icons + icons-sanity | @web-portfolio/icons | Third-party library | Inline SVG | SVG sprite | Icon font | |
|---|---|---|---|---|---|---|
| Bundle impact | यह कीमत केवल आइकन के लिए ही है। | एक रजिस्ट्री जिसमें सभी 633 आइकन शामिल हैं। | प्रत्येक आइकन के लिए पेड़ से गिरी हुई पत्तियाँ। | केवल आयातित आइकन ही भेजे जाएंगे। | सभी आइकन एक ही शीट में उपलब्ध हैं। | फ़ॉन्ट फ़ाइल का पूरा भाग, किसी भी स्थिति में लोड हो जाता है। |
| Runtime selection | उसी तरह, पिकर के माध्यम से। | किसी भी प्रकार की वायरिंग की आवश्यकता नहीं है। | वही— खुद ही नक्शा बनाएं। | इसे एक मैनुअल नाम मानचित्र की आवश्यकता है। | पहले से ही पहचान निर्धारित कर दी गई है। | पहले से ही स्ट्रिंग-कीड। |
Living with it
icons + icons-sanity | @web-portfolio/icons | Third-party library | Inline SVG | SVG sprite | Icon font | |
|---|---|---|---|---|---|---|
| Selecting from dynamic data | यह सामग्री बिना किसी नए इंस्टॉलेशन के अपडेट होती रहती है। | नाम कहीं से भी आ सकता है - कुछ भी आपको इसे सही करने में मदद नहीं करता। | हर आइकन एक घटक है, न कि केवल एक खोज कुंजी। | यह आइकन एक विशिष्ट आयात है, जो कि बदली जाने वाली कोई सामान्य वैल्यू नहीं है। | समान - एक आईडी स्ट्रिंग जिसमें कोई चयनकर्ता नहीं है। | एक स्ट्रिंग — इसके लिए कोई भी पिकर प्रदर्शित नहीं होता है। |
| Non-developer control | IconPickerInput में वह पिकर पहले से ही निर्मित होता है। | कोई संपादन सतह नहीं है - फिर भी एक डेवलपर किसी प्रॉपर्टी को बदल रहा है। | यह भी वही है - यह कोड स्तर पर किया गया इम्पोर्ट है। | आइकन बदलना का मतलब है कोड में बदलाव करना। | अभी भी किसी को वह "पिकर" बनाना होगा। | एक ऐसे पिकर की आवश्यकता है जिसे हाथ से बनाया गया हो और जो ग्लिफ़ के नामों को जानता हो। |
| Adding a brand-new icon | समान प्रतिबंध - एक व्यवस्थापक द्वारा सीधे SVG कोड डालने की क्षमता एक "बचाव मार्ग" का काम करती है। | स्रोत को आरंभ करें, "generate-registry" चलाएं, और फिर से प्रकाशित करें। | आपको वही मिलेगा जो लाइब्रेरी में उपलब्ध है, और कुछ नहीं। | एसवीजी को पेस्ट कर दिया गया है, काम पूरा हुआ - अब किसी भी फ़ाइल को साझा करने की आवश्यकता नहीं है। | स्प्राइट फ़ाइल को संपादित करें, फिर से तैनात करें। | फ़ॉन्ट फ़ाइल को फिर से बनाएं, पुनर्निर्माण करें और पुनः तैनात करें। |
| Consistency across sources | एक ही रजिस्ट्री—स्टूडियो एक ऐसे मान को भी चिह्नित करता है जो अब अप्रचलित हो गया है। | कई आइकन सेटों को एक ही रजिस्ट्री में समेकित किया गया। | यह आंतरिक रूप से सुसंगत होता है, लेकिन आमतौर पर इसमें ब्रांड के लोगो शामिल नहीं होते हैं। | जब तक कोई इसे लागू नहीं करता, तब तक कोई भी नियम या अनुशासन प्रभावी नहीं हो सकता। | यह केवल उतना ही अच्छा होगा जितना उसमें डाले गए तत्वों की गुणवत्ता पर निर्भर करता है। | एक परिवार, एक दृश्य भाषा, निर्माण के माध्यम से। |
| License & provenance | समान श्रेय, बिना किसी बदलाव के विरासत में मिला। | प्रत्येक स्रोत के लिए एक बार, रजिस्ट्री (registry) और README फ़ाइल में उल्लेख किया जाना चाहिए। | पूरे पैकेज के लिए एक ही लाइसेंस, आमतौर पर यह स्पष्ट होता है। | ट्रैकिंग नहीं— स्रोत की जानकारी उस व्यक्ति के पास रहती है जिसने इसे डाला है, यदि कोई व्यक्ति उसे डालता है। | आमतौर पर, एक बार जब आइकन को एक फ़ाइल में कॉपी कर दिया जाता है, तो उनकी ट्रैकिंग बंद हो जाती है। | चाहे फ़ॉन्ट पैकेज का लाइसेंस कुछ भी कहता हो, लेकिन आमतौर पर यह प्रति अक्षर के हिसाब से होता है। |
Bundle impact, @web-portfolio/icons row: the ✕ is a client-side cost only. Render <Icon> in a server component and the registry never reaches the browser at all.
इसका एक पहलू है, और इसे छिपाने की कोई कोशिश नहीं।
ऊपर दिए गए दो बिंदु वास्तविक कमियां हैं: बंडल का आकार और टाइप सुरक्षा। बंडल का आकार इसलिए कम होता है क्योंकि registry.generated.ts में सभी आइकन एक ही ऑब्जेक्ट में होते हैं, अलग-अलग टुकड़ों में नहीं जिन्हें एक बंडलर छोटा कर सकता है - इसलिए <Icon> सभी 633 आइकन को लोड करता है, भले ही किसी पेज पर केवल एक आइकन का उपयोग किया जा रहा हो। एक साधारण इम्पोर्ट-आधारित लाइब्रेरी इस समस्या से बचती है, लेकिन इसके लिए रनटाइम चयन को छोड़ना पड़ता है, जो कि आइकन को कंटेंट के रूप में संग्रहीत करने के उद्देश्य को विफल कर देता है। ऊपर दिए गए फुटनोट में दिया गया समाधान यह है: <Icon> को सर्वर पर रेंडर करें, और रजिस्ट्री कभी भी ब्राउज़र तक नहीं पहुंचेगी - आकार का अंतर गायब हो जाएगा। यह सामान्य तौर पर आइकनों के लिए एक अच्छा डिफ़ॉल्ट विकल्प है, खासकर अब जब वे कोड की बजाय कंटेंट हो सकते हैं: वैल्यू केवल अनुरोध के समय मौजूद होती है, इसलिए इसे कभी भी ब्राउज़र में भेजने का कोई कारण नहीं था। इस तरह रेंडर करने पर, <Icon> इनलाइन SVG और एक छोटे इम्पोर्ट के बराबर होता है, जिसमें अतिरिक्त जावास्क्रिप्ट की आवश्यकता नहीं होती है, और यह वेब फ़ॉन्ट (जिसके लिए अपनी अलग फाइल की आवश्यकता होती है) और स्प्राइट शीट (जो सब कुछ बंडल करता है या इसके अपने अनुरोध की लागत लेता है) से बेहतर है।
टाइप सुरक्षा भी इसी तरह से काम करती है। यदि आप मैन्युअल रूप से कोई नाम लिखते हैं, तो वह सिर्फ टेक्स्ट होता है—कोई भी टाइपिंग की गलती को नहीं पकड़ता। icons-sanity का पिकर केवल एक संपादक को उस नाम को चुनने देता है जो वास्तव में मौजूद है, लेकिन यह गारंटी स्टूडियो इंटरफ़ेस में होती है, न कि अंतर्निहित स्कीमा में—एक टाइपिंग की गलती अभी भी स्टूडियो के बाहर से आ सकती है। इसे स्कीमा स्तर पर लागू करने की योजना अगले छोटे संस्करण में है।
आपको वास्तव में कब इस चीज़ का उपयोग करना चाहिए।
यह एक ऐसे सीएमएस-समर्थित प्रोजेक्ट के लिए उपयुक्त है—जैसे कि पोर्टफोलियो, एजेंसी वेबसाइट या मार्केटिंग पेज—जहां "कौन सा आइकन" एक सामग्री संबंधी निर्णय है, न कि एक तकनीकी निर्णय। यदि कोडबेस के बाहर कभी भी कुछ संपादित नहीं किया जाता है, तो बस ट्री-शेक किए गए लाइब्रेरी का उपयोग करें: तस्वीर हमेशा समान रहने वाली थी, इसलिए शुरू से ही कोई ऐसा फ्रेम मौजूद नहीं था जिसे माउंट करने की आवश्यकता होती।
एक बार फ्रेम को सही ढंग से स्थापित कर लें, और फिर तस्वीर बदलना एक नवीनीकरण नहीं रह जाता—बस एक निकालें और दूसरा डालें। फ्रेम दीवार पर ही रहता है: कोई नया छेद नहीं, और यह टेढ़ा होने का जोखिम भी नहीं होता। यही रजिस्ट्री (registry) और चयनकर्ता (picker) बनाने का असली लाभ है: `<Icon name>`, रजिस्ट्री, और `IconPickerInput` फिर से नहीं बदलते। केवल उन तक पहुंचने वाले नाम बदलते हैं।
फ्रेम को लगाएं, तस्वीर सौंप दें, और फिर शिपिंग पर वापस लौट जाएं।