{"id":"l-ds-4-3","courseId":"design-systems","lang":"ar","kind":"lesson","summary":"احذف الـ tokens والـ props والمكوّنات دون كسر عمل الفرق، بإطلاق البديل أولًا، وإعلان الـ deprecation بوضوح في كل قناة، وأتمتة الانتقال بالـ codemods، والحذف في إصدار major فقط.","html":"<p>الحذف أصعب جزء في إدارة نظام التصميم. إضافة مكوّن تُسعد فريقًا واحدًا؛ أما حذف مكوّن فيجعل أربعين فريقًا يقومون بعمل لم يخطّطوا له. حذف أول إصدار major في Northwind أحد عشر prop وستة tokens دفعة واحدة، مع دليل انتقال نُشر في اليوم نفسه. وبعد تسعة أشهر كان فريقان ما زالا على الإصدار الـ major القديم، ينسخان الإصلاحات بصمت. عمليات الحذف نفسها كانت صحيحة. ما كان ينقص هو الطريق إليها.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">مسار الـ deprecation</h2>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 210\" role=\"img\" aria-labelledby=\"l-ds-4-3-t1\"><defs><marker id=\"arrow-l-ds-4-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-ds-4-3-t1\">خط زمني من الإصدار 4.3 إلى 5.0 وما بعده: يُطلق 4.3 البديل ويعلن deprecation الـ API القديم؛ ومن 4.4 إلى 4.9 يجري الانتقال بالـ codemods وتتبّع الاستخدام؛ ويحذف 5.0 الـ API القديم؛ ويستمرّ 4.x في تلقّي الإصلاحات ستة أشهر.</title>\n  <path class=\"d-line\" d=\"M30 90 L650 90\"/>\n  <circle class=\"d-dot\" cx=\"80\" cy=\"90\" r=\"8\"/>\n  <circle class=\"d-dot\" cx=\"300\" cy=\"90\" r=\"8\"/>\n  <circle class=\"d-dot\" cx=\"520\" cy=\"90\" r=\"8\"/>\n  <text class=\"d-label-strong\" x=\"80\" y=\"60\" text-anchor=\"middle\">4.3.0</text>\n  <text class=\"d-label-strong\" x=\"300\" y=\"60\" text-anchor=\"middle\">من 4.4 إلى 4.9</text>\n  <text class=\"d-label-strong\" x=\"520\" y=\"60\" text-anchor=\"middle\">5.0.0</text>\n  <rect class=\"d-box-success\" x=\"20\" y=\"112\" width=\"120\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"80\" y=\"136\" text-anchor=\"middle\">البديل</text>\n  <text class=\"d-label\" x=\"80\" y=\"156\" text-anchor=\"middle\">+ deprecation</text>\n  <rect class=\"d-box-warn\" x=\"230\" y=\"112\" width=\"140\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"300\" y=\"136\" text-anchor=\"middle\">Codemods،</text>\n  <text class=\"d-label\" x=\"300\" y=\"156\" text-anchor=\"middle\">تتبّع الاستخدام</text>\n  <rect class=\"d-box-primary\" x=\"460\" y=\"112\" width=\"120\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"520\" y=\"144\" text-anchor=\"middle\">الحذف</text>\n  <rect class=\"d-box\" x=\"560\" y=\"20\" width=\"110\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label-muted\" x=\"615\" y=\"45\" text-anchor=\"middle\">إصلاحات لـ 4.x</text>\n</svg><figcaption>الـ deprecation تمتدّ عبر إصدارات: بديل، فتحذير، فانتقال، فحذف</figcaption></figure>\n<p>كل عملية حذف في Northwind تتبع اليوم الخطوات نفسها، موزّعة على عدّة إصدارات.</p>\n<p><strong>1. أطلق البديل أولًا.</strong> لا يمكنك أن تطلب من الفرق الانتقال إلى شيء غير موجود. أُطلق الـ prop المسمّى <code dir=\"ltr\">variant</code> في Button ضمن الإصدار 4.3.0 كإضافة عادية، أي إصدار minor.</p>\n<p><strong>2. أعلن الـ deprecation في كل القنوات دفعة واحدة.</strong> في الإصدار نفسه يُعلَّم <code dir=\"ltr\">kind</code> كـ deprecated في كل مكان قد يصادفه فيه الناس:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Button.tsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ButtonProps</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  /** </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@deprecated</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\"> Use `variant`. Removed in 5.0.0. */</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  kind</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'primary'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'secondary'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'danger'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  variant</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'primary'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'secondary'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'danger'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // …rest of the props</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> warned</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> warnOnce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">key</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">message</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (process.env.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">NODE_ENV</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'production'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ||</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> warned.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">has</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(key)) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  warned.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(key);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">warn</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`[Northwind UI] ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">message</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">variant</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">rest</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ButtonProps</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (kind) </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">warnOnce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'button-kind'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Button `kind` is deprecated; use `variant`. Removed in 5.0.0.'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> resolvedVariant</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> variant </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> kind </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'secondary'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // …render as before, using resolvedVariant</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>وسم JSDoc المسمّى <code dir=\"ltr\">@deprecated</code> يجعل المحرّرات تشطب الـ prop أثناء الكتابة. والتحذير يظهر مرة واحدة في كل جلسة، وفي نسخ التطوير وحدها؛ إذ تستبدل الـ bundlers قيمة <code dir=\"ltr\">process.env.NODE_ENV</code> بثابت، فيسقط الفحص والرسالة من كود الإنتاج. وتحصل صفحة التوثيق على شارة «Deprecated»، وتُعاد تسمية خاصية المكوّن في Figma حتى يلاحظ المصمّمون أيضًا.</p>\n<p>والـ tokens تُعامَل بالطريقة نفسها. يبقى الاسم القديم اسمًا مستعارًا للجديد، فلا ينكسر شيء، ويقول ملف الـ tokens ذلك صراحة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JSON</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"color\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"text\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"light\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        \"$type\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"color\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        \"$value\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"{color.text.subtle}\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        \"$deprecated\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Use color.text.subtle. Removed in 5.0.0.\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><strong>3. أتمت الانتقال.</strong> الـ codemod سكربت يعيد كتابة الكود المصدري. ولـ prop أُعيدت تسميته، يكفي transform من بضعة أسطر في jscodeshift:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">codemods/button-kind.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> transformer</span><span style=\"color:#953800;--shiki-dark:#FFA657\">(file, api) </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> j</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> api.jscodeshift;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> j</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(file.source)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">find</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(j.JSXOpeningElement, { name: { name: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Button'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } })</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">find</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(j.JSXAttribute, { name: { name: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'kind'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } })</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">forEach</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">path</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      path.node.name.name </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'variant'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    })</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toSource</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>تشغّله الفرق بالأمر <code dir=\"ltr\">npx jscodeshift --parser=tsx --extensions=tsx,ts -t codemods/button-kind.js src/</code> (فبدون هذين الخيارين لا يقرأ jscodeshift إلا ملفات <code dir=\"ltr\">.js</code> بمحلّل Babel)، ثم تراجع الفروقات. ولأكبر خمسة مستخدمين، يفتح فريق النواة في Northwind pull requests الانتقال بنفسه. إعادة تسمية كانت ستكلّف كل فريق من الأربعين ظهيرة كاملة تصبح أربعين مراجعة سريعة.</p>\n<p>للـ codemods حدود، ويجب أن يسمّيها دليل الانتقال. الـ transform أعلاه يعيد تسمية <code dir=\"ltr\">kind=&quot;danger&quot;</code> و<code dir=\"ltr\">kind={isUrgent ? 'danger' : 'primary'}</code> بشكل صحيح، لأن كليهما سمة مكتوبة على <code dir=\"ltr\">&lt;Button&gt;</code>. لكنه لا يرى <code dir=\"ltr\">kind</code> المختبئ داخل props تُمرَّر بالـ spread، مثل <code dir=\"ltr\">&lt;Button {...actionProps} /&gt;</code> حيث بُني <code dir=\"ltr\">actionProps</code> في ملف آخر، ولا Button أُعيد تصديره باسم آخر. لهذا يبقى تحذير وقت التشغيل مهمًّا حتى بعد تشغيل الـ codemod: فهو يلتقط الحالات التي لا تصل إليها إعادة الكتابة الثابتة. ودليل Northwind لكل عملية حذف له الأجزاء الأربعة نفسها: كود قبل وبعد، وأمر الـ codemod، والحالات المعروفة التي يفوّتها الـ codemod، ورابط إلى موعد الساعات المكتبية حيث يساعد فريق النواة في الباقي.</p>\n<p><strong>4. قِس، ثم احذف.</strong> البحث في الكود عبر المستودعات يخبرك بعدد الاستخدامات المتبقّية؛ ويشرح القسم 5 كيف تجمع Northwind ذلك تلقائيًا. وحين يقارب الاستخدام الصفر، يدخل الحذف في الإصدار الـ major التالي، مجمّعًا مع عمليات الحذف المخطّطة الأخرى، فتواجه الفرق ترقية واحدة بدل عدّة ترقيات.</p>\n<p><strong>5. ادعم الإصدار الـ major السابق.</strong> بعد إطلاق 5.0.0 يظلّ 4.x يتلقّى إصلاحات الأخطاء والأمان ستة أشهر. الفرق التي لا تستطيع الترقية فورًا لا تُترك عالقة، ولا تنسخ النظام.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>deprecation بلا تاريخ</span></p>\n<p>عبارة «مُهمَل، وسيُحذف في إصدار مستقبلي» لا تعطي أحدًا سببًا للتحرّك هذا الربع. كل رسالة deprecation تسمّي البديل والإصدار الذي يحذف القديم. الفرق تخطّط حول التواريخ؛ وتتجاهل «يومًا ما».</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">أعلنه كما تعلن تغييرًا في منتج</h2>\n<p>الـ deprecation خبر لأربعين فريقًا، فعامله كإعلان منتج لا كسطر في الـ changelog. تنشر Northwind كل deprecation في قناة نظام التصميم بثلاث جمل: ما الذي يتغيّر، ولماذا يستحقّ الجهد، ومتى يحدث الحذف. ويرتبط المنشور بدليل الانتقال ويبقى مثبّتًا حتى يُطلَق الـ major. ويتلقّى مديرو الهندسة ملخّصًا شهريًا بعمليات الحذف القادمة التي تمسّ فرقهم، لأنهم هم مَن يجدولون العمل.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">كم من الوقت يكفي؟</h2>\n<p>تُبقي Northwind الـ API المُهمَل لإصدارين minor على الأقل أو ثلاثة أشهر، أيّهما أطول، قبل الـ major الذي يحذفه، وتنشر جدول الإصدارات الـ major قبل ربع سنة. أرقامك قد تختلف؛ المهمّ أن تكون مكتوبة ويمكن توقّعها، حتى تخطّط الفرق لعمليات الانتقال ضمن خرائط طريقها بدل أن تكتشفها في عملية بناء فاشلة.</p>\n<p>في التمرين ترتّب مسار deprecation كاملًا. وبعدها تبني خط الإصدار الذي يلتقط التغييرات الكاسرة غير المقصودة قبل أن تُطلَق.</p>\n","toc":[{"id":"sec-1","text":"مسار الـ deprecation","level":2},{"id":"sec-2","text":"أعلنه كما تعلن تغييرًا في منتج","level":2},{"id":"sec-3","text":"كم من الوقت يكفي؟","level":2}],"takeaways":["لا تعلن deprecation لشيء قبل أن يُطلَق بديله؛ فالـ deprecation بلا بديل مجرّد شكوى.","علّم الـ deprecation في كل مكان ينظر إليه الناس - الأنواع، والـ tokens، وتحذيرات الـ console في بيئة التطوير وحدها، والتوثيق، وFigma - وسمِّ دائمًا البديل وإصدار الحذف.","الـ codemods تحوّل الانتقال من مهمّة لأربعين فريقًا إلى مراجعة لأربعين فريقًا.","لا تحذف العناصر المُهمَلة إلا في إصدار major، بعد أن تقيس أن الاستخدام قارب الصفر، واستمرّ في إصلاح الإصدار الـ major السابق لمدّة معلنة."],"further":[{"title":"jscodeshift (official repository)","url":"https://github.com/facebook/jscodeshift"},{"title":"Design token $deprecated property (DTCG specification source)","url":"https://github.com/design-tokens/community-group/blob/main/technical-reports/format/design-token.md"},{"title":"console: warn() static method (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/API/console/warn_static"}],"quiz":[{"q":"<p>يريد فريق النواة إعلان deprecation لـ ShipmentCard لأن Card المركّب أفضل. لكن الـ slot الخاص بـ Footer في Card لم يُطلَق بعد. ما الذي يجب أن يحدث أولًا؟</p>","options":[{"text":"إطلاق قطع Card، بما فيها الـ slot الخاص بـ Footer، حتى يكون لكل استخدام لـ ShipmentCard بديل يعمل.","why":"صحيح. لا تستطيع الفرق الانتقال إلا إلى شيء موجود ويغطّي حالة استخدامها."},{"text":"إعلان deprecation لـ ShipmentCard الآن حتى تتوقّف الفرق عن إضافة استخدامات جديدة.","why":"الفرق التي تحتاج بطاقة اليوم لا تملك بديلًا، فستتجاهل التحذير أو تبني بطاقتها الخاصة."},{"text":"حذف ShipmentCard في الـ major التالي وترك الفرق تتكيّف.","why":"حذفه بلا بديل يكسر 60 شاشة ولا يترك للفرق شيئًا تنتقل إليه."},{"text":"كتابة دليل الانتقال أولًا، ثم بناء Card لاحقًا.","why":"الدليل الذي يشير إلى مكوّن لم يُطلَق لا يمكن اتّباعه."}],"answer":0},{"q":"<p>أين يجب أن يظهر تحذير «Button <code dir=\"ltr\">kind</code> is deprecated» وقت التشغيل؟</p>","options":[{"text":"في الإنتاج كتنبيه منبثق، حتى يلاحظه مديرو المنتجات.","why":"المستخدمون النهائيون سيرون رسالة عن كود لا يستطيعون تغييره."},{"text":"في لا مكان؛ الـ changelog يكفي.","why":"أغلب المهندسين لا يقرؤون كل changelog؛ أما التحذير في الـ console الخاص بهم أثناء التطوير فيصلهم حيث يعملون."},{"text":"في كل render، وفي كل بيئة.","why":"التسجيل في كل render يُغرق الـ console ويخفي الأخطاء الحقيقية؛ ومستخدمو الإنتاج لا يحتاجونه أيضًا."},{"text":"في الـ console أثناء التطوير، مرة واحدة في كل جلسة، مع ذكر البديل وإصدار الحذف.","why":"صحيح. يصل إلى المطوّرين وهم يعملون على الكود، دون ضجيج في الإنتاج."}],"answer":3},{"q":"<p>يُظهر البحث في الكود لدى Northwind 4 استخدامات متبقّية للـ prop المُهمَل <code dir=\"ltr\">kind</code> عبر 40 مستودعًا، كلها في أداة إدارة قديمة لفريق واحد. إصدار 5.0 الأسبوع القادم. ما أفضل خطوة؟</p>","options":[{"text":"تأجيل 5.0 حتى تخلو المستودعات الأربعون من كل API مُهمَل.","why":"احتجاز إصدار major رهينةً لأداة قديمة واحدة يؤخّر تحسينات كل الفرق الأخرى."},{"text":"إطلاق 5.0، وفتح pull request لذلك الفريق يشغّل الـ codemod، مع إخبارهم أنهم يستطيعون البقاء على 4.x، الذي ما زال يتلقّى الإصلاحات، حتى يُدمج.","why":"صحيح. العمل المتبقّي صغير، وأنت تساعد مباشرة، وفترة الدعم تحميهم في الأثناء."},{"text":"إطلاق 5.0 دون إخبار الفريق.","why":"سينكسر التحديث التالي لاعتمادياتهم دون إنذار، وهذا يكلّف النظام ثقته."}],"answer":1}],"exercise":{"kind":"order","items":["replacement","deprecate","codemod","measure","remove","support"],"title":"خطّط لإعادة تسمية الـ token من أولها إلى آخرها","prompt":"<p>تستبدل Northwind الـ token المسمّى <code dir=\"ltr\">--nw-color-text-light</code> بـ <code dir=\"ltr\">--nw-color-text-subtle</code>. هناك نحو 300 استخدام عبر 40 مستودعًا، والإصدار الحالي 4.6.0. رتّب خطوات مسار الـ deprecation.</p>\n","hints":["لا يستطيع الناس الانتقال إلا إلى شيء موجود بالفعل.","الحذف تغيير كاسر، فينتظر إصدارًا major وبيانات الاستخدام.","خطوة واحدة تحدث بعد إطلاق الحذف."],"explanation":"<p>يُطلَق الـ token الجديد أولًا (إصدار minor)، فيكون هناك شيء ينتقل إليه الناس. وفي الإصدار نفسه يصبح الاسم القديم اسمًا مستعارًا معلَّمًا بـ <code dir=\"ltr\">$deprecated</code>، ويسمّي التوثيق والـ changelog البديل وإصدار الحذف. وتنجز الـ codemods وpull requests الانتقال معظم العمل، ويُظهر تتبّع الاستخدام متى قاربت الاستخدامات المتبقّية الصفر، وعندها فقط يحذف الـ major التالي الاسم المستعار. واستمرار إصلاح 4.x لفترة معلنة بعد ذلك يحمي الفرق التي تحتاج وقتًا أطول.</p>\n","checkLabels":{},"itemText":{"replacement":"إطلاق <code dir=\"ltr\">--nw-color-text-subtle</code> في 4.7.0","deprecate":"إبقاء <code dir=\"ltr\">--nw-color-text-light</code> اسمًا مستعارًا معلَّمًا بـ <code dir=\"ltr\">$deprecated</code>، وإعلان إصدار الحذف في التوثيق والـ changelog","codemod":"نشر codemod وفتح pull requests الانتقال لأكبر المستخدمين","measure":"تتبّع الاستخدامات المتبقّية عبر المستودعات حتى تقارب الصفر","remove":"حذف الـ token القديم في 5.0.0، مجمّعًا مع عمليات الحذف المخطّطة الأخرى","support":"الاستمرار في إطلاق إصلاحات الأخطاء على 4.x لستة أشهر"}},"words":646,"readMinutes":4}