چالش‌های نسخه Production در React JS

دنیای Front-End داره روز به روز پیچیده‌تر و البته حرفه‌ای تر میشه. در گذشته همه دیدی ساده انگارانه به Front-End داشتن ولی الان این فیلد داره شونه به شونه فیلد‌هایی حرکت می‌کنه که در گذشته پرچم دارن بودن. این خروج Front-End از حاشیه و ایفای نقش به عنوان یک نقش کلیدی در بازی توسعه وب علاوه بر حس خوبی که ایجاد کرده مشکلات و چالش‌هایی رو هم به همراه داشته.

تا دیروز چندتا فایل js رو با تگ script به انتهای یک صفحه اضافه می‌کردید و در قلب یک Framework یا ساختاری از جنس Back-End کد میزدید اما امروز به عنوان یک واحد مستقل عمل کنید و کارهایی مثل مدیریت Routeهای وب سایت که تا دیروز back-end انجام می‌داد رو در لایه front-End انجام بدید.

برای همین مانند یک سیستم توسعه حرفه‌ای، در پروژه‌های جدید Front-End و به خصوص React باید دو حالت داشته باشیم، یکی برای توسعه (dvelopment) و یکی هم برای تولید (prodcution). یکی برای وقتی که در حال توسعه و دیباگ هستیم و یکی دیگه هم واسه وقتی که قرار نیست کدی تغییر کنه و باید توسط تعداد زیادی کاربر استفاده بشه. به عبارتی در حالت توسعه تعداد کاربران ( توسعه دهندگان) کم هست و نرخ تغییرات کد زیاد ولی در حالت تولید تعداد کابران زیاد و نرخ تغییرات کم. جدای از این‌ها، محل اجرا و تنظیمات جانبی این دو حالت هم با هم متفاوت هست.


نمونه‌ای برای دو حالت توسعه‌ و تولید

اگر شما از یک ابزار استاندارد توسعه‌ری‌اکت استفاده می‌کنید، احتمال زیاد این دو حالت در اون وجود داره. مثلا در ابزارCRA یا همون Creact React App شما با اجرای دستور npm start می‌تونید برنامه ری‌اکت خودتون رو در حالت dvelopment اجرا کنید و با اجرای دستور npm run build هم برنامه رو به صورت فایل‌های استاتیک فشرده و باندل شده‌ در بیارید و نسخه prodcution رو بسازید.


عدم استقبال توسعه‌دهندگان

چند موردی که دیدم و باشون صحبت کردم فهمیدم دلیل این که سراغ پروداکشن نرفتن یک یا چندتا از موارد زیر هست:

  • بی‌انگیزه بودن: بخاطر این که در حالت توسعه هم پروژه کار می‌کنه و تفاوت این دو حالت رو خیلی واضح درک نمی‌کنن انگیزی زیادی برای این که وقت بذارن و یاد بگیرن نمی‌بینن!
    در نهایت این افراد نسخه توسعه رو در اختیار کاربران هم قرار میدن! مثلا پروژه CRA رو می‌برن روی یک سرور مجازی و روی اون دستور npm start رو اجرا می‌کنن، بعد nginx رو به پورت 3000 پراکسی می‌کنن و بعد خوشحال میشن از این که پروژه لانچ شده!
  • وقت نداشتن: حس می‌کنن خیلی کار وقت گیری هست و الان وقت این کار رو ندارن (البته تو این مورد بعضی‌ها راست میگن! به طرف دوزار میدن یه ماه هم وقت بعد ازش بوینگ 727 می‌خوان!)
  • آشنا نبودن: چون قبلا در توسعه Front-End چنین چیزی رو تجربه نکرده بودن اصلا با این موضوع آشنایی نداشتن و گاهی هم که چیزهای در موردش شنیدن احساس کردن یه چیز تشریفاتی هست نه الزامی.


الزامی برای پروژه‌های React ساختار مستقل

پروژه‌های ری‌اکتی که به سبک جدید یعنی ساختاری مستقل و SPA هستن (نه به صورت زیر مجموعه‌ای از یک ساختار Back-Endی) عمدتا دارای ساختار پیچیده‌ای هستن. برای مدیریت پیچیدگی، دیباگ و بهبود performance کد‌ها نیاز هست از ابزارهایی مثل webpack ، ES-lint ، react-dev-tools و ... استفاده بشه و گاهی هم استفاده ازشون الزامی چون واقعا بدون اینها نمیشه کار رو خیلی خوب توسعه داد. طبیعی هست که برخی ساختارها صرفا به درد محیط توسعه می‌خوره و جایی در تولید ندارد مثل webpack-hot-reload که با تغییر کد اتوماتیک مرورگر رو رفرش می‌‌کنه.

البته می‌دونید که خود React صرفا یک کتابخانه مدیریت Component هست و چیز خیلی خاصی نداره ولی از اونجایی که پایه ساختار و بستر کار چندین کتابخانه،پلاگین و ابزار دیگه شده اسم اون روی پروژه میاد و مثلا میگن این پروژه با React زده شده در حالی که ترکیب چند پکیج مختلف و گاه مستقل هست.
( redux ، react-dom، react-router-dom ،axios و ....)


فقط داشتن دو محیط تفکیک شده کافی نیست

اگر بخواهیم کمی دیدمون رو وسیع‌‌تر کنیم و تولید رو فقط نسخه تولید معنی نکنیم و به داشتن دو محیط مجزا کار رو ختم نکنیم میشه گفت که ساختار هر پروژه می‌تونه شرایطی رو به وجود بیاره که ما رو مجبور به توسعه برنامه به سبک خاصی کنه. مثلا نیاز به SEO و Render سمت سرور ما رو مجبور به تغییر ساختار پروژه از CSR به SSR می‌کنه که این تغییر خود باعث تغییر بسیاری از موارد دیگه میشه. برای آشنایی با چیستی و تفاوت‌های SSR و CSR در React JS این مطلب رو بخونید:

https://virgool.io/iran-react-community/%D8%B4%D9%86%D8%A7%D8%AE%D8%AA-%D9%85%D9%81%D8%A7%D9%87%DB%8C%D9%85-ssr-%D9%88-csr-%D8%AF%D8%B1-react-js-v2lsdju7aiuc


چالش‌های تبدیل CSR به SSR

دو حالت CSR و SSR شاید در 90٪ ساختار‌ و سبک توسعه یکسان باشن ولی همون 10٪ تفاوت‌های بنیادینی رو ایجاد میکنه و باعث بروز برخی چالش‌ها میشه. که در زیر دو مورد از این چالش‌های رو می‌بینید:


فایل‌های استاتیک در مقابل نسخه پویا

در حالت CSR نسخه تولید تنها چند فایل فشرده شده استاتیک هست و میشه با یک هاست معمولی هم پروژه رو لانچ کرد ولی در حالت SSR شما به یک وب سرور node js برای مدیریت Render سمت سرور و یک process manager مثل PM2 برای پایداری اجرای برنامه نیاز دارید. این تفاوت در این هم تفاوت ایجاد می‌کنه که شخصی که توسعه دهده CSR هست الزاما نیاز نیست با node js آشنا باشه ولی در SSR الزامی هست و باید کلیت و دانشی حداقلی از این موضوع داشته باشه. از طرفی CSR رو میشه با یک هاست معمولی هم هندل کرد در حالی که SSR نیاز به محیطی داره که بشه روی اون یک وب سرور node js رو اجرا کرد. این در حالی هست که حالت توسعه این دو تقریبا یکسان هست.


مرورگر در مقابل ایزومورفیک

کدها در CSR فقط در مرورگر اجرا میشه در حالی که در SSR به صورت ایزومورفیک یعنی هم در مرورگر و هم در سرور توسط node js اجرا میشه. این یعنی اگر در کدهایی که در سرور پردازش میشه شما ویژگی‌هایی که خاص مرورگر هست رو فراخوانی کنید به خطا میخورید. مثلا اگر در render یک کلاس Component کد زیر نوشته شده باشه به خطا می خورید:

const path = .pathname;

برای حل این مشکل باید محیط رو تشخصی بدید (یکی از راه‌های تشخیص بررسی وجود شی window است) و بر اساس شرایط کار رو پیش ببرید. مانند:

const path =  (typeof window !== 'undefined') ? 
                            .pathname
                            :
                             req.url    // req is request object of express node JS framework

همچنین برای عملیات‌های اصلی از ساختار‌هایی که از ایزومورفیک پشتیبانی می‌کنند باید استفاده کنید. مثلا برای هندل درخواست‌های ajax در مرورگر و HTTP request یا اصطلاحا server fetch در سرور باید از axios استفاده کرد و پکیج‌هایی که صرفا می‌توانند ajax یا server fetch کنند کارایی ندارن.


متغییر‌های محیطی/ environment variable (فایل env.)

گفتیم که برخی اتفاقات فقط باید در یکی از این دو حالت اتفاق بی‌افته، مثلا اسکریپت‌های مدیریت مارکتینگ یا ابزارهایی مثل چت آنلاین دلیلی نداره توی محیط develop فعال باشن و مزاحمت ایجاد کنن و یا توی محیط Production دلیلی نداره بعضی لاگر‌ها فعال باشن. برای مدیریت این کار از متغییر‌های محیطی یا همون environment variable استفاده میشه. اگر از CRA استفاده می‌کنید این قابلیت در اون وجود داره و می‌تونید ایــنجا در موردش اطلاعات بیشتری کسب کنید.

به عنوان مثال در حالت توسعه یک سرویس‌دهنده لوکال داریم که با آدرس مثلا http://localhost:9090 میشه با API اون ارتباط برقرار کرد ولی در حالت تولید درخواست‌ها باید به سرویس دهنده اصلی بخوره مثلا https://api.site.com. این دست مقادیر متغییر نسبت به محیط رو میشه به کمک متغییرهای محیطی به برنامه پاس داد.


چالش Router و خطای 404

یکی دیگه از چالش‌هایی که در حالت Production رخ میده درست کار نکردن router هست که به خاطر SPA بودن ساختار رخ میده. اگر به عنوان مثال شما نسخه Prodcution یک برنامه CRA رو روی یک هاست معمولی قرار داده باشید و از ساختاری مسیریابی SPA ای مانند React-Router استفاده کرده باشید، صفحه اصلی سایت به درستی نمایش داده میشه. با کلیک روی یک لینک مثلا www.site.com/about-us صفحه درباره ما به درستی نمایش داده میشه ولی وقتی صفحه‌ رو بارگذاری مجدد کنید به خطای سرور 404 می‌خورید.

دلیل این اتفاقا این هست که وب سرور به صورت پیش فرض عنوان بعد از هر اسلش (/) رو نام یک دایرکتوری فرض میکنه در حالی که در برنامه شما این عبارت صرفا یک ادرس شبیه سازی شده هست برای اجرا فلان کامپوننت و اصلا دایرکتوری‌ای با عنوان about-us در ساختار شما وجود نداره.

برای حل این مشکل باید به وب سرور دستور بدیم که اگر فایلی با آدرس ارسالی پیدا شد رو برگردون و اگر هم پیدا نشد درخواست رو به فایل index.html ارسال کنه. با ارسال درخواست‌ها به اون react-router فعال و کامپوننت‌ مورد نظر رندر میشه.

البته این کار وظیفه DevOps هست و ربطی به توسعه دهنده front-end نداره ولی بهتره بدونید. نحوه انجام این کار بستگی به مدل پروژتون که SSR هست یا CSR ،هاست هست یا سرر مجازی ،وب سرورتون چی هست (nginx، apache و ...) هست بستگی داره.

به عنوان مثال در یک هاست معمولی که وب سرور آپاچی داره میشه با تعریف فایل .htaccess اون به شکل زیر این مشکل رو رفع کرد:

<ifModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule (.*) index.html [QA,L]
</ifModule>


چالش‌های هنگام build

موکول کردن تست و بررسی نسخه تولید به زمان تولید برای اکثر توسعه‌دهنده‌ها دردسر‌هایی رو ایجاد کرده و بهتره قبل از این که زیر فورس شدید قرار بگیرید و زندگی به کامتون تلخ بشه قبلش این کار رو بکنید.

ساختاری که در محیط توسعه برای bundle و optimize کردن کد‌ها استفاده میشه واضح هست که با محیط تولید متفاوت هست. مثلا در محیط توسعه کدها روی رم Bundle و در واقع به صورت یک فایل مجازی ساخته میشن در حالی که در حالت تولید فایل‌های واقعی هستن. همچنین در محیط توسعه کدها minify نمیشن و عملیات‌های optimization روی اونها اعمال نمیشه چون هم این کار زمان بر هست و هم برای دیباگ لازم هست کد‌ها به همان صورتی که هستند باشند در حالی که در نسخه تولید عملیات‌های مختلفی برای کم‌حجم کردن و افزایش کارایی کدها انجام میشه.

همین موضوع باعث ایجاد برخی خطاها هنگام build میشه. برای این که خطاهای مختلف روی هم انباشته نشه و روز تولید شما رو نامید نکنه بهتره هر چند وقت پروژه رو build کنید.

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