توسعه فرانت‌اند

Tailwind CSS و CSS Modules؛ مقایسهٔ شیوهٔ نگهداری style

تفاوت utility و scope محلی، token و class پویا، مرز component و نمونه کامل Vite با دو کارت Tailwind 4 و CSS Modules.

1405/01/05 4 دقیقه مطالعه 815 بازدید

دو روش متفاوت برای نگهداری style

یک تیم دوست دارد ظاهر component را در همان markup بخواند. تیم دیگری ترجیح می‌دهد نام معنایی کارت و دکمه در markup باشد و جزئیات style در فایل جدا نگه داشته شود. Tailwind و CSS Modules می‌توانند این دو مسیر را پشتیبانی کنند، اما پاسخ واحدی برای همهٔ پروژه‌ها ندارند. انتخاب به قرارداد تیم، اندازهٔ رابط و روش تغییر طراحی وابسته است. سرعت نوشتن یک کارت در جلسهٔ اول، هزینهٔ نگهداری صد component در سال بعد را به تنهایی مشخص نمی‌کند.

Tailwind utilityهایی برای propertyها و حالت‌ها فراهم می‌کند و ابزار build CSS مورد نیاز را از منابع پروژه تولید می‌کند. CSS Modules نگاشت نام classهای محلی را از فایل style به مصرف‌کننده می‌دهد. اولی بیشتر دربارهٔ vocabulary و شیوهٔ نوشتن ظاهر است؛ دومی دربارهٔ scope نام‌ها در ابزار build. بنابراین الزاماً دو گزینهٔ کاملاً ناسازگار نیستند. می‌توان آن‌ها را کنار هم به کار برد، به شرط اینکه مالکیت قواعد روشن باشد و اعضای تیم بدانند هر بخش از کجا می‌آید.

Scope با جلوگیری از همهٔ اثرهای CSS یکی نیست

در CSS Modules، نام محلی مثل card هنگام پردازش به نام خروجی مناسب نگاشت می‌شود و کد آن را از object واردشده می‌خواند. این کار برخورد اتفاقی نام‌ها را کم می‌کند. اما inheritance، cascade و قواعد global همچنان وجود دارند. Style روی body یا selector عمومی می‌تواند به component برسد. مستندات CSS Modules مفهوم local scope و export نام‌ها را توضیح می‌دهد. Module به معنی sandbox کامل style نیست.

در Vite، فایل با پسوند module.css همین قرارداد را فعال می‌کند. Import آن object نام‌ها را در اختیار کد می‌گذارد و selectorهای واقعی را ابزار می‌سازد. راهنمای CSS Modules در Vite گزینه‌های پردازش را بیان می‌کند. اگر فایل عادی CSS را import کنید، انتظار scope محلی خودکار نداشته باشید. نام و تنظیم ابزار بخشی از قرارداد پروژه‌اند و باید در مثال آموزشی مشخص باشند.

Utilityها vocabulary مشترک می‌خواهند

Tailwind نام‌های کوتاه برای فاصله، رنگ، اندازه و حالت‌ها دارد. خوانندهٔ آشنا می‌تواند ظاهر را از classها دنبال کند. برای کسی که vocabulary را نمی‌شناسد، همان markup ممکن است طولانی و دشوار باشد. این تفاوت آموزش تیم است، نه اثبات خوب یا بد بودن ابزار. اگر اعضا هر بار مقدار arbitrary متفاوتی بنویسند، داشتن utility به تنهایی طراحی یکدست ایجاد نمی‌کند. مقدارهای مشترک و قرارداد component هنوز باید تعریف شوند.

در Tailwind فعلی، theme variableها بخشی از تعریف tokenها و utilityها هستند. رنگ پیش‌فرض یک palette، هویت برند شما را انتخاب نمی‌کند. راهنمای theme رابطهٔ variableها و utilityها را توضیح می‌دهد. تعبیر design system آماده می‌تواند گمراه‌کننده باشد؛ ابزار vocabulary و تنظیم دارد، اما قواعد محصول، وضعیت خطا و معیار استفاده از componentها را تیم تعیین می‌کند. یک design system از مجموعهٔ classها بزرگ‌تر است.

نمونهٔ کامل با دو کارت در یک پروژه

این نمونه Vite با Tailwind 4 و CSS Modules است. Node 24 و npm لازم است. package.json، vite.config.js و index.html در ریشه‌اند. داخل src، main.js، global.css و card.module.css را قرار دهید. فایل‌های نمونه را به همین نام‌های مقصد تغییر دهید. npm install dependency و lock واقعی می‌سازد؛ npm run dev صفحه را در نشانی اعلام‌شده نشان می‌دهد و npm run build خروجی می‌سازد. این مجموعه از plugin رسمی Vite برای Tailwind استفاده می‌کند، مطابق راهنمای نصب فعلی.

{"name":"style-comparison","private":true,"version":"1.0.0","type":"module","scripts":{"dev":"vite","build":"vite build"},"devDependencies":{"vite":"^8.0.0","tailwindcss":"^4.0.0","@tailwindcss/vite":"^4.0.0"}}
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({ plugins: [tailwindcss()] });
<!doctype html><html lang="fa" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>دو روش Style</title></head><body class="bg-slate-100 p-6 text-slate-900"><main class="mx-auto max-w-3xl"><h1 class="mb-6 text-2xl font-bold">دو کارت با دو روش</h1><div class="grid gap-6 sm:grid-cols-2"><article id="module-card"><h2 id="module-title">CSS Modules</h2><p id="module-text">Style این کارت از فایل module وارد شده است.</p><a id="module-link" href="#notes">خواندن توضیح</a></article><article class="rounded-xl border border-slate-300 bg-white p-6"><h2 class="mb-3 text-xl font-bold">Tailwind</h2><p class="mb-4 leading-8">Style این کارت از utilityهای مستقیم آمده است.</p><a class="inline-block rounded-md bg-teal-800 px-4 py-2 text-white focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-amber-700" href="#notes">خواندن توضیح</a></article></div><section id="notes" class="mt-8"><h2 class="text-xl font-bold">توضیح</h2><p class="mt-3 leading-8">هر دو کارت از HTML استاندارد و لینک واقعی همین صفحه استفاده می‌کنند.</p></section></main><script type="module" src="/src/main.js"></script></body></html>
import './global.css';
import styles from './card.module.css';
for (const [id, key] of [['module-card', 'card'], ['module-title', 'title'], ['module-text', 'text'], ['module-link', 'link']]) {
  document.getElementById(id).classList.add(styles[key]);
}
@import "tailwindcss";
body { font-family: system-ui, sans-serif; }
.card { background: white; border: 1px solid #cbd5e1; border-radius: .75rem; padding: 1.5rem; }
.title { margin-block: 0 .75rem; font-size: 1.25rem; font-weight: 700; }
.text { margin-block: 0 1rem; line-height: 2; }
.link { display: inline-block; padding: .5rem 1rem; background: #115e59; color: white; border-radius: .375rem; }
.link:focus-visible { outline: 2px solid #b45309; outline-offset: 4px; }

کارت اول classهای module را از JavaScript می‌گیرد و کارت دوم utilityهای ثابت در HTML دارد. ساختار هر دو article، عنوان، متن و لینک است. لینک‌ها به notes واقعی همان صفحه می‌روند و دکمهٔ بی‌رفتار نداریم. پروژه تصویر و API ندارد. Tailwind Preflight و styleهای global در همین صفحه روی هر دو کارت اثر دارند؛ نمونه عمداً یک آزمایش isolation یا مقایسهٔ عملکرد نیست. قواعد module برای قسمت‌های لازم صریح نوشته شده‌اند تا مقایسهٔ محل تعریف style قابل دنبال کردن باشد.

ظاهر دو کارت نزدیک است، ولی از آن ادعای یکسان بودن همهٔ جزئیات مرورگر یا فایل خروجی نمی‌کنیم. این مقاله روش اجرا را ارائه می‌دهد و اجرای تست یا اندازه‌گیری حجم گزارش نشده است. فایل lock ساخته‌شده را نگه دارید و برای نصب تازه از npm ci استفاده کنید. اگر در framework دیگری کار می‌کنید، روش import style و تنظیم build را مطابق همان framework انتخاب کنید؛ کپی کردن plugin Vite در ابزار دیگر قرارداد درست ایجاد نمی‌کند.

Class پویا باید برای ابزار قابل تشخیص باشد

در Tailwind، ساختن نام class با چسباندن رشته‌های ناقص می‌تواند باعث شود ابزار نام کامل مورد نیاز را در منبع نبیند. بهتر است حالت‌های محدود را به رشته‌های کامل class نگاشت کنید. مقدار دادهٔ کاربر هم نباید بی‌قاعده به نام utility تبدیل شود. راهنمای تشخیص class این رفتار را توضیح می‌دهد. در مثال همهٔ نام‌ها به صورت کامل و ثابت در HTML هستند؛ هیچ نام runtime پنهانی لازم نیست.

در CSS Modules، حالت پویا می‌تواند با نام class محلی جدا یا data attribute بیان شود. باز هم باید مشخص باشد چه کسی حالت را تغییر می‌دهد و style چگونه مصرف می‌شود. اگر چند روش برای یک حالت استفاده کنید، مثل utility، inline style و class module هم‌زمان، پیدا کردن قاعدهٔ غالب سخت می‌شود. تعداد ابزارهای بیشتر لزوماً انعطاف مفید بیشتری نمی‌دهد. برای هر component یک قرارداد سادهٔ ورودی، حالت و style انتخاب کنید.

تکرار را در مرز component حل کنید

کپی کردن رشتهٔ بلند utility در ده صفحه می‌تواند اصلاح را پرهزینه کند. جدا کردن component مشترک، راهی برای نگهداری همان قرارداد است. در CSS Modules هم کپی کردن style مشابه در چند فایل مشکل تکرار می‌سازد؛ داشتن فایل جدا به تنهایی reuse ایجاد نمی‌کند. هر دو رویکرد به شناخت بخش واقعاً مشترک نیاز دارند. اختلاف کوچک دو محصول را با یک component پر از شرط پنهان نکنید و شباهت ظاهری امروز را همیشه قرارداد پایدار فرض نکنید.

Tokenهای رنگ و فاصله می‌توانند مستقل از محل نوشتن class مشترک باشند. مقالهٔ Custom Properties و theme دربارهٔ این مسئولیت توضیح می‌دهد. در مخزن چندپروژه‌ای، package طراحی هم باید dependency واقعی و قرارداد export داشته باشد؛ نوشتهٔ Turborepo و package مشترک این مرز را بیان می‌کند. انتخاب Tailwind یا Modules تصمیم دربارهٔ همهٔ ساختار مخزن نیست.

RTL، focus و کاهش حرکت هنوز باید طراحی شوند

هیچ‌کدام از دو روش، رابط را خودکار دسترس‌پذیر نمی‌کنند. Label مناسب، ترتیب DOM، تراز متن و وضعیت focus به HTML و تصمیم طراحی وابسته‌اند. برای RTL باید قصد فاصله و جایگاه روشن باشد؛ مقالهٔ Logical Properties این بخش را توضیح می‌دهد. Utility مناسب یا property منطقی در module هر دو امکان‌اند، اما رفتار component باید با زبان و محتوای واقعی سازگار باشد.

در نمونه، focus-visible برای لینک‌ها تعریف شده است. این کار محدود به روش style نیست و باید در هر دو مسیر حفظ شود. اگر design token رنگ عوض شد، کنتراست حالت‌ها هم نیاز به بررسی دارد. نام یک رنگ یا class موفقیت، معنای مناسب و کنتراست کافی را تضمین نمی‌کند. نوشتهٔ دسترس‌پذیری از روز اول کمک می‌کند این تصمیم‌ها کنار ساختار component بمانند، نه اینکه بعد از پایان کار به utility یا stylesheet دیگری واگذار شوند.

انتخاب را با یک تغییر واقعی بسنجید

برای مقایسه، یک component واقعی با حالت خطا، عنوان بلند و نسخهٔ کوچک صفحه انتخاب کنید. سپس تغییر رنگ برند، فاصله و یک حالت تازه را در هر رویکرد دنبال کنید. ببینید عضو تازهٔ تیم محل رفتار را پیدا می‌کند و آیا اصلاح مشترک قابل کنترل است. عدد سرعت توسعه بدون همین شرایط، نتیجهٔ عمومی نمی‌دهد. در پروژهٔ دارای CSS جاافتاده، مهاجرت کامل شاید هزینهٔ لازم نداشته باشد؛ در تیم آشنا با utilityها، Tailwind می‌تواند vocabulary مشترک مناسبی باشد. تصمیم خوب با توان نگهداری تیم و مرز component هماهنگ است.

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!