
در فرانتاند، تمرکز اغلب روی کد، کامپوننت و فریمورک هاست، اما مسیر واقعی پروژه از نوشتن کد تا اجرا شدن در دست کاربر بسیار گستردهتره. این مجموعه نشون میده که تصمیمهای فنی در مراحل build، deploy، runtime و observability چطور روی پرفورمنس، پایداری و هزینهی نگهداری سیستم اثر میگذارن. هدف این چهار بخش، دادن دیدی end-to-end است؛ در قسمت اول، تمرکز روی Build و Docker است و یاد میگیریم چگونه مرحلهی build را قابل پیشبینی و قابل اعتماد کنیم تا پایهی معماری پروژه محکم شود.
سالهاست که در فرانتاند دربارهی Performance، معماری و تجربهی کاربری صحبت میکنیم،اما یک لایهی مهم معمولاً دستکم گرفته میشه: فرآیندی که کد را به خروجی قابل اجرا تبدیل میکند.
فرانتاند، برخلاف تصور رایج، صرفاً مجموعهای از کامپوننتها یا stateها نیست.سیستمیه که در نهایت باید build بشه، منتقل بشه و اجرا بشه.
نقطهی شروع تمام این زنجیره، build هستش ...
در این مرحله، دپندنسی ها ریزالو میشن، کد به خروجی قابل اجرا تبدیل میشه و بسیاری از تصمیمهای معماری عملاً غیرقابل بازگشت میشن.
بعد از این مرحله:
فریمورک صرفاً یک جزئیات پیادهسازیه
رفتار runtime تابع خروجی تولیدشده است
و هر تفاوتی در build، میتونه به تفاوت در رفتار نهایی منجر بشه
به همین دلیل، build یک مرحلهی اجرایی ساده نیست؛بلکه بخشی از معماری سیستم محسوب میشه.
یکی از مسائل نادیده گرفتهشده در پروژهها این است که build اغلب وابسته به محیط یا همون environment افراد مختلف است؛ نسخهی Node، سیستمعامل یا حتی تنظیمات جزئی package manager میتواند باعث شود یک سورسکد واحد در شرایط مختلف خروجیهای متفاوت تولید کند. در چنین شرایطی، صحبت از ثبات، قابلیت پیشبینی یا حتی دیباگ مؤثر عملاً بیمعنا میشود.
Docker در فرانتاند وظیفهاش کنترل محیط build است.با Docker میتونیم:
محیط build را ثابت کنیم
نسخهی runtime و وابستگیها را تثبیت کنیم
خروجی قابل اعتماد تولید کنیم، مستقل از ماشین یا شخص اجراکننده
یک مثال واقعی:
پروژه React شما روی سیستم local بدون مشکل build میشه، اما در CI یا staging شکست میخوره. مشکل معمولاً تفاوت محیطهاست. Docker این تفاوت را حذف میکنه و خروجی یکسان تولید میکنه.
فرض کنید پروژهای با React داریم. ساختار پروژه:
my-app/ ├─ src/ │ ├─ App.jsx │ └─ components/ ├─ package.json └─ Dockerfile
یک Dockerfile سادهی multi-stage میتونه این شکلی باشه:
# Stage 1: Build FROM node:20 AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build # Stage 2: Serve FROM nginx:alpine COPY --from=build /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
توضیح کوتاه:
مرحله اول، کد ما رو build میکنه و تمام وابستگیها رو resolve میکنه
مرحله دوم، خروجی static build شده رو داخل Nginx میذاره تا سرو بشه
این یعنی تمام تصمیمهای Build ما، از محیط گرفته تا وابستگیها(دپندنسی ها)، قابل پیشبینی و یکسان باقی میمونن.
[Developer Code] │ ▼ [Install Dependencies] ---> (Resolve Node Modules) │ ▼ [Run Build Scripts] ---> (Transpile + Bundle + Minify) │ ▼ [Output: Static Files] │ ▼ [Docker Multi-Stage] │ ▼ [Stage 1: Build Environment] --> Compile Code │ ▼ [Stage 2: Runtime Environment] --> Serve Static Files │ ▼ [CI/CD / Staging / Production] --> Consistent & Reliable Output
این فلوچارت نشان میده که فرانتاند از اولین خط کد تا خروجی پایدار روی سرور، نیازمند مرحلهی build کنترلشده است و Docker وظیفهی تضمین این ثبات را داره.
وقتی پروژهی فرانتاند شما رشد میکنه، حجم وابستگیها و فایلها به سرعت زیاد میشه.اگر همهی ابزارها، کامپایلرها و وابستگیهای توسعه در خروجی نهایی باقی بمونن،
نتیجه: bundle بزرگ، اجرای سنگین و حتی ریسکهای امنیتی بوجود میان.
اینجاست که Multi-Stage Build وارد بازی میشه.ایدهی ساده اما قدرتمندش اینه:
مراحل مختلف build را در کانتینرهای جداگانه اجرا کنیم و فقط خروجی مورد نیاز برای اجرا (runtime) را به مرحلهی نهایی منتقل کنیم.
به عبارت دیگر:
Stage 1: محیط کامل توسعه و build
Stage 2: محیط سبک runtime که فقط فایلهای نهایی رو سرو میکنه
این کار باعث میشه خروجی کوچک، سبک، و قابل اعتماد باشه، و وابستگیهای غیرضروری وارد سرور یا CDN نشه.
فرض کنید پروژه React داریم. Dockerfile معمولاً این شکلیه:
# Stage 1: Build FROM node:20 AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build # Stage 2: Serve FROM nginx:alpine COPY --from=build /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
توضیح مراحل:
Stage 1 — Build
محیط Node کامل است
تمام وابستگیها نصب میشوند
کد transpile و bundle میشود
خروجی نهایی در /app/build قرار میگیرد
Stage 2 — Serve
محیط سبک Nginx
فقط فایلهای static (HTML, JS, CSS) از Stage 1 منتقل میشوند
اپلیکیشن قابل اجرا روی سرور میشود
مزیت: فقط چیزی که نیاز داریم به محیط runtime میآید و حجم کانتینر کاهش مییابد
[Developer Code + Dependencies] │ ▼ [Stage 1: Build Container] - Install dependencies - Compile code - Bundle & minify │ ▼ [Build Output /app/build] │ ▼ [Stage 2: Runtime Container] - Lightweight environment (Nginx) - Copy only build output │ ▼ [Serve Static Files] --> Consistent & Optimized Delivery
حجم کانتینر runtime کاهش پیدا میکنه
امنیت بالاتر، چون ابزارهای build وارد سرور نمیشن
قابلیت پیشبینی و تکرارپذیری افزایش پیدا میکنه
آمادهسازی برای CI/CD سادهتر میشه
Build تنها مرحلهی آمادهسازی خروجی نیست؛ ستون معماری فرانتاند است
Docker ابزاری برای تثبیت تصمیمهای فنی و قابل اعتماد کردن خروجی است
Multi-stage build نشاندهندهی تفکر معماری و تفکیک مسئولیتهاست
در Part 2، سراغ deploy و CDN میریم و یاد میگیریم چگونه خروجی build شده را بهینه، سریع و مطمئن در دسترس کاربران قرار دهیم.
دوست داشتین تو لینکدین با من در ارتباط باشین :)