Shipping Notes29 أغسطس 2026

قم بتركيب الإطار، وليس الصورة.

تحتاج الرموز إلى إطار لعرضها وصورة للعرض داخل الإطار. توجد الحزمتان `@web-portfolio/icons` و `@web-portfolio/icons-sanity` بحيث يقوم المطور فقط بتركيب الإطار، ويمكن أن تأتي الصورة من أي مكان، وهذا القرار يقع ضمن مسؤولية الجهة المناسبة.

قم بتركيب الإطار، وليس الصورة.

بدأ الأمر كسؤال، وليس كخطة: هل يجب أن يكون الرمز (icon) عبارة عن محتوى أم كود؟ تتغير قائمة المهارات في ملف الأعمال (portfolio) بشكل أكثر تكرارًا من مكوناتها، فكل مهارة جديدة تعني رمزًا جديدًا. لذلك، بدا من الواضح التعامل مع الرموز على أنها شيء يمكن لنظام Sanity تخزينه وتعديله دون الحاجة إلى إعادة نشر. لكن هذا الأمر لم يكن واضحًا لفترة طويلة.

إلى أين أدى مفهوم "الرمز كعنصر محتوى" فعليًا؟

النسخة الأولى كانت تخزن الرمز كعلامات SVG خام في حقل نصي عادي، وذلك لأن هذا هو الشكل الواضح لـ "الرمز كمحتوى": يقوم المحرر بلصق ما لديه، ويقوم الواجهة الأمامية بعرضه. ولكن هنا توقف الأمر عن أن يكون واضحًا.

كل أيقونة تم لصقها كانت مستمدة من مصدر مختلف - إحدى الأيقونات كانت بتنسيق تصدير مع مسار ثنائي اللون واضح، وأخرى تحتوي على تعبئة ثابتة وعنصر `<g>` متداخل مليء بأنماط مضمنة لا يمكن لأي محرر رؤيتها أو إزالتها. بعضها كان له `viewBox` بحجم 24×24، وبعضها الآخر 512×512، وبعضها لم يكن لديه أي `viewBox` على الإطلاق. لم تكن هناك شكل واحد يمكن استخدامه لبناء مكون.

حاول المكون فرض خصائص معينة — مثل الحجم أو اللون أو سمك الخط — وهذا الجزء تحديدًا هو ما أدى إلى تفاقم مشكلة عدم الانتظام، بدلاً من تحسينها. يعمل خاصية `currentColor` فقط إذا لم يكن الكود الأصلي قد حدد بالفعل قيمة لون التعبئة بشكل ثابت. كما أن تجاوز قيمة `stroke-width` لا ينطبق أيضًا بمجرد أن يحدد الرمز الخاص به قيمته. كان كل رمز جديد يتم لصقه يحتاج إلى إعدادات خاصة به، وهو ما يقوض الهدف من حقل المحتوى الذي يفترض ألا يتطلب أي تغييرات في الكود على الإطلاق.

حقل المحتوى الذي يتطلب تغيير الكود في كل مرة يتم استخدامه ليس حقلاً للمحتوى بالمعنى الحقيقي.

تحت كل ذلك، كانت تكمن المشكلة الخطيرة: فقد تم عرض هذا الحقل باستخدام خاصية `dangerouslySetInnerHTML`، مما يتيح إدخال أي شيء يتم لصقه مباشرةً في هيكل DOM - وهو خطر أمني حقيقي، وليس مجرد مشكلة تتعلق بالتنسيق. إن محاولة إصلاح هذه المشكلة عن طريق إضافة تعليمات برمجية لم تعالج المشكلة الأساسية: فما زالت قيمة الحقل هي بالضبط ما تم لصقه فيه.

إن الإصلاحات التي تعتمد على التعليمات البرمجية فقط لا تحل المشكلة أيضًا.

العودة إلى الطريقة التي يتعامل بها الجميع مع الرموز في التعليمات البرمجية لا تحل مشكلة محتوى الرمز، على الرغم من ذلك. تستخدم خطوط الرموز فئة تجمع بين الإطار والصورة — `<i class="fa fa-docker">`— وأي خطأ إملائي يؤدي ببساطة إلى ظهور مساحة فارغة. تعمل أوراق الصور بنفس الطريقة، ولكن باستخدام مُعرّف بدلاً من ذلك. تعتبر مكونات SVG المضمنة هي الأصعب: يتم تضمين الصورة في الرسم البياني للاستيراد، لذلك لا يوجد سجل ولا حقل على الإطلاق. توفر المكتبات الخارجية مثل lucide-react تجربة تطوير أفضل، ولكن استيراد `{ Docker }` لا يزال يتطلب تغييرًا في التعليمات البرمجية، ولكنه تغيير أكثر سلاسة.

كل طريقة من هذه الطرق هي وسيلة منطقية تمامًا لكتابة رمز أيقونة في التعليمات البرمجية. لا تسمح أي منها بتغيير الصورة المعروضة دون تعديل التعليمات البرمجية، وهذا هو الهدف الأساسي.

ضعه فيه: إطار مصمم خصيصًا لعرض صورة.

تقوم الحزمة `@web-portfolio/icons` بحل هذه المشكلة. `<Icon>` هو إطار، ويأخذ اسم الصورة من خلال خاصية "name"، حيث يتم استخلاص الصور من حزم مسجلة تقوم ببناءها مكتبة `core` عن طريق دمج الرموز `devicon` و `Material Symbols` و `Simple Icons` باستخدام أداة `SVGO`. لا يمكن لـ `<Icon>` التمييز بين قيمة حرفية ومتغير، لذا فإن نفس الاستدعاء يعمل في كلتا الحالتين.

packages/react — usagetsx
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` بتحويل حقل إلى شبكة قابلة للبحث فيها في Sanity Studio، حيث يكتب `IconPickerInput` فقط الصور التي تم العثور عليها في نفس السجل الذي يستخدمه `<Icon>`. مهمة المطور ليست اختيار الصورة، بل تركيب الإطار فقط.

من المهم أيضاً حساب التكلفة: بما أن الحزمتين تتضمنان نفس السجل، فإن الشيء الوحيد الذي يتم نقله عبر الشبكة من Sanity إلى الواجهة الأمامية هو هذه السلسلة النصية - والتي تبلغ متوسط حجمها حوالي 7 بايت، مقارنة بحوالي 955 بايت للتعليمات البرمجية الفعلية للرمز المتوسط (وقد تصل إلى عدة كيلوبايت للعلامة التجارية المعقدة). تخزين ملف SVG الخام في حقل نظام إدارة المحتوى (CMS) بدلاً من ذلك - وهو الحل الذي يهدف هذا الملحق إلى تجنبه - يكلف ما بين 130 و285 ضعفاً أكثر لكل رمز، في كل مرة يتم فيها استرجاعه. تتجنب خطوط الرموز وصفحات الصور أيضاً هذه المشكلة عن طريق الإشارة إلى اسم؛ بينما لا يمكن لمكونات SVG المضمنة والمكتبات القائمة على الاستيراد تجنب ذلك، إلا إذا تم بناء السجل الدقيق الذي يوفره هذا الملحق بالفعل.

يُحل هذا التغيير المشكلة الشائعة المتمثلة في إرسال رمز قبل أن يوافق فريق التصميم عليه. قم بعرض الصورة الأقرب اليوم باستخدام الاسم "link"، وقم بتغيير هذه القيمة فقط عندما يتم إصدار العلامة التجارية الرسمية، دون الحاجة إلى إعادة الاستيراد أو إنشاء مكون جديد. بدلاً من ذلك، قم بتوجيهها عبر حقل في نظام إدارة المحتوى (CMS)، وسيقوم الشخص المسؤول عن هذا الجزء – سواء كان فريق التصميم أو التسويق أو أي قسم آخر مسؤول عن الحملة الأسبوعية – بتغيير الصورة بنفسه، مباشرةً في البرنامج Studio، دون الحاجة إلى تدخل من فريق التطوير.

يتم تضمين العنصر البديل في نفس التعديل البرمجي الخاص بالميزة. فقط الصورة تتغير لاحقًا.

حيث تتفوق فعليًا.

يتم تقييمها بناءً على أربع طرق مختلفة، وتقسم إلى ثلاثة أسئلة: ما هو شعور عملية الكتابة؟ وما هي التكلفة في وقت التشغيل؟ وماذا يحدث عندما تحتاج الأيقونة إلى تغيير ولا يكون الشخص الذي يقوم بالتغيير مطورًا؟

Writing it

icons + icons-sanity
@web-portfolio/icons
Third-party library
Inline SVG
SVG sprite
Icon font
Adding an icon in code
نفس الاستدعاء - يمكن أن تأتي السلسلة من حقل.
<أيقونة باسم "docker"/> - خاصية واحدة.
استيراد {Docker} - يكتمل النص تلقائيًا في المحرر.
ملف مكون جديد لكل أيقونة، يتم نسخه ولصقه.
#docker - نفس خطر الأخطاء الإملائية عند الكتابة بدون رؤية.
fa-docker - لا يوجد فحص للتأكد من وجود الرمز.
Type safety / autocomplete
يُصدر "بيكر" مفاتيح حقيقية فقط - وهذا ضمان من "استوديو"، وليس مجرد نوع واحد.
الاسم عبارة عن سلسلة نصية بسيطة، والخطأ الإملائي هو مجرد تحذير.
التصدير المسمى - خطأ إملائي يؤدي إلى فشل عملية البناء.
كل رمز هو استيراد مستقل له اسم محدد.
سلسلة معرف - نفس المشكلة.
اسم الفئة (سلسلة نصية) - لا يوجد فحص للتأكد من وجودها.
Styling — color, stroke, size
نفس الخصائص، نفس المكون.
خصائص الحجم/اللون/السُمك لعنصر `<Icon>`.
خصائص الحجم واللون وعرض الخط المدخلة.
إنها دقيقة فقط بقدر الشخص الذي قام بلصقها.
الخاصية "fill: currentColor" تعمل بشكل جيد.
اللون يرث الصفات، ولكن سمك الخط جزء لا يتجزأ من شكله.

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"، وأعد نشر النتائج.
تحصل على ما توفره المكتبة، ولا شيء آخر.
الصق كود SVG، تم الأمر - لا يوجد ملف مشترك للوصول إليه.
قم بتحرير ملف الصورة، ثم أعد نشره.
أعد إنشاء ملف الخط، وأعِد تجميعه، ثم أعد نشره.
Consistency across sources
نفس السجل - كما أن البرنامج يحدد قيمة أصبحت قديمة ولم تعد صالحة.
تم دمج مجموعات أيقونات متعددة في سجل واحد.
الاتساق الداخلي موجود، ولكن شعارات العلامات التجارية عادة لا تكون مضمنة فيه.
لا يوجد انضباط مشترك إلا إذا قام شخص بتطبيقه.
جودته تعتمد فقط على جودة المواد المستخدمة في تصنيعه.
عائلة واحدة، لغة بصرية موحدة، من خلال التصميم.
License & provenance
نفس الإسناد، موروث دون تغيير.
يُشار إليه مرة واحدة لكل مصدر، في السجل وملف التعليمات.
ترخيص واحد للبرنامج بأكمله، وعادةً ما يكون واضحًا.
غير مُتتبّع - يظل مصدر الإسناد مع الشخص الذي قام بلصقه، إن وجد.
عادةً ما يتم تجاهل التغييرات بعد نسخ الرموز إلى ملف واحد.
بغض النظر عما تنص عليه ترخيص حزمة الخطوط، نادرًا ما يكون السعر لكل حرف.

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` يحتوي على جميع الرموز في كائن واحد، وليس أجزاء منفصلة يمكن لأداة تجميع (bundler) تقليلها — وبالتالي، يقوم العنصر `<Icon>` بتحميل جميع الـ 633 رمزًا حتى لو كانت الصفحة تستخدم رمزًا واحدًا فقط. تتجنب المكتبة العادية التي تعتمد على الاستيراد هذه المشكلة فقط عن طريق التخلي عن الاختيار في وقت التشغيل، وهو ما يقوض الهدف من تخزين الرموز كمحتوى. الحل، كما هو مذكور في الحاشية أعلاه: قم بعرض العنصر `<Icon>` على الخادم، ولن يصل سجل الرموز إلى المتصفح — وبالتالي، يختفي الفرق في الحجم. هذا أيضًا هو الإعداد الافتراضي المناسب للرموز بشكل عام، خاصة الآن بعد أن يمكن اعتبارها محتوى بدلاً من التعليمات البرمجية: القيمة موجودة فقط عند الطلب، لذلك لم يكن هناك سبب لإرسالها إلى المتصفح. عند عرضها بهذه الطريقة، يتطابق العنصر `<Icon>` مع SVG المضمن واستيراد مُقَلَّص دون إضافة أي JavaScript إضافي، ويتفوق على خط الويب (الذي لا يزال يحتاج إلى ملفه الخاص) ورسمة الرموز (التي تجمع كل شيء أو تتطلب طلبًا منفصلًا).

تعتمد سلامة الأنواع على نفس المبدأ. عند كتابة اسم يدويًا، فإنه مجرد نص - ولا يوجد شيء يكتشف الأخطاء الإملائية. يسمح محرر "icons-sanity" للمحرر فقط باختيار اسم موجود بالفعل، ولكن هذا الضمان موجود في واجهة "Studio"، وليس في المخطط الأساسي - لا يزال من الممكن حدوث أخطاء إملائية من خارج "Studio". ويتم التخطيط لتطبيق ذلك على مستوى المخطط في الإصدار الثانوي القادم.

متى يجب عليك اللجوء إلى ذلك تحديدًا؟

هذا يناسب مشروعًا مدعومًا بنظام إدارة محتوى (CMS) - مثل موقع عرض أعمال، أو موقع وكالة، أو صفحة تسويقية - حيث أن "اختيار الأيقونة" هو قرار يتعلق بالمحتوى وليس بالهندسة. إذا لم يتم تعديل أي شيء خارج الكود المصدري، فما عليك سوى استخدام المكتبة المُحسّنة: الصورة كانت دائمًا ستبقى كما هي، لذلك لم يكن هناك إطار عمل يستحق التركيب في المقام الأول.

عندما يتم تركيب الإطار بشكل صحيح مرة واحدة، يصبح تغيير الصورة عملية بسيطة لا تتطلب تجديدًا - ما عليك سوى إزالة صورة وإدخال أخرى. يبقى الإطار مثبتًا على الحائط: لا توجد ثقوب جديدة، ولا يوجد خطر من إمالته. هذا هو الفائدة الحقيقية من إنشاء قاعدة البيانات وأداة الاختيار: لا تتغير قاعدة البيانات (Icon name) وأداة الاختيار (IconPickerInput) مرة أخرى. يتغير فقط الاسم الذي يتم تمريره إليهما.

قم بتركيب الإطار، وسلم الصورة، ثم عد إلى تجهيز الشحنات.

Author

Jatin Kumar

Frontend Engineer

Icon gallery@web-portfolio/icons@web-portfolio/icons-sanitygithub.com/jatinrao/iconsSanity Plugin