تخطى إلى المحتوى
·18 دقيقة قراءة

Tailwind CSS v4: ما الذي تغيّر فعلاً وهل يجب الترحيل

الإعداد بأسلوب CSS أولاً، وتكامل @layer، واستعلامات الحاوية المدمجة، وأداء المحرك الجديد، والتغييرات الجذرية، وتجربتي الصادقة في الترحيل من v3 إلى v4.

مشاركة:X / TwitterLinkedIn

أستخدم Tailwind CSS منذ الإصدار v1.x، حين كان نصف المجتمع يرى أنها كارثة والنصف الآخر لا يستطيع التوقف عن الشحن بها. كل إصدار رئيسي كان قفزة كبيرة، لكن v4 مختلف. ليس مجرد إصدار ميزات جديدة. إنه إعادة كتابة معمارية من الصفر تغيّر العقد الأساسي بينك وبين الإطار.

بعد ترحيل مشروعين في الإنتاج من v3 إلى v4 وبدء ثلاثة مشاريع جديدة على v4 من الصفر، أملك صورة واضحة عمّا هو أفضل حقاً، وما هو خام، وهل يجب أن ترحّل اليوم. لا مبالغة، لا غضب — فقط ما لاحظته.

الصورة الكبيرة: ما هو v4 فعلاً#

Tailwind CSS v4 هو ثلاثة أشياء في آنٍ واحد:

  1. محرك جديد — أُعيد كتابته من JavaScript إلى Rust (محرك Oxide)، مما يجعل عمليات البناء أسرع بشكل دراماتيكي
  2. نموذج إعداد جديد — الإعداد بأسلوب CSS أولاً يحل محل tailwind.config.js كخيار افتراضي
  3. تكامل أقوى مع منصة CSS@layer الأصلية، واستعلامات الحاوية، و@starting-style، وطبقات التتالي أصبحت مواطنين من الدرجة الأولى

العنوان الذي ستراه في كل مكان هو "أسرع 10 مرات." هذا حقيقي، لكنه يُقلّل من شأن التغيير الفعلي. النموذج الذهني لإعداد وتوسيع Tailwind تغيّر جذرياً. أنت تعمل مع CSS الآن، وليس مع كائن إعداد JavaScript يولّد CSS.

إليك كيف يبدو إعداد Tailwind v4 الأدنى:

css
/* app.css — هذا هو الإعداد الكامل */
@import "tailwindcss";

هذا كل شيء. لا ملف إعداد. لا إعداد إضافة PostCSS (لمعظم الحالات). لا توجيهات @tailwind base; @tailwind components; @tailwind utilities;. استيراد واحد، وأنت تعمل.

قارن ذلك بـ v3:

css
/* v3 — app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
js
// v3 — tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};
js
// v3 — postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

ثلاثة ملفات اختُزلت إلى سطر واحد. هذا ليس مجرد نموذج أقل — إنه مساحة أقل لسوء الإعداد. في v4، اكتشاف المحتوى تلقائي. يفحص ملفات مشروعك دون الحاجة لكتابة أنماط glob.

الإعداد بأسلوب CSS أولاً مع @theme#

هذا هو أكبر تحوّل مفاهيمي. في v3، كنت تخصّص Tailwind من خلال كائن إعداد JavaScript:

js
// v3 — tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: "#eff6ff",
          500: "#3b82f6",
          900: "#1e3a5f",
        },
      },
      fontFamily: {
        display: ["Inter Variable", "sans-serif"],
      },
      spacing: {
        18: "4.5rem",
        112: "28rem",
      },
      borderRadius: {
        "4xl": "2rem",
      },
    },
  },
};

في v4، كل هذا يعيش في CSS باستخدام توجيه @theme:

css
@import "tailwindcss";
 
@theme {
  --color-brand-50: #eff6ff;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a5f;
 
  --font-display: "Inter Variable", sans-serif;
 
  --spacing-18: 4.5rem;
  --spacing-112: 28rem;
 
  --radius-4xl: 2rem;
}

في البداية، قاومت هذا. أحببت وجود كائن JavaScript واحد حيث يمكنني رؤية نظام التصميم بالكامل. لكن بعد أسبوع مع نهج CSS، غيّرت رأيي لثلاثة أسباب:

1. خصائص CSS المخصصة الأصلية تُكشف تلقائياً. كل قيمة تعرّفها في @theme تصبح خاصية CSS مخصصة على :root. هذا يعني أن قيم السمة متاحة في CSS العادي، وفي CSS Modules، وفي وسوم <style>، وفي أي مكان يعمل فيه CSS:

css
/* تحصل على هذا مجاناً */
:root {
  --color-brand-50: #eff6ff;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a5f;
}
css
/* استخدمها في أي مكان — لا حاجة لـ Tailwind */
.custom-element {
  border: 2px solid var(--color-brand-500);
}

2. يمكنك استخدام ميزات CSS داخل @theme. استعلامات الوسائط، وlight-dark()، وcalc() — CSS الحقيقي يعمل هنا لأنه CSS حقيقي:

css
@theme {
  --color-surface: light-dark(#ffffff, #0a0a0a);
  --color-text: light-dark(#0a0a0a, #fafafa);
  --spacing-container: calc(100vw - 2rem);
}

3. التجاور مع CSS الآخر الخاص بك. السمة، والأدوات المساعدة المخصصة، وأنماط الأساس كلها تعيش في نفس اللغة، ونفس الملف إن أردت. لا يوجد تبديل سياق بين "عالم CSS" و"عالم إعداد JavaScript."

تجاوز مقابل توسيع السمة الافتراضية#

في v3 كان لديك theme (استبدال) مقابل theme.extend (دمج). في v4، النموذج الذهني مختلف:

css
@import "tailwindcss";
 
/* هذا يُوسّع السمة الافتراضية — يضيف ألوان العلامة التجارية بجانب الموجودة */
@theme {
  --color-brand-500: #3b82f6;
}

إذا أردت استبدال مساحة اسم بالكامل (مثل إزالة جميع الألوان الافتراضية)، تستخدم @theme مع إعادة تعيين البدل --color-*:

css
@import "tailwindcss";
 
@theme {
  /* مسح جميع الألوان الافتراضية أولاً */
  --color-*: initial;
 
  /* الآن حدّد ألوانك فقط */
  --color-white: #ffffff;
  --color-black: #000000;
  --color-brand-50: #eff6ff;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a5f;
}

نمط إعادة تعيين البدل هذا أنيق. تختار بالضبط أي أجزاء من السمة الافتراضية تحتفظ بها وأيها تستبدل. تريد كل التباعد الافتراضي لكن ألواناً مخصصة؟ أعد تعيين --color-*: initial; واترك التباعد كما هو.

ملفات سمات متعددة#

للمشاريع الأكبر، يمكنك تقسيم السمة عبر ملفات:

css
/* styles/theme/colors.css */
@theme {
  --color-brand-50: #eff6ff;
  --color-brand-100: #dbeafe;
  --color-brand-200: #bfdbfe;
  --color-brand-300: #93c5fd;
  --color-brand-400: #60a5fa;
  --color-brand-500: #3b82f6;
  --color-brand-600: #2563eb;
  --color-brand-700: #1d4ed8;
  --color-brand-800: #1e40af;
  --color-brand-900: #1e3a5f;
  --color-brand-950: #172554;
}
 
/* styles/theme/typography.css */
@theme {
  --font-display: "Inter Variable", sans-serif;
  --font-body: "Source Sans 3 Variable", sans-serif;
  --font-mono: "JetBrains Mono Variable", monospace;
 
  --text-display: 3.5rem;
  --text-display--line-height: 1.1;
  --text-display--letter-spacing: -0.02em;
}
css
/* app.css */
@import "tailwindcss";
@import "./theme/colors.css";
@import "./theme/typography.css";

هذا أنظف بكثير من نمط v3 في وجود tailwind.config.js ضخم أو محاولة تقسيمه باستخدام require().

محرك Oxide: أسرع 10 مرات فعلاً#

محرك Tailwind v4 هو إعادة كتابة كاملة بلغة Rust. يسمونه Oxide. كنت متشككاً في ادعاء "أسرع 10 مرات" — أرقام التسويق نادراً ما تصمد أمام المشاريع الحقيقية. لذا قمت بقياس الأداء.

مشروع الاختبار: تطبيق Next.js يحتوي على 847 مكوّناً، و142 صفحة، وحوالي 23,000 استخدام لفئات Tailwind.

المقياسv3 (Node)v4 (Oxide)التحسن
البناء الأولي4,280ms387ms11x
تدريجي (تعديل ملف واحد)340ms18ms19x
إعادة بناء كاملة (نظيفة)5,100ms510ms10x
بدء خادم التطوير3,200ms290ms11x

ادعاء "10x" متحفظ لمشروعي. البناء التدريجي هو حيث يتألق حقاً — 18ms يعني أنه فوري بشكل أساسي. تحفظ ملفاً والمتصفح يحصل على الأنماط الجديدة قبل أن تتمكن من تبديل التبويبات.

لماذا هو أسرع بكثير؟#

ثلاثة أسباب:

1. Rust بدلاً من JavaScript. محلل CSS الأساسي، واكتشاف الفئات، وتوليد الكود كلها Rust أصلي. هذه ليست حالة "لنعيد الكتابة بـ Rust للمتعة" — تحليل CSS هو حقاً عمل مكثف للمعالج حيث الكود الأصلي يتفوق بشكل كبير على V8.

2. لا PostCSS في المسار الساخن. في v3، كان Tailwind إضافة PostCSS. كل بناء كان يعني: تحليل CSS إلى AST الخاص بـ PostCSS، وتشغيل إضافة Tailwind، وتسلسل العودة إلى سلسلة CSS، ثم تشغيل إضافات PostCSS الأخرى. في v4، لدى Tailwind محلل CSS خاص به يذهب مباشرة من المصدر إلى المخرج. لا يزال PostCSS مدعوماً للتوافق، لكن المسار الأساسي يتخطاه تماماً.

3. معالجة تدريجية أذكى. المحرك الجديد يخزّن بشكل مكثف. عندما تعدّل ملفاً واحداً، يعيد فقط فحص ذلك الملف لأسماء الفئات ويعيد توليد قواعد CSS التي تغيرت فقط. محرك v3 كان أذكى مما ينسبه له الناس (وضع JIT كان بالفعل تدريجياً)، لكن v4 يأخذ ذلك أبعد بكثير مع تتبع التبعيات الدقيق.

هل السرعة مهمة فعلاً؟#

نعم، لكن ليس للسبب الذي تتوقعه. لمعظم المشاريع، سرعة بناء v3 كانت "مقبولة." كنت تنتظر بضع مئات من الملي ثانية في التطوير. ليس مؤلماً.

سرعة v4 مهمة لأنها تجعل Tailwind غير مرئي في سلسلة أدواتك. عندما يكون البناء أقل من 20ms، تتوقف عن التفكير في Tailwind كخطوة بناء على الإطلاق. يصبح مثل تلوين البنية — موجود دائماً، لا يعيق أبداً. هذا الفرق النفسي مهم على مدى يوم كامل من التطوير.

تكامل @layer الأصلي#

في v3، استخدم Tailwind نظام طبقات خاصاً به مع @layer base، و@layer components، و@layer utilities. هذه بدت مثل طبقات تتالي CSS لكنها لم تكن كذلك — كانت توجيهات خاصة بـ Tailwind تتحكم في مكان ظهور CSS المولّد في المخرج.

في v4، يستخدم Tailwind طبقات تتالي CSS الفعلية:

css
/* مخرج v4 — مبسّط */
@layer theme, base, components, utilities;
 
@layer base {
  /* إعادة تعيين، preflight */
}
 
@layer components {
  /* فئات المكونات */
}
 
@layer utilities {
  /* جميع فئات الأدوات المساعدة المولّدة */
}

هذا تغيير مهم لأن طبقات تتالي CSS لها تأثيرات حقيقية على الخصوصية. قاعدة في طبقة ذات أولوية أقل تخسر دائماً أمام قاعدة في طبقة ذات أولوية أعلى، بغض النظر عن خصوصية المحدد. هذا يعني:

css
@layer components {
  /* الخصوصية: 0-1-0 */
  .card { padding: 1rem; }
}
 
@layer utilities {
  /* الخصوصية: 0-1-0 — نفس الخصوصية لكنها تفوز لأن طبقة utilities تأتي لاحقاً */
  .p-4 { padding: 1rem; }
}

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

إضافة أدوات مساعدة مخصصة#

في v3، كنت تعرّف أدوات مساعدة مخصصة باستخدام واجهة برمجة الإضافات أو @layer utilities:

js
// v3 — نهج الإضافة
const plugin = require("tailwindcss/plugin");
 
module.exports = {
  plugins: [
    plugin(function ({ addUtilities }) {
      addUtilities({
        ".text-balance": {
          "text-wrap": "balance",
        },
        ".text-pretty": {
          "text-wrap": "pretty",
        },
      });
    }),
  ],
};

في v4، الأدوات المساعدة المخصصة تُعرّف بتوجيه @utility:

css
@import "tailwindcss";
 
@utility text-balance {
  text-wrap: balance;
}
 
@utility text-pretty {
  text-wrap: pretty;
}

توجيه @utility يخبر Tailwind "هذه فئة أداة مساعدة — ضعها في طبقة الأدوات واسمح باستخدامها مع المتغيرات." الجزء الأخير هو المفتاح. أداة مساعدة معرّفة بـ @utility تعمل تلقائياً مع hover:، وfocus:، وmd:، وكل متغير آخر:

html
<p class="text-pretty md:text-balance">...</p>

متغيرات مخصصة#

يمكنك أيضاً تعريف متغيرات مخصصة بـ @variant:

css
@import "tailwindcss";
 
@variant hocus (&:hover, &:focus);
@variant theme-dark (.dark &);
html
<button class="hocus:bg-brand-500 theme-dark:text-white">
  اضغط هنا
</button>

هذا يحل محل واجهة addVariant الخاصة بالإضافات في v3 لمعظم حالات الاستخدام. إنه أقل قوة (لا يمكنك توليد متغيرات برمجياً)، لكنه يغطي 90% مما يفعله الناس فعلاً.

استعلامات الحاوية: مدمجة، بدون إضافة#

استعلامات الحاوية كانت من أكثر الميزات المطلوبة في v3. كان يمكنك الحصول عليها مع إضافة @tailwindcss/container-queries، لكنها كانت إضافة. في v4، مدمجة في الإطار.

الاستخدام الأساسي#

حدّد حاوية بـ @container واستعلم عن حجمها بالبادئة @:

html
<!-- حدّد الأب كحاوية -->
<div class="@container">
  <!-- متجاوب مع عرض الأب، ليس مساحة العرض -->
  <div class="flex flex-col @md:flex-row @lg:grid @lg:grid-cols-3">
    <div class="p-4">بطاقة 1</div>
    <div class="p-4">بطاقة 2</div>
    <div class="p-4">بطاقة 3</div>
  </div>
</div>

متغيرات @md، و@lg، إلخ تعمل مثل نقاط التوقف المتجاوبة لكنها نسبية لأقرب سلف @container بدلاً من مساحة العرض. قيم نقاط التوقف تتوافق مع نقاط التوقف الافتراضية لـ Tailwind:

المتغيرالحد الأدنى للعرض
@sm24rem (384px)
@md28rem (448px)
@lg32rem (512px)
@xl36rem (576px)
@2xl42rem (672px)

الحاويات المسماة#

يمكنك تسمية الحاويات للاستعلام عن أسلاف محددة:

html
<div class="@container/sidebar">
  <div class="@container/card">
    <!-- يستعلم عن حاوية البطاقة -->
    <div class="@md/card:text-lg">...</div>
 
    <!-- يستعلم عن حاوية الشريط الجانبي -->
    <div class="@lg/sidebar:hidden">...</div>
  </div>
</div>

لماذا هذا مهم#

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

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

html
<!-- هذا المكوّن يتكيف مع أي حاوية يوضع فيها -->
<article class="@container">
  <div class="grid grid-cols-1 @md:grid-cols-[200px_1fr] gap-4">
    <img
      class="w-full @md:w-auto rounded-lg aspect-video @md:aspect-square object-cover"
      src="/post-image.jpg"
      alt=""
    />
    <div>
      <h2 class="text-lg @lg:text-xl font-semibold">عنوان المقال</h2>
      <p class="mt-2 text-sm @md:text-base text-gray-600">
        مقتطف المقال يأتي هنا...
      </p>
      <div class="mt-4 hidden @md:flex gap-2">
        <span class="text-xs bg-gray-100 px-2 py-1 rounded">وسم</span>
      </div>
    </div>
  </div>
</article>

متغيرات جديدة مهمة فعلاً#

يضيف v4 عدة متغيرات جديدة كنت ألجأ إليها باستمرار. تملأ فجوات حقيقية.

متغير starting:#

يتوافق مع @starting-style في CSS، والذي يتيح لك تعريف الحالة الأولية لعنصر عند ظهوره لأول مرة. هذه القطعة المفقودة لتحريك دخول العنصر بدون JavaScript:

html
<dialog class="opacity-0 starting:opacity-0 open:opacity-100 transition-opacity duration-300">
  <p>هذا الحوار يتلاشى عند فتحه</p>
</dialog>

متغير starting: يولّد CSS داخل كتلة @starting-style:

css
/* ما يولّده Tailwind */
@starting-style {
  dialog[open] {
    opacity: 0;
  }
}
 
dialog[open] {
  opacity: 1;
  transition: opacity 300ms;
}

هذا ضخم للحوارات، والنوافذ المنبثقة، وقوائم الانسدال — أي شيء يحتاج حركة دخول. قبل هذا، كنت تحتاج JavaScript لإضافة فئة في الإطار التالي، أو كنت تستخدم @keyframes. الآن هي فئة أداة مساعدة.

متغير not-*#

النفي. شيء أردناه للأبد:

html
<!-- كل عنصر فرعي ما عدا الأخير يحصل على حد -->
<div class="divide-y">
  <div class="not-last:pb-4">عنصر 1</div>
  <div class="not-last:pb-4">عنصر 2</div>
  <div class="not-last:pb-4">عنصر 3</div>
</div>
 
<!-- تنسيق كل شيء ليس معطلاً -->
<input class="not-disabled:hover:border-brand-500" />
 
<!-- نفي سمات البيانات -->
<div class="not-data-active:opacity-50">...</div>

متغيرات nth-*#

وصول مباشر لـ nth-child وnth-of-type:

html
<ul>
  <li class="nth-1:font-bold">العنصر الأول — عريض</li>
  <li class="nth-even:bg-gray-50">الصفوف الزوجية — خلفية رمادية</li>
  <li class="nth-odd:bg-white">الصفوف الفردية — خلفية بيضاء</li>
  <li class="nth-[3n+1]:text-brand-500">كل ثالث+1 — لون العلامة التجارية</li>
</ul>

بنية الأقواس (nth-[3n+1]) تدعم أي تعبير nth-child صالح. هذا يحل محل الكثير من CSS المخصص الذي كنت أكتبه لتخطيط الجداول وأنماط الشبكة.

متغير in-* (حالة الأب)#

هذا هو عكس group-*. بدلاً من "عندما يكون أبي (المجموعة) في حالة تمرير، نسّقني،" هو "عندما أكون داخل أب يطابق هذه الحالة، نسّقني":

html
<div class="in-data-active:bg-brand-50">
  هذا يحصل على خلفية عندما يكون لأي سلف data-active
</div>

متغير **: العميق الشامل#

تنسيق جميع الأحفاد، ليس فقط الأبناء المباشرين. هذه قوة مسيطر عليها — استخدمها باعتدال، لكنها لا تُقدّر بثمن لمحتوى النثر ومخرجات CMS:

html
<!-- جميع الفقرات داخل هذا div، على أي عمق -->
<div class="**:data-highlight:bg-yellow-100">
  <section>
    <p data-highlight>هذا يُميّز</p>
    <div>
      <p data-highlight>وهذا أيضاً، متداخل أعمق</p>
    </div>
  </section>
</div>

التغييرات الجذرية: ما الذي تعطّل فعلاً#

دعني أكون صريحاً. إذا كان لديك مشروع v3 كبير، الترحيل ليس تافهاً. إليك ما تعطّل في مشاريعي:

1. صيغة الإعداد#

ملف tailwind.config.js لا يعمل مباشرة. تحتاج إما:

  • تحويله إلى CSS باستخدام @theme (موصى به للمعمارية الجديدة)
  • استخدام طبقة التوافق بتوجيه @config (مسار ترحيل سريع)
css
/* ترحيل سريع — احتفظ بإعدادك القديم */
@import "tailwindcss";
@config "../../tailwind.config.js";

جسر @config هذا يعمل، لكنه صريحاً أداة ترحيل. التوصية هي الانتقال إلى @theme مع الوقت.

2. إزالة الأدوات المهملة#

بعض الأدوات المساعدة التي كانت مهملة في v3 اختفت:

/* أُزيلت في v4 */
bg-opacity-*     → استخدم bg-black/50 (بنية الشرطة المائلة للشفافية)
text-opacity-*   → استخدم text-black/50
border-opacity-* → استخدم border-black/50
flex-shrink-*    → استخدم shrink-*
flex-grow-*      → استخدم grow-*
overflow-ellipsis → استخدم text-ellipsis
decoration-slice  → استخدم box-decoration-slice
decoration-clone  → استخدم box-decoration-clone

إذا كنت تستخدم البنية الحديثة بالفعل في v3 (شفافية الشرطة المائلة، shrink-*)، فأنت بخير. وإلا، هذه تغييرات بحث واستبدال مباشرة.

3. تغييرات لوحة الألوان الافتراضية#

لوحة الألوان الافتراضية تحوّلت قليلاً. إذا كنت تعتمد على قيم ألوان دقيقة من v3 (ليس بالاسم بل بقيمة hex الفعلية)، قد تلاحظ اختلافات بصرية. الألوان المسماة (blue-500، gray-200) لا تزال موجودة لكن بعض قيم hex تغيرت.

4. اكتشاف المحتوى#

v3 تطلّب إعداد content صريحاً:

js
// v3
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
};

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

css
@import "tailwindcss";
@source "../shared-components/**/*.tsx";
@source "../design-system/src/**/*.tsx";

5. تغييرات واجهة الإضافات#

إذا كتبت إضافات مخصصة، تغيرت الواجهة. دوال addUtilities، وaddComponents، وaddBase، وaddVariant لا تزال تعمل عبر طبقة التوافق، لكن النهج الاصطلاحي في v4 هو CSS الأصلي:

js
// إضافة v3
plugin(function ({ addUtilities, theme }) {
  addUtilities({
    ".scrollbar-hide": {
      "-ms-overflow-style": "none",
      "scrollbar-width": "none",
      "&::-webkit-scrollbar": {
        display: "none",
      },
    },
  });
});
css
/* v4 — مجرد CSS */
@utility scrollbar-hide {
  -ms-overflow-style: none;
  scrollbar-width: none;
  &::-webkit-scrollbar {
    display: none;
  }
}

معظم الإضافات الرسمية (@tailwindcss/typography، و@tailwindcss/forms، و@tailwindcss/aspect-ratio) لديها إصدارات متوافقة مع v4. الإضافات الخارجية متفاوتة — تحقق من مستودعها قبل الترحيل.

6. JIT هو الوضع الوحيد#

في v3، كان يمكنك الخروج من وضع JIT (رغم أن لا أحد تقريباً فعل ذلك). في v4، لا يوجد وضع غير JIT. كل شيء يُولّد عند الطلب، دائماً. إذا كان لديك سبب لاستخدام محرك AOT (المسبق) القديم، هذا المسار ذهب.

7. بعض تغييرات بنية المتغيرات#

بعض المتغيرات أُعيد تسميتها أو تغيّر سلوكها:

html
<!-- v3 -->
<div class="[&>*]:p-4">...</div>
 
<!-- v4 — جزء >* يستخدم الآن بنية المتغير الداخلي -->
<div class="*:p-4">...</div>

بنية المتغير التعسفي [&...] لا تزال تعمل، لكن v4 يوفر بدائل مسماة للأنماط الشائعة.

دليل الترحيل: العملية الحقيقية#

إليك كيف رحّلت فعلاً، ليس المسار السعيد من التوثيق بل كيف بدت العملية حقاً.

الخطوة 1: تشغيل الأداة الرسمية#

يوفر Tailwind أداة تحويل تلقائي تتعامل مع معظم التغييرات الميكانيكية:

bash
npx @tailwindcss/upgrade

هذا يفعل الكثير تلقائياً:

  • يحوّل توجيهات @tailwind إلى @import "tailwindcss"
  • يعيد تسمية فئات الأدوات المهملة
  • يحدّث بنية المتغيرات
  • يحوّل أدوات الشفافية إلى بنية الشرطة المائلة (bg-opacity-50 إلى bg-black/50)
  • ينشئ كتلة @theme أساسية من إعدادك

ما تتعامل معه الأداة جيداً#

  • إعادة تسمية فئات الأدوات (شبه مثالي)
  • تغييرات بنية التوجيهات
  • قيم السمة البسيطة (الألوان، التباعد، الخطوط)
  • ترحيل بنية الشفافية

ما لا تتعامل معه الأداة#

  • تحويلات الإضافات المعقدة
  • قيم الإعداد الديناميكية (استدعاءات theme() في JavaScript)
  • إعداد السمة المشروط (مثل قيم السمة المبنية على البيئة)
  • ترحيل واجهة الإضافات المخصصة
  • حالات حافة القيم التعسفية حيث يفسرها المحلل الجديد بشكل مختلف
  • أسماء الفئات المُنشأة ديناميكياً في JavaScript (القوالب النصية، تسلسل السلاسل)

الخطوة 2: إصلاح إعداد PostCSS#

لمعظم الحالات، ستحدّث إعداد PostCSS:

js
// postcss.config.js — v4
module.exports = {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

ملاحظة: اسم الإضافة تغيّر من tailwindcss إلى @tailwindcss/postcss. إذا كنت تستخدم Vite، يمكنك تخطي PostCSS تماماً واستخدام إضافة Vite:

js
// vite.config.ts
import tailwindcss from "@tailwindcss/vite";
 
export default defineConfig({
  plugins: [tailwindcss()],
});

الخطوة 3: تحويل إعداد السمة#

هذا هو الجزء اليدوي. خذ قيم سمة tailwind.config.js وحوّلها إلى @theme:

js
// إعداد v3 — قبل
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          light: "#60a5fa",
          DEFAULT: "#3b82f6",
          dark: "#1d4ed8",
        },
      },
      fontSize: {
        "2xs": ["0.65rem", { lineHeight: "1rem" }],
      },
      animation: {
        "fade-in": "fade-in 0.5s ease-out",
      },
      keyframes: {
        "fade-in": {
          "0%": { opacity: "0" },
          "100%": { opacity: "1" },
        },
      },
    },
  },
};
css
/* CSS الخاص بـ v4 — بعد */
@import "tailwindcss";
 
@theme {
  --color-brand-light: #60a5fa;
  --color-brand: #3b82f6;
  --color-brand-dark: #1d4ed8;
 
  --text-2xs: 0.65rem;
  --text-2xs--line-height: 1rem;
 
  --animate-fade-in: fade-in 0.5s ease-out;
}
 
@keyframes fade-in {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

لاحظ أن الإطارات المفتاحية تنتقل خارج @theme وتصبح @keyframes عادية في CSS. اسم الحركة في @theme يشير إليها فقط. هذا أنظف — الإطارات المفتاحية هي CSS، يجب أن تُكتب كـ CSS.

الخطوة 4: اختبار الانحدار البصري#

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

  • بضعة أماكن حيث ترحيل بنية الشفافية أنتج نتائج مختلفة قليلاً
  • مخرجات الإضافات المخصصة التي لم تُنقل
  • تغييرات تكديس z-index بسبب ترتيب الطبقات
  • بعض تجاوزات !important التي تصرفت بشكل مختلف مع طبقات التتالي

الخطوة 5: تحديث التبعيات الخارجية#

تحقق من كل حزمة متعلقة بـ Tailwind:

json
{
  "@tailwindcss/typography": "^1.0.0",
  "@tailwindcss/forms": "^1.0.0",
  "@tailwindcss/container-queries": "أزل — مدمجة الآن",
  "tailwindcss-animate": "تحقق من دعم v4",
  "prettier-plugin-tailwindcss": "حدّث لأحدث إصدار"
}

إضافة @tailwindcss/container-queries لم تعد مطلوبة — استعلامات الحاوية مدمجة. الإضافات الأخرى تحتاج إصداراتها المتوافقة مع v4.

العمل مع Next.js#

بما أنني أستخدم Next.js لمعظم المشاريع، إليك الإعداد المحدد.

نهج PostCSS (موصى به لـ Next.js)#

Next.js يستخدم PostCSS تحت الغطاء، لذا إضافة PostCSS هي الملائمة الطبيعية:

bash
npm install tailwindcss @tailwindcss/postcss
js
// postcss.config.mjs
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};
css
/* app/globals.css */
@import "tailwindcss";
 
@theme {
  --font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "JetBrains Mono Variable", ui-monospace, monospace;
}

هذا هو الإعداد الكامل. لا tailwind.config.js، لا autoprefixer (v4 يتعامل مع بادئات المتصفحات داخلياً).

ترتيب استيراد CSS#

شيء أوقعني: ترتيب استيراد CSS أهم في v4 بسبب طبقات التتالي. يجب أن يأتي @import "tailwindcss" قبل أنماطك المخصصة:

css
/* الترتيب الصحيح */
@import "tailwindcss";
@import "./theme.css";
@import "./custom-utilities.css";
 
/* @theme و @utility المضمنة، إلخ */

إذا استوردت CSS مخصصاً قبل Tailwind، قد ينتهي تنسيقك في طبقة تتالي أقل ويُتجاوز بشكل غير متوقع.

الوضع المظلم#

الوضع المظلم يعمل بنفس الطريقة مفاهيمياً لكن الإعداد انتقل إلى CSS:

css
@import "tailwindcss";
 
/* استخدم الوضع المظلم المبني على الفئة (الافتراضي مبني على الوسائط) */
@variant dark (&:where(.dark, .dark *));

هذا يحل محل إعداد v3:

js
// v3
module.exports = {
  darkMode: "class",
};

نهج @variant أكثر مرونة. يمكنك تعريف الوضع المظلم كما تريد — مبني على الفئة، أو سمة البيانات، أو استعلام الوسائط:

css
/* نهج سمة البيانات */
@variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
 
/* استعلام الوسائط — هذا الافتراضي، لذا لا تحتاج للإعلان عنه */
@variant dark (@media (prefers-color-scheme: dark));

توافق Turbopack#

إذا كنت تستخدم Next.js مع Turbopack (وهو الآن المجمّع الافتراضي للتطوير)، v4 يعمل بشكل رائع. محرك Rust يتناغم جيداً مع معمارية Turbopack المبنية على Rust أيضاً. قست أوقات بدء التطوير:

الإعدادv3 + Webpackv3 + Turbopackv4 + Turbopack
البدء البارد4.8s2.1s1.3s
HMR (تغيير CSS)450ms180ms40ms

الـ 40ms لـ HMR لتغييرات CSS بالكاد ملحوظة. تبدو فورية.

تعمق في الأداء: أبعد من سرعة البناء#

فوائد محرك Oxide تتجاوز سرعة البناء الخام.

استخدام الذاكرة#

v4 يستخدم ذاكرة أقل بكثير. على مشروعي ذي 847 مكوّناً:

المقياسv3v4
ذروة الذاكرة (البناء)380MB45MB
الحالة المستقرة (التطوير)210MB28MB

هذا مهم لخطوط CI/CD حيث الذاكرة محدودة، ولآلات التطوير التي تشغّل عشر عمليات في وقت واحد.

حجم مخرجات CSS#

v4 يولّد مخرجات CSS أصغر قليلاً لأن المحرك الجديد أفضل في إزالة التكرار والكود الميت:

مخرج v3: 34.2 KB (مضغوط gzip)
مخرج v4: 29.8 KB (مضغوط gzip)

تخفيض 13% بدون تغيير أي كود. ليس تحويلياً، لكنه أداء مجاني.

إزالة قيم السمة غير المستخدمة#

في v4، إذا عرّفت قيمة سمة لكن لم تستخدمها في قوالبك، خاصية CSS المخصصة المقابلة تُصدر (في @theme، التي تتوافق مع متغيرات :root). ومع ذلك، فئات الأدوات للقيم غير المستخدمة لا تُولّد. هذا نفس سلوك JIT في v3 لكن يستحق الملاحظة: خصائص CSS المخصصة متاحة دائماً، حتى للقيم بدون استخدام أدوات.

إذا أردت منع قيم سمة معينة من توليد خصائص CSS مخصصة، يمكنك استخدام @theme inline:

css
@theme inline {
  /* هذه القيم تولّد أدوات لكن ليس خصائص CSS مخصصة */
  --color-internal-debug: #ff00ff;
  --spacing-magic-number: 3.7rem;
}

هذا مفيد لرموز التصميم الداخلية التي لا تريد كشفها كمتغيرات CSS.

متقدم: تركيب السمات للعلامات التجارية المتعددة#

نمط واحد يجعله v4 أسهل بكثير هو التخصيص متعدد العلامات التجارية. لأن قيم السمة هي خصائص CSS مخصصة، يمكنك تبديلها في وقت التشغيل:

css
@import "tailwindcss";
 
@theme {
  --color-brand: var(--brand-primary, #3b82f6);
  --color-brand-light: var(--brand-light, #60a5fa);
  --color-brand-dark: var(--brand-dark, #1d4ed8);
}
 
/* تجاوزات العلامة التجارية */
.theme-acme {
  --brand-primary: #e11d48;
  --brand-light: #fb7185;
  --brand-dark: #9f1239;
}
 
.theme-globex {
  --brand-primary: #059669;
  --brand-light: #34d399;
  --brand-dark: #047857;
}
html
<body class="theme-acme">
  <!-- كل bg-brand و text-brand إلخ تستخدم ألوان Acme -->
  <div class="bg-brand text-white">Acme Corp</div>
</body>

في v3، هذا تطلّب إضافة مخصصة أو إعداد متغيرات CSS معقد خارج Tailwind. في v4، هذا طبيعي — السمة هي متغيرات CSS، ومتغيرات CSS تتتالي. هذا النوع من الأشياء الذي يجعل نهج CSS أولاً يبدو صحيحاً.

ما أفتقده من v3#

دعني أكون متوازناً. هناك أشياء فعلها v3 أفتقدها حقاً في v4:

1. إعداد JavaScript للسمات البرمجية. كان لدي مشروع حيث كنا نولّد تدرجات الألوان من لون علامة تجارية واحد باستخدام دالة JavaScript في الإعداد. في v4، لا يمكنك فعل ذلك في @theme — ستحتاج خطوة بناء تولّد ملف CSS، أو تحسب الألوان مرة واحدة وتلصقها. طبقة التوافق @config تساعد، لكنها ليست القصة طويلة المدى.

2. IntelliSense كان أفضل عند الإطلاق. إضافة VS Code لـ v3 كانت لديها سنوات من الصقل. IntelliSense في v4 يعمل لكن كانت لديه بعض الفجوات مبكراً — قيم @theme المخصصة أحياناً لم تكتمل تلقائياً، وتعريفات @utility لم تُلتقط دائماً. تحسن هذا بشكل كبير مع التحديثات الأخيرة، لكن يستحق الملاحظة.

3. نضج النظام البيئي. النظام البيئي حول v3 كان ضخماً. Headless UI، وRadix، وshadcn/ui، وFlowbite، وDaisyUI — كل شيء كان مُختبراً ضد v3. دعم v4 قيد الطرح لكنه ليس شاملاً. اضطررت لتقديم PR إلى مكتبة مكوّنات واحدة لإصلاح توافق v4.

هل يجب أن ترحّل؟#

إليك إطار قراري بعد العيش مع v4 لعدة أسابيع:

ارحّل الآن إذا:#

  • كنت تبدأ مشروعاً جديداً (خيار واضح — ابدأ بـ v4)
  • مشروعك يحتوي على إضافات مخصصة قليلة
  • تريد فوائد الأداء للمشاريع الكبيرة
  • كنت تستخدم بالفعل أنماط Tailwind الحديثة (شفافية الشرطة المائلة، shrink-*، إلخ)
  • تحتاج استعلامات الحاوية وتفضل عدم إضافة إضافة

انتظر إذا:#

  • كنت تعتمد بشكل كبير على إضافات Tailwind الخارجية التي لا تدعم v4 بعد
  • لديك إعداد سمة برمجي معقد
  • مشروعك مستقر ولا يُطوّر بنشاط (لماذا تلمسه؟)
  • أنت في منتصف سباق ميزات (رحّل بين السباقات، ليس خلالها)

لا ترحّل إذا:#

  • أنت على v2 أو أقدم (رقّ إلى v3 أولاً، استقر، ثم فكر في v4)
  • مشروعك ينتهي خلال الأشهر القليلة القادمة (لا يستحق التقلب)

رأيي الصادق#

للمشاريع الجديدة، v4 هو الخيار الواضح. الإعداد بأسلوب CSS أولاً أنظف، والمحرك أسرع بشكل دراماتيكي، والميزات الجديدة (استعلامات الحاوية، @starting-style، المتغيرات الجديدة) مفيدة حقاً.

للمشاريع القائمة، أوصي بنهج تدريجي:

  1. الآن: ابدأ أي مشروع جديد على v4
  2. قريباً: جرّب بتحويل مشروع داخلي صغير إلى v4
  3. عندما تكون جاهزاً: رحّل مشاريع الإنتاج خلال سباق هادئ، مع اختبار انحدار بصري

الترحيل ليس مؤلماً إذا استعددت له. الأداة تتعامل مع 80% من العمل. الـ 20% المتبقية يدوية لكنها مباشرة. خصص يوماً لمشروع متوسط، يومين إلى ثلاثة أيام لمشروع كبير.

Tailwind v4 هو ما كان يجب أن يكون عليه Tailwind طوال الوقت. إعداد JavaScript كان دائماً تنازلاً لأدوات وقته. الإعداد بأسلوب CSS أولاً، وطبقات التتالي الأصلية، ومحرك Rust — هذه ليست اتجاهات، إنها الإطار يلحق بالمنصة. منصة الويب تحسنت، وTailwind v4 يميل إليها بدلاً من محاربتها.

الانتقال لكتابة رموز التصميم في CSS، وتركيبها مع ميزات CSS، وترك تتالي المتصفح نفسه يتعامل مع الخصوصية — هذا الاتجاه الصحيح. استغرق أربعة إصدارات رئيسية للوصول إلى هنا، لكن النتيجة هي أكثر إصدار متماسك من Tailwind حتى الآن.

ابدأ مشروعك القادم به. لن تنظر للوراء.

مقالات ذات صلة