پیشنهاد می کنم ابتدا مقدمه این سری از مقالات رو بخونید تا پیش زمینه خوبی داشته باشین برای شروع لینک
توسعه دهندگان زبان جاوا اسکریپت در طول سالها، دستورات و قابلیت های بیشتری را به آن اضافه کرده اند. و در ES2015 یا همان ES6 به طور موثر مقدار سینتکس ها و ویژگی ها در این زبان تقریبا دو برابر شد.
هر بار که مرورگر ها نسخه جدیدی را منتشر می کنند، آن نسخه درک ثابتی از نسخه های خاص از زبان JS دارد. از آنجایی که هر نسخه مرورگر ممکن است برای سالهای زیادی مورد استفاده قرار گیرد، توسعهدهندگان باید کد را تنها با استفاده از نسخه ای از زبان جاوا اسکریپت ارسال کنند که توسط حداکثر مرورگرها پشتیبانی شود(مثلا ورژن ES5 زبان جاوا اسکریپت که اکثر مرورگرها پشتیبانی خیلی خوبی از اون دارن). از طرف دیگه توسعه دهندگان تمایل دارند که با استفاده از آخرین ورژن زبان جاوا اسکریپت و بهترین سینتکس، کد بنویسند . به طور ساده تر میشه گفت که بعضی از کاربر ها از نسخه قدیمی مرورگرها استفاده می کنند و به سرعت به سمت نسخه های جدید مرورگرها که درک درستی از ورژن های جدید js دارند نمی روند و از آن طرف هم چون معمولا در نسخه های جدید زبان js کد نویسی ساده تر ، تمیز تر و آسان تر میشه دولوپر ها دوست دارن که بتونن از سینتکس ها و ویژگی های جدید آخرین ونسخه زبان استفاده کنن، برای رفع این مشکل توسعه دهندگان باید کد JS ای را که نوشتهاند به نسخهای معادل از سینتکس قدیمیتر ، کامپایل یا تبدیل کنند تا مرورگرهای بیشتری از برنامه آنها پشتیبانی کند .
برای حل این مشکل در حال حاضر بهترین کار استفاده کردن از کامپایلر ها هستش که پرکاربرد ترین آنها کامپایلر Babel هستش. که این کامپایلر ابزار استانداردی است که برای کامپایل متقابل کد JS در انواع مختلف ورژن های آن استفاده می شود ، و همچنین این کامپایلر دارای تعداد وسیعی از پلاگین ها است که قدرت اون را بیشتر می کنه ! حتی کد را بوسیله اون میتوان برای تبدیل به ماژول فرمت خاص نیز استفاده کرد.
برای مثال قطعه کد زیر را مشاهد کنید که از یکسری قابلیت های ورژن ES6 زبان JS مثل کلمات کلیدی let و const و همچنین توابع arrow و در خط آخر از shorthand object declaration استفاده میکنه :
export const myFunc = () => { let longVariableName = 1; return {longVariableName}; }
حال اگر بوسیله کامپایلر بابل این قطعه از کد را بخواهیم به فرمت CommonJs تبدیل کنیم خروجی ما به شکل زیر می شود :
"use strict" Object.defineProperty(exports, "__esModule", { value: true }); exports.myFunc = void 0; var myFunc = function myFunc() { var longVariableName = 1; return { longVariableName: longVariableName }; }; exports.myFunc = myFunc;
در حال حاضر بیشتر از این کامپایلر برای تبدیل کد های جدید به نسخه ES5 جاوا اسکریپت استفاده میشه که پشتیبانی وسیعی رو مرورگرها از این نسخه دارن.( البته در فریمورکی مثل ری اکت هم از این کامپایلر استفاده وسیعی میشه ! )
علاوه بر این، بسیاری از زبانهای «کامپایل به JS» در صنعت استفاده میشوند ( که برخی از این زبانهای خاص گمنام هستند، برخی برای چند سال محبوب بودند و از آن زمان منقرض شدهاند مثل (CoffeeScript) ) و در مواقعی نیاز است که آنها نیز به نسخه خوبی از جاوا اسکریپت کامپایل شوند .
رایجترین زبان کامپایل به JS که در زمان حاضر استفاده میشود TypeScript است که یک ابرمجموعه با تایپ های استاتیک از JS است که توسط مایکروسافت ایجاد شده است. کامپایلر TypeScript خودش مقادیر تایپ ها را در زمان کامپایل حذف میکند و JS ساده را خروجی میدهد. مشابه با کامپایلر بابل، همچنین میتواند سینتکس جدیدتر را به نسخههای مختلف زبان قدیمی کامپایل کند.
// Input: TypeScript syntax is JS with type annotations const add2 = (x: number, y: number) => { return x + y; }; // Output: plain JS, no type annotations const add2 = (x, y) => { return x + y; };
دلایل متعددی وجود دارد که کد JS را نباید آنطور که ما نوشته ایم به مرورگر تحویل بدهیم :
1- کد نوشته شده با فرمت CommonJS توسط مرورگرها قابل ارزیابی و اجرا نیست !
2-کد نوشته شده در قالب فرمت ES Module می تواند باشد، اما نیاز به کار دقیق دارد تا همه فایل ها و URL های مسیر به درستی تنظیم شوند.
3-مرورگرها مورد استفاده توسط مصرف کنندگان احتمالاً از تمام سینتکس مدرن پشتیبانی نمی کنند
4- کد بیس ( Codebase ) ممکن است از هزاران فایل JS مجزا تشکیل شده باشد و بارگیری هر فایل به صورت جداگانه خیلی زمان بر می شود.
5- منبع اصلی کد حاوی کامنت ها ، فضاهای خالی و نام متغیرهای طولانی تر است و توسعه دهندگان باید تعداد بایت های ارسال شده به مرورگر را به حداقل برسانند تا صفحات سریعتر دریافت و بارگذاری شوند.
6- زبانهایی مانند TypeScript توسط مفسرهای JS پشتیبانی نمیشوند - کد اصلی آنها باید به سینتکس ساده JS کامپایل شود.
به همین دلایل، کد منبع JS ما باید برای استفاده بهینه توسط مرورگرها آماده سازی شود که به این عملیات باندل کردن میگوییم . که این عملیات باید هم در محیط توسعه و هم در خروجی نهایی کد اتفاق بیفتد.
فرآیند باندل کردن از بررسی درخت ایمپورت و اکسپورت ها شروع شده و ابتدا از فایل نقطه ورودی پروژه یا entry point پروژه ماننده (مانند src/index.js) شروع به پردازش میکند. سپس هر فایل ایمپورت شده در این نقطه ورودی به لیست فایل هایی که باید پردازش شوند اضافه می شود.باندلر تمام ایمپورت های درخواستی را پردازش میکند، ترتیب بارگیری آنها را تعیین میکند، و ماژول ها را که در چند فایل قرار داده شده اند را خروجی میدهد و برنامه را هنگام لود فایل باندل شده توسط مرورگر، مقداردهی اولیه میکند.
ابزارهای باندل سازی نیز معمولاً از چندین مرحله پردازش اضافی در طول فرآیند باندل کردن پشتیبانی میکنند. به طور خاص، باندلرها معمولاً به شکل زیر پیکربندی می شوند:
یک کامپایلر مانند Babel یا TypeScript را روی همه فایل های JS/TS اجرا می کنند .
اگر از TS استفاده می کنید، با کامپایلر TS چک می کند تا مطمئن شود که کد واقعاً کامپایل شده است
قابلیت ایمپورت کردن و پردازش منابعی مانند فایل های CSS و تصاویر را فعال می کند
اندازه فایل خروجی پروژه را با کوچک کردن آن (فرآیند minifying کردن که آن را به عنوان uglifying هم میشناسیم) بهینه می کند تا آن را تا حد ممکن کوچک نگه دارد.minifying کد های JS شامل کوچک کردن حجم فایل خروجی کد تا حد امکان است که این فرایند با کارهایی نظیر حذف فضای خالی و کامنت ها، جایگزینی نام های طولانی با نام های کوتاه تر و استفاده از کوتاه ترین نسخه های ممکن سینتکس زبان.به علاوه، minifier ها می توانند کد مرده و بی کاربرد را شناسایی کرده و آن را حذف کنند. و کد هایی از جاوا اسکریپت که مثلا با فلگی مثل زیر نوشته شده اند را if (process.env.NODE_ENV !== 'production')
حذف میکند از فایل نهایی...
همان خروجی کامپایلشده بوسیله کامپایلر بابل در بالا وقتی کوچکسازی میشود به این شکل تقریبا تبیدل می شود:
"use strict"Object.defineProperty(exports,"__esModule",{value:!0}),exports.myFunc=void 0;var myFunc=function(){return{longVariableName:1}};exports.myFunc=myFunc;
ابزار Webpack پرکاربردترین باندلر JS است. ابزارهای دیگری مانند Parcel، Snowpack و ESBuild نقش های مشابهی را انجام می دهند، اما هرکدام مزایا و محدودیت های خاص خودشان را دارند و برای اهداف مشخصی استفاده می شوند
به دلیل انجام این فرآیند ها روی کد ما، کد لود شده توسط یک مرورگر به یک فرم کاملا غیر قابل شناسایی تبدیل میشود که باعث می شود که خوانایی و اشکال زدایی و دیباگ کردن آن خیلی سخت و غیرممکن شود . برای حل این مشکلات، ابزارهای توسعه نقشه های کد را می نویسند ، که بوسیله این نقشه ، کد تبدل شده را به نقاط مرتبط با کد منبع اصلی مرتبط می کنند . این کار اجازه می دهد تا debuggers مرورگر برای نشان دادن کد منبع اصلی، حتی اگر یک زبان غیر از جاوا اسکریپت باشه نیز مشکلی نداشته باشد . مرورگر "منبع اصلی کد " را در فایل های مجزا نشان می دهد و به توسعه دهندگان اجازه می دهد تا "منبع اصلی" را با تنظیم نقطه های breakpoint و مشاهده محتویات متغیر پیدا کنند.
ران تایم Node.js
یک ران تایم یا محیط اجرای کد های JS خارج از محیط مرورگر است. معادل JRE برای جاوا است یا NET Framework SDK یا ران تایم اجرایی پایتون است. که شامل موتور V8 کروم است که برای استفاده به عنوان یک فایل اجرایی مستقل، به همراه کتابخانه های استاندارد از APIها برای تعامل با سیستم فایل، ایجاد سوکتها و سرورها، و همراه یا موارد دیگر پکیج شده است.
پکیج منیجر NPM
در حالت کلی "NPM" سه معنی دارد:
الف : یک منبع رجیستری برای کتابخانه های جاوا اسکریپت است که میزبان کتابخانه های JS شخص ثالث و کتاب خانه های های منتشر شده توسط انجمن است.
ب : یک CLI منبع باز است که برای نصب بسته ها از آن رجیستری استفاده می شود
پ : NPM شرکتی است که رجیستری را اجرا می کند و کلاینت CLI را توسعه می دهد (که اخیراً توسط مایکروسافت خریداری شده است)
کتابخانه ها و بسته های نصب شده بوسیله NPM در پوشه node_modules قرار می گیرند. مثلا دستور ، npm install redux آخرین آرشیو منتشر شده از این پکیج redux را از سرورهای رجیستری NPM دانلود می کند و محتویات را در پوشه node_modules/redux قرار می دهد.
مدیریت بسته Yarn یک ابزار جایگزین برای NPM است که همان پکیج ها ها را از همان رجیستری NPM عمومی نصب می کند. این ابزار همان قابلیتهای اصلی ابزار npm را دارد، اما گزینههای پیکربندی متفاوتی را ارائه میکند.
ابزارهای ساخت Node یا Node Build Tools
از آنجایی که اکثر ابزارهای کمکی توسعه JS توسط توسعه دهندگان JS برای توسعه دهندگان JS نوشته می شوند، خود این ابزارها معمولاً بوسیله زبان JS نوشته می شوند، که شامل ابزارهای پرکاربرد مانند Babel، Webpack، ESLint و بسیاری دیگر هستند ، بنابراین، برای اجرای آنها، باید Node.js را در محیط توسعه خود نصب کرده باشید ( در کامپیوتری که قصد برنامه نویسی با جاوا اسکریپت را دارید ) ( البته همانطور که بعدا توضیح داده میشود ، برای اجرای کد کلاینت خود در مرورگر نیازی به نصب Node.js روی سرور ندارید، مگر اینکه خود برنامه سرور خود را نیز بوسیله جاوا اسکریپت نوشته باشید.)
اخیراً روند جدیدی از ایجاد ابزارهای ساخت JS بوسیله زبان های پرسرعتی مثل Rust یا Go ایجاد شده است که هدف آنها ایجاد کامپایل و باندلینگ بسیار سریعتر از طریق استفاده از کد native و موازی سازی است . البته بسیاری از این ابزارها هنوز به استفاده زیاد بوسیله عموم برنامه نویس های جاوا اسکریپت نرسیدهاند، اما پتانسیل بالقوه آنها به اندازهای بزرگ است که این ابزارها احتمالاً در آینده فراگیر میشوند.البته نمونه هایی عملیاتی از این ابزار های ساخت هم داریم که به مرحله اجرا رسیده اند و سرعت توسعه رو به مقدار زیادی بالا برده اند مانند کامپایلری که فریم ورک Next js در ورژن 12 ام خودش معرفی کرده است که براساس زبان Rust نوشته شده است و مدعی هستند حدود 3 برابر قابلیت Fast Refresh را بالا برده است و حدود 5 برابر هم سرعت build پروژه را بالا برده !
سرورهای توسعه یا Dev Servers
از آنجا که کد اصلی که ما به عنوان توسعه دهنده آن را مینویسیم باید بارها و بارها مجددا دوباره کامپایل شود و به صورت محلی یا لوکال برای ما نمایش داده شود ، فرآیند های توسعه کد معمولا شامل راه اندازی یک سرور Development هستند ، یک فرایند جداگانه ای است که تغییر کد را تشخیص می دهد و تغییرات را مثلا در مرورگر برای ما نمایش می دهند . سرور DEV به طور معمول به عنوان یک پروکسی HTTP عمل می کند و درخواست های ارسال شده برای داده ها و منابع را به یک سرور نرم افزاری واقعی منتقل می کند.
یک مثال از کارکرد آنها به این صورت است:
فرآیند سرور برنامه ( سرور APP ) به پورت 8080 گوش می کند
فرایند سرور توسعه ( سرور dev ) برنامه های کلاینتی ما به پورت 3000 گوش می کند
در سمت توسعه کلاینتی ،توسعهدهنده برای مشاهده صفحه به آدرس http://localhost:3000 در کامپیوتر خودش مراجعه میکند سپس سرور توسعه در پورت 3000 درخواست را دریافت می کند، صفحه HTML و کد های JS را از حافظه بارگیری می کند و آن را نمایش می دهد. هنگامی که مرورگر http://localhost:3000/images/avatar.png را درخواست می کند، سرور dev آن را به سرور APP در http://localhost:8080/images/avatar.png ارسال می کند. به طور مشابه، درخواست داده توسط مرورگر برای دریافت http://localhost:3000/items به سرور برنامه در آدرس http://localhost:8080/items ارسال می شود و پاسخ از طریق سرور توسعه به مرورگر ارسال می شود .
ابزاری مانند Webpack یک سرور توسعهدهنده از پیش ساخته شده در دسترس ما قرار می دهد و ابزارهای دیگری مانند Create-React-App اغلب در اطراف سرور توسعه دهنده Webpack قرار میگیرند تا قابلیتهای بیشتری را برای ما فراهم کنند.
قابلیت Hot Module Reloading
به طور معمول، کامپایل مجدد یک برنامه وب نیاز به بارگیری کامل صفحه برای مشاهده کد تغییر یافته در حال اجرا دارد. وقتی صفحه بهروزرسانی میشود، هر state یا حالتی که در برنامه قرار داده شده است را پاک میکند.
ابزارهایی مانند Webpack توانایی "Hot Module Reloading" را ارائه می دهند. هنگامی که یک فایل ویرایش می شود، سرور توسعه تغییرات را دوباره کامپایل می کند، سپس یک اعلان به کد کلاینت در مرورگر ارسال می کند. سپس کلاینت میتواند با توجه به اعلان رسیده تغییرات فایل ها را متوجه شود، آنگاه نسخه جدید کد را مجدداً وارد می کند و کد قدیمی را با کد جدید جایگزین کند و برنامه همچنان در حال اجرا می ماند.
ابزارهای دیگری مانند کتابخانه React دارای حالتی به نام "Fast Refresh" هستند که می توانند از این قابلیت بارگیری مجدد ماژول برای تعویض بخش های خاصی از برنامه استفاده کنند، مانند جایگزینی اجزای کامپوننت های React در زمان درست.
ارائه خروجی Build شده :
خروجی یک فرآیند Build باندل شده ، پوشه ای پر از فایل های JS، HTML، CSS و فایل های تصویری استاتیک است. در اینجا ساختار خروجی یک برنامه معمولی React آمده است:
/my-project/build - index.html /static /css - main.34928ada.chunk.css - 2.7110e618.chunk.css /js - 2.e5df1c81.chunk.js - 2.e5df1c81.chunk.js.map - main.caa84d88.chunk.js - main.caa84d88.chunk.js.map - runtime-main.d653cc00.js - runtime-main.d653cc00.js.map /media - image1.png - image2.jpg - fancy-font.woff2
اینها فایل های استایک ساده ای هستند که می توانند توسط هر وب سروری پردازش شوند. و برای استقرار این فایلها و انتشار سایت، باید در مکانی مناسب در دستگاهی که برنامه سرور را میزبانی میکند، آپلود شوند. این کار اغلب با استفاده از پروتکل انتقال فایل مانند SFTP یا FTP انجام می شود.
افزودن ویژگی با Polyfills
بسیاری از ویژگی ها جدید و API های جدید وجود دارند که مرورگرهای قدیمی از آنها پشتیبانی نمی کنند، ونمی توان آنها را با سینتکس کامپایل به گذشنه( backwards-compiling ) مدیریت کرد. و شامل توابع داخلی، کلاس ها و انواع داده می شوند. برای نمونه می توان از متد String.padStart و ساختار داده Map نام برد .
با این حال، برخی از این موارد را هنوز هم می توان با Polyfills ارائه شده توسط توسعه دهندگان به مروگر اضافه کرد.Polyfills کد اضافی است که هنگام بارگذاری یک برنامه اجرا می شود، تشخیص می دهد که آیا یک ویژگی مشخص در زمان اجرا در محیط فعلی وجود دارد یا خیر، و یک پیاده سازی مصنوعی معادل را به صورت پویا و داینامیک اضافه می کند.
به عنوان مثال، یک polyfill برای متد ()String.padStart ممکن است چیزی شبیه به کد زیر باشد :
if (!String.prototype.padStart) { String.prototype.padStart = function padStart(targetLength,padString) { // actual logic here } }
تقسیم کردن کدها یا Code Splitting
حتی با انجام عملیات کوچکسازی( minification )، کد های نهایی ما میتوانند بسیار بزرگ شوند (250K، 1MB یا بدتر) . و بزرگ شدن و حجیم شدن یک فایل سرعت دریافت آن را کاهش می دهد و در نهایت باعث کاهش عملکرد سایت نوشته شده خواهیم بود ، که اغلب به دلیل استفاده از کتابخانه های شخص ثالث برای انجام عملکردهای اضافی است.
تقسیم کد یا Code Splitting به باندلرها اجازه می دهد تا فایل های بسیار بزرگ را به قطعات کوچکتر ( chunks ) تقسیم کنند. که این chunk های اضافی تولید شده یا بهعنوان تگهای اضافی به صفحه HTML میزبان اضافه میشوند، یا در حین اجرای برنامه به صورت داینامیک دانلود میشوند.
هر کدام از این قطعات ممکن است رفتار متفاوتی را داشته باشند برخی از این تکه ها ممکن است فقط حاوی کد کتابخانه شخص ثالث باشند که به آنها «vendor chunks» میگوییم که قابلیت این را دارند کهتوسط مرورگر ذخیره شوند و فقط اولین باری که کاربر از یک سایت بازدید میکند دانلود شود. یا برخی دیگر از تکهها ممکن است منطق و لاجیک مشترکی باشند که بین چندین بخش از یک برنامه به اشتراک گذاشته میشوند، مانند توابع utilities که آنها را نوشته ایم ودر چند قسمت مختلف برنامه مانند صفحه اصلی سایت و صفحه مدیریت استفاده شود. برخی از تکهها ممکن است تنها زمانی که کاربر یک ویژگی خاص را فعال میکند، به صورت"lazy loaded" باشند.
به عنوان مثال، یک ویرایشگر متن ممکن است 500K اضافی به بسته نهایی برنامه ما اضافه کند، اما فقط در یک مدال (Modal) خاص استفاده شود، و کد مدالی که نوشته ایم می تواند به صورت داینامیک کتابخانه ویرایشگر متن را ایمپورت کند. سپس باندلر آن ایمپورت پویا را شناسایی میکند، کد ویرایشگر را به یک تکه جداگانه تقسیم میکند، و این قطعه تنها زمانی دانلود میشود که کاربر آن مدال را باز کند.