EduFlow

الوحدة 4 · الأدوات والتسليم

الـ Semver والـ Changelog لنظام التصميم

20 دقراءة 4 داملأ الفراغات

الدرس في لمحة

طبّق الـ semver (الترقيم الدلالي للإصدارات) على نظام التصميم بتحديد الـ API العام بدقّة، وتصنيف التغييرات على الـ props والـ tokens والمظهر، وكتابة changelog يخبر المستخدمين بما عليهم فعله.

  1. 1الـ semver في فقرة واحدة
  2. 2حدّد الـ API العام أولًا
  3. 3تصنيف التغييرات الحقيقية
  4. 4الـ Changesets والـ changelogs

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

حين أطلقت Northwind الإصدار 3.4.0 من حزمة الواجهة، فشل البناء لدى أحد عشر فريقًا في الصباح التالي. ملاحظات الإصدار كانت تقول «تحسينات طفيفة». أحد تلك التحسينات أعاد تسمية الـ token المسمّى --nw-color-text-light، لأن الفريق قرّر (محقًّا) أن الاسم مضلِّل. كان تغييرًا جيدًا أُطلق برقم خاطئ، وكلّفنا من الثقة أكثر من أي خلل في تلك السنة. الترقيم هو الطريقة التي يقطع بها نظام التصميم وعوده لمستخدميه، والوعود المكسورة باهظة الثمن.

الـ semver في فقرة واحدة

الترقيم الدلالي للإصدارات (semantic versioning أو semver) يعطي كل إصدار رقمًا بالشكل MAJOR.MINOR.PATCH. الـ patch ‏(4.7.2 ← 4.7.3) يصلح أخطاء دون تغيير الـ API العام. والـ minor ‏(4.7.2 ← 4.8.0) يضيف وظائف بطريقة متوافقة مع ما سبق. والـ major ‏(4.7.2 ← 5.0.0) يحتوي تغييرات قد تكسر الاستخدام الحالي. ومديرو الحزم يعتمدون على هذا: الاعتمادية المكتوبة بالشكل ^4.7.2 تقبل أي 4.x.x من 4.7.2 فصاعدًا، لكنها لا تقبل 5.0.0 أبدًا. وقبل 1.0.0 يمكن أن يتغيّر أي شيء في أي وقت، لذا يجب أن يصل النظام الذي تعتمد عليه الفرق إلى 1.0 مبكرًا.

حدّد الـ API العام أولًا

الـ semver لا يعني شيئًا حتى تكتب ما هو «الـ API العام». في Northwind UI هو:

  • كل مكوّن مُصدَّر والـ props الخاصة به، بما فيها قيمها الافتراضية؛
  • كل اسم token في حزمة الـ tokens، والدور الذي يصفه كلٌّ منها؛
  • نقاط ربط CSS الموثّقة: data-variant وdata-size والـ component tokens؛
  • السلوك الموثّق، مثل «يعيد Dialog التركيز إلى العنصر الذي فتحه».

أي شيء خارج هذه القائمة، مثل أسماء الـ classes الداخلية، أو تداخل الـ DOM داخل المكوّن، أو الدوال المساعدة الخاصة، يمكن أن يتغيّر في أي إصدار. اكتب القائمة في التوثيق، وأخبر الفرق بوضوح أن الاعتماد على التفاصيل الداخلية غير الموثّقة على مسؤوليتها.

تصنيف التغييرات الحقيقية

بعد تحديد الـ API، تصبح معظم القرارات آلية:

التغيير الرفع
إصلاح Dialog الذي لا يعيد التركيز عند الإغلاق Patch
إضافة prop باسم loading إلى Button Minor
إضافة مكوّن Tabs جديد أو token جديد Minor
تحسين قيمة token ضمن الدور نفسه Minor، مع ملاحظة وفروقات بصرية
إعادة تسمية token أو prop أو مكوّن، أو حذفه Major
تغيير قيمة افتراضية، مثل الـ variant الافتراضي لـ Button Major

صفّان يستحقّان تعليقًا. تغيير القيمة الافتراضية major لأن الكود الذي اعتمد عليها صار يُعرض بشكل مختلف دون تغيير سطر واحد. وتحسينات القيم minor لأن عقد الـ token هو دوره، لا لونه بالضبط؛ فإن صار «النص الهادئ» أغمق قليلًا لينجح في التباين، فكل شاشة استخدمته ما زالت تحصل على نص هادئ. عامل هذه كتحديثات تصميم: أبرزها في الـ changelog بصور قبل وبعد، لأن الفرق التي لديها اختبارات لقطات شاشة سترى صورها المرجعية تتغيّر.

الـ Changesets والـ changelogs

تذكّر الرفع الصحيح وقت الإصدار لا يصمد مع كثرة المساهمين. تستخدم Northwind أداة Changesets: كل pull request يغيّر حزمة يضيف ملفًا صغيرًا يذكر نوع الرفع وملخّصًا مكتوبًا للمستخدمين.

.changeset/quiet-trucks-dance.md
---
"@northwind/ui": minor
---

Button: add a `loading` prop. While loading, the button keeps its width, sets `aria-busy`, and ignores clicks.

حين يُجهَّز إصدار، تأخذ الأداة أعلى رفع بين الـ changesets المعلّقة، وتحدّث الإصدار، وتحوّل الملخّصات إلى CHANGELOG.md. وهذا المنطق نفسه في صورة مصغّرة:

JavaScript
const order = { patch: 0, minor: 1, major: 2 };

function nextVersion(current, changes) {
  const [major, minor, patch] = current.split('.').map(Number);
  const bump = changes.reduce((top, c) => (order[c.bump] > order[top] ? c.bump : top), 'patch');
  if (bump === 'major') return `${major + 1}.0.0`;
  if (bump === 'minor') return `${major}.${minor + 1}.0`;
  return `${major}.${minor}.${patch + 1}`;
}

const pending = [
  { bump: 'patch', summary: 'Dialog returns focus to its trigger on close.' },
  { bump: 'minor', summary: 'Button: add a `loading` prop.' },
];
console.log(nextVersion('4.7.2', pending));
console.log(nextVersion('4.7.2', [...pending, { bump: 'major', summary: 'Remove Button `kind`.' }]));

الـ changelog الجيد يجمّع الإدخالات حسب الأثر (كاسر، مُضاف، مُصلَح)، ويُكتب للأشخاص الذين يرفعون الإصدار، ويعطي كل إدخال كاسر خطوات الانتقال الخاصة به. عبارة «Refactored Button internals» لا تقول لفريق المنتج شيئًا؛ أما «حُذف kind من Button، استخدم variant، وهناك codemod متاح» فتقول له بالضبط ما يفعل.

في التمرين تعطي أرقام إصدارات لدفعة من التغييرات. وبعدها تخطّط لكيفية حذف الأشياء دون أن تكسر عمل أربعين فريقًا في صباح واحد.

أهم النقاط

  • لا يعمل الـ semver إلا حين تكتب ما هو الـ API العام لديك؛ وفي نظام التصميم يشمل ذلك props المكوّنات، والأسماء المصدَّرة، وأسماء الـ tokens، ونقاط ربط CSS الموثّقة.
  • حذف أي شيء عام أو إعادة تسميته، أو تغيير قيمة افتراضية، تغيير major، حتى حين يبدو صغيرًا.
  • إضافة props أو مكوّنات أو tokens تغيير minor؛ وإصلاح السلوك ليطابق التوثيق patch.
  • الـ changelog يُكتب للمستخدمين، ويُجمَّع حسب الأثر، وكل إدخال كاسر يتضمّن خطوات الانتقال.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

الحزمة @northwind/ui في الإصدار 4.7.2. إصدار جديد يحذف الـ prop المُهمَل kind من Button (حلّ محلّه variant في 4.3.0). ما الإصدار التالي؟

السؤال 2 من 3

تجعل Northwind اللون --nw-color-text-subtle أغمق قليلًا حتى ينجح في التباين في الوضع الداكن. الاسم والدور يبقيان كما هما. كيف يجب أن يُطلَق؟

السؤال 3 من 3

أيّ إدخال في الـ changelog هو الأكثر فائدة لفريق منتج؟

تدريب

املأ الفراغات

رقّم الإصدارات

طبّق سياسة الترقيم في Northwind من الدرس. كل سطر إصدار مستقل يبدأ من 4.7.2. اكتب رقم الإصدار التالي كاملًا لكلٍّ منها، مثل 4.7.3.

@northwind/ui is at 4.7.2. Each line is a separate release from 4.7.2. Fix: Dialog now returns focus to its trigger on close -> Add a `loading` prop to Button -> Remove the old token name --nw-color-text-light -> Change Button's default variant from "primary" to "secondary" -> A Dialog focus fix plus a new Tabs component -> Darken --nw-color-text-subtle slightly (same name and role) ->