مهندسی مدرن فرانتاند هیچ شباهتی به ده سال پیش ندارد. شما دیگر فقط HTML و CSS نمینویسید. یک پروژه معمولی امروزه همراه با یک محیط اجرای مشخص Node.js، یک نسخه قفلشده از مدیریت بسته (package manager)، مجموعهای پیچیده از ابزارهای ساخت (build tools) و خط لولههای استقرار (deployment pipelines) عرضه میشود که انتظار دارند همه چیز دقیقاً مطابق برنامه باشد. جایی بین اولین npm install و آخرین نسخه تولید (production build)، تفاوتهای کوچکی رخ میدهد. همکار شما از Node 20 استفاده میکند، در حالی که شما از Node 18 استفاده میکنید. یک ابزار CLI جهانی در سیستم شما، نبود یک وابستگی (dependency) در سیستم او را پنهان میکند. سپس آن جملهای میآید که هیچکس دوست ندارد بشنود: "روی سیستم من که کار میکند!"
Docker جایگاه خود را در جعبهابزار فرانتاند شما به دست میآورد، زیرا آن عدم قطعیت را از بین میبرد. Docker اپلیکیشن شما را همراه با دقیقترین محیط اجرا، کتابخانههای سیستم و وابستگیهای مورد نیاز آن بستهبندی میکند. چه در حال کدنویسی روی Windows باشید، چه از macOS خروجی بگیرید و چه روی یک نمونه ابری Linux مستقر شوید، رفتار برنامه کاملاً یکسان باقی میماند.
چرا توسعهدهندگان فرانتاند باید اهمیت دهند
نقاط ضعفی که Docker آنها را حل میکند، انتزاعی نیستند؛ آنها در هر اسپرینت (sprint) خود را نشان میدهند.
تداخل نسخهها باعث هدر رفتن زمان میشود. یک پروژه قدیمی مشتری به Node 18 نیاز دارد، پروژه جانبی شما به Node 20 و کار جدید در یک استارتاپ به Node 22 نیاز دارد. بدون کانتینرها، شما این موضوع را از طریق مدیریتکنندههای نسخه (version managers) مدیریت میکنید. این روش تا زمانی که کار میکند، خوب است، اما همیشه اینطور نیست. یک عدم تطابق جزئی در نسخه npm میتواند نحوه حل وابستگیهای همتا (peer dependencies) را تغییر دهد و باعث شود با یک نسخه ساخت (build) خراب مواجه شوید که برای شخص دیگری به خوبی اجرا میشود. وقتی یک فریمورک نسخه جدیدی را معرفی میکند، چرخه بازخورد نباید دو ساعت نصب مجدد باشد؛ بلکه باید تنها با تغییر یک فایل و ریاستارت کردن یک کانتینر انجام شود.
بستههای جهانی (Global packages) منبع دیگری برای اصطکاکهای بیصدا هستند. ممکن است Angular CLI، Expo یا Prisma را از شش ماه پیش به صورت جهانی نصب کرده باشید. یک توسعهدهنده جدید همان ابزار را تازه نصب میکند و نسخه متفاوتی دریافت میکند. ناگهان اسکریپتهای ساخت شما هشدارهایی میدهند که در هیچ جای دیگر ظاهر نمیشوند. Docker با محلی نگه داشتن همه چیز در سطح پروژه، این مشکل را حل میکند. شما نسخه Node را در Dockerfile خود تعریف میکنید. وابستگیها داخل کانتینر و جدا از سیستمعامل میزبان شما نصب میشوند. لپتاپ شما میتواند macOS، Windows یا Ubuntu باشد؛ اپلیکیشن هر بار دقیقاً همان محیط را میبیند.
مزایای فرآیند ورود به تیم (Onboarding) را نمیتوان نادیده گرفت. نیروهای جدید نیازی به یک فایل readme سه صفحهای ندارند که شامل نصب Homebrew، نامهای مستعار nvm و اصلاح مجوزهای جهانی باشد. آنها Docker را نصب میکنند، مخزن (repository) را کلون میکنند و یک دستور را اجرا میکنند. فرآیندی که قبلاً یک بعدازظهر طول میکشید، به چند دقیقه کاهش مییابد. و وقتی آنها پروژهها را عوض میکنند، چیزی باقی نمیماند. نه ابزارهای جهانیِ رها شده و نه مدیریتکنندههای نسخهای که برای اولویت در PATH با هم میجنگند. سیستم محلی آنها تمیز میماند.
ایمیجها و کانتینرها: مفاهیم پایه
اگر با Docker آشنا نیستید، اصطلاحات آن سادهتر از آن چیزی است که به نظر میرسد. یک Docker image یک نقشه (blueprint) است. این نقشه شامل کد منبع، محیط اجرای Node.js، فایل قفل (lockfile) و تمام وابستگیهای مورد نیاز برای اجرای برنامه است. یک container یک نمونه زنده است که از آن image ساخته شده است. image را مانند یک دستور پخت و container را مانند خودِ غذا تصور کنید. شما میتوانید از یک دستور پخت، صد بار یک کیک مشابه بپزید. شما میتوانید بدون نگرانی از آنچه روی کامپیوتر میزبان نصب شده است، کانتینرهای یکسانی را از یک image بالا بیاورید.
یک Dockerfile کاربردی
بیایید به یک نقطه شروع ملموس نگاه کنیم. اگر شما
