هر توسعه‌دهنده‌ای آن پوشه را دارد. همان پوشه‌ای به نام utils یا helpers که از یک مخزن به مخزن دیگر کپی می‌کنید. آن را می‌چسبانید، بیست دقیقه وقت صرف می‌کنید تا ارجاعات به طرحواره‌های (schemas) قدیمی پایگاه داده را پاک کنید، بررسی‌های احراز هویت (auth checks) که کاربردی ندارند را از بین ببرید و متغیرها را تغییر نام دهید تا لینتر (linter) جدیدتان از فریاد زدن دست بردارد. من قبلاً این کار را با یک سیستم تم‌سازی که ساخته بودم، یعنی Dynamic Theme Kit، انجام می‌دادم. این سیستم به عنوان یک ویژگی در داخل یک اپلیکیشن شروع شد و ماه‌ها مانند یک ابزار قابل حمل با آن برخورد کردم. اشتباه می‌کردم. کپی کردن کد، بازاستفاده (reuse) نیست؛ بلکه تکثیر کردن با مراحل اضافی است.

تله‌ی ذهنیت تک-پروژه‌ای

وقتی یک ویژگی را داخل یک پروژه می‌سازید، صدها فرض نامرئی انجام می‌دهید. پالت رنگی ممکن است یک ساختار خاص CSS-in-JS را فرض کرده باشد. مقیاس فاصله‌گذاری (spacing scale) ممکن است به یک design token از راهنمای برند شرکت شما ارجاع دهد. سوئیچ بین حالت روشن و تاریک (light and dark mode) ممکن است یک endpoint مربوط به تنظیمات کاربر را فراخوانی کند که مختص بک‌اِند آن اپلیکیشن است. این وابستگی‌ها بی‌خطر به نظر می‌رسند، چون در داخل پروژه، بی‌خطر هستند. آن‌ها به آنجا تعلق دارند.

مشکل زمانی شروع می‌شود که سعی می‌کنید آن کد را بیرون بکشید. متوجه می‌شوید که آن کامپوننت «قابل بازاستفاده»، در واقع شبکه‌ای از رشته‌های پنهان است که آن را به همان یک کدبیس متصل می‌کند. من این را با DTK یاد گرفتم. بله، متغیرهای تم را تولید می‌کرد، اما در عین حال انتظار یک ساختار پوشه‌بندی خاص را هم داشت. یک تعریف تایپ (type definition) را از جایی در اعماق دایرکتوری types اپلیکیشن اصلی ایمپورت می‌کرد. فرض را بر وجود یک شیء پیکربندی (config object) سراسری می‌گذاشت که فقط در همان یک مخزن وجود داشت. من هرگز متوجه نشده بودم، چون در آن پروژه، همه چیز همیشه در دسترس بود.

تبدیل DTK به یک پکیج مستقل به معنای جراحی بود، نه گسترش. من به ویژگی‌های بیشتر نیاز نداشتم؛ به وابستگی‌های کمتر نیاز داشتم.

استخراج Dynamic Theme Kit

سخت‌ترین کار این بود که با کدبیس بنشینم و از هر تابع و هر export بپرسم: آیا این در خدمت منطق تم‌سازی است یا در خدمت پروژه؟ من پیش‌فرض‌های استایل‌دهی (styling presets) را حذف کردم. این فرض را که مصرف‌کننده یک اپلیکیشن React خواهد بود، از بین بردم. پالت‌های رنگی پیش‌فرض را کاملاً پاک کردم. پروژه اصلی یک زیبایی‌شناسی شرکتی با رنگ‌های سرمه‌ای و سنگی (navy-and-slate) داشت که در پیش‌فرض‌ها تزریق شده بود. آن باید حذف می‌شد. یک پکیج نمی‌تواند رنگ‌های برند شما را همراه خود داشته باشد.

کیت جدید دقیقاً یک کار انجام می‌داد. یک شیء پیکربندی می‌گیرد — مقداری از مقادیر رنگ، مقداری از اعداد فاصله‌گذاری، مقداری از مقیاس‌های تایپوگرافی — و CSS custom properties تولید می‌کند. همین. این پکیج آن‌ها را اعمال نمی‌کند. تصمیم نمی‌گیرد که آن‌ها در کجا در DOM شما قرار بگیرند. برایش مهم نیست که از Tailwind استفاده می‌کنید، Styled Components یا HTML ساده. پکیج متغیرها را به اپلیکیشن شما می‌دهد و پروژه شما انتخاب می‌کند که چگونه از آن‌ها استفاده کند.

آن محدودیت در ابتدا محدودکننده به نظر می‌رسید، اما مشخص شد که رهایی‌بخش است.

وقتی واقعاً سعی می‌کنید از آن بازاستفاده کنید، چه چیزی خراب می‌شود؟

قبل از اینکه چیزی منتشر کنم، نیاز داشتم مدرکی داشته باشم که این انتزاع (abstraction) واقعاً کارآمد است. سه پروژه شخصی کوچک را از آرشیو خود بیرون کشیدم: یک ابزار پیش‌نمایش markdown، یک ردیاب عادت (habit tracker) و یک لندینگ پیج برای یک رویداد. هیچ‌کدام از آن‌ها فریم‌ورک یا ساختار پوشه‌بندی مشترکی نداشتند. من DTK را به صورت محلی (locally) در هر کدام نصب کردم و سعی کردم آن‌ها را تم‌سازی کنم.

اولین تلاش بلافاصله شکست خورد. نام متغیرهایی که DTK تولید می‌کرد بیش از حد خاص بودند. توکن‌هایی مثل --primary-action و --background-overlay تولید می‌کرد که بر یک چیدمان UI خاص دلالت داشتند. در پیش‌نمایش‌ساز markdown، این نام‌ها هیچ معنایی نداشتند. هیچ دکمه اکشنی وجود نداشت. هیچ لایه‌ی overlay وجود نداشت. من منطق تولید را تغییر دادم تا نام‌های ساختاری و خنثی تولید کند که به جای ویجت، خودِ مقدار را توصیف کنند.

همچنین متوجه شدم که مقادیر پیش‌فرض من بیش از حد جسورانه بودند. وقتی کاربر یک پیکربندی ناقص ارسال می‌کرد، DTK شکاف‌ها را با مقادیری پر می‌کرد که در یک داشبورد متراکم خوب به نظر می‌رسیدند اما در یک لندینگ پیج خلوت، همه چیز را خراب می‌کردند. من به سمت پیش‌فرض‌های شفاف (transparent defaults) رفتم، جایی که توکن‌های مفقود شده به سادگی رندر نمی‌شدند و اجازه می‌دادیم پروژه مصرف‌کننده، مقادیر جایگزین (fallbacks) خودش را تعریف کند.

سپس نوبت به مستندات رسید. چیزی که برای من بدیهی به نظر می‌رسید — «فقط یک شیء پیکربندی پاس بده» — برای کسی که نیمه‌شب در حال خواندن README بود، مبهم بود. من آن را با اشیاء واقعی، مسیرهای فایل واقعی و توضیحات شفاف درباره اینکه هنگام فراخوانی تابع چه اتفاقی می‌افتد و اپلیکیشن شما پس از آن چه کاری باید انجام دهد، بازنویسی کردم.

این پروژه‌های شخصی کوچک به عنوان بستر تست عمل کردند. ریسک پایینی داشتند، اما نقص‌های واقعی را آشکار کردند که اگر فقط به کد منبع در انزوا خیره می‌شدم، هرگز متوجه آن‌ها نمی‌شدم.

تست واقعی: محیط عملیاتی در Web Weavers World

Personal projects are sandboxes. They do not have deadlines, stakeholders, or legacy CSS that predates your package. The real test came when I integrated DTK into Web Weavers World, my business site. This was a live property with existing styles, client expectations, and analytics to consider. If the package broke something, I could not just delete the repo and start over.

I added DTK to the build pipeline, pointed it at a new color configuration, and let it generate a fresh set of CSS variables. The integration took an afternoon, not a week. That was the signal. Previously, adding a new theme meant writing new CSS, hunting down hardcoded hex values in twenty files, and hoping I did not miss an edge case. Now I add a palette to the configuration file, DTK generates the variables, and the rest of the site consumes them. The theme logic went from being a fragile manual process to something I trust enough to hand off to collaborators.

Three Questions That Changed How I Build

Going through this process forced me to formalize a mental checklist I now use before I abstract anything:

  • Is this variable truly generic? If the name or the logic references a domain concept from the original project, it stays behind.
  • Does this belong in the package or the application? Business rules, brand identities, and layout assumptions live in the app. Plumbing that generates standardized output lives in the package.
  • Am I solving a reusable problem or a project-specific one? This is the hardest to answer honestly. We like to think our solutions are universal. Usually they are local.

Answering these questions forced me to simplify my design, often by removing code rather than adding it. DTK taught me that reuse is not a gift you give yourself. It is a discipline you practice by saying no to convenience.

A Different Way to Think About Refactoring

I used to measure refactors by how much shorter they made the code. Fewer lines felt like progress. Now I measure them by how many doors they open. The Dynamic Theme Kit is not elegant because it is concise. It is useful because it survived three unrelated personal projects and a production business site without needing to change its internals.

That is the metric that matters. Code that works once is an expense. Code that works repeatedly is an asset. Before I start any feature now, I stop. I ask if I am building something I will need again. If the answer is yes, I build it differently from the first line. I isolate the inputs. I define the outputs. I remove the assumptions.

The best refactor does not make your code shorter. It makes your code work in places you have not imagined yet.