ساخت (Build) برنامههای Angular
Angular CLI کار ساخت و کامپایل برنامه را به صورت خودکار انجام داده و خروجی را در مسیری مشخص درج میکند. در اینجا میتوان گزینههایی را بر اساس نوع کامپایل مدنظر مانند کامپایل برای حالت توسعه و یا کامپایل برای حالت توزیع نهایی، انتخاب کرد. همچنین مباحث bundling و یکی کردن تعداد بالای ماژولهای برنامه در آن لحاظ میشوند تا برنامه در حالت توزیع نهایی، سبب 100ها رفت و برگشت به سرور برای دریافت ماژولهای مختلف آن نشود. به علاوه مباحث uglification (به نوعی obfuscation کدهای جاوا اسکریپتی نهایی) و tree-shaking (حذف کدهایی که در برنامه استفاده نشدهاند؛ یا کدهای مرده) نیز پیاده سازی میشوند. با انجام tree-shaking، نه تنها اندازهی توزیع نهایی به کاربر کاهش پیدا میکند، بلکه مرورگر نیز حجم کمتری از کدهای جاوااسکریپتی را باید تفسیر کند.
برای شروع میتوان از دستور ذیل برای مشاهدهی تمام گزینههای مهیای ساخت برنامه استفاده کرد:
> ng build --help
"apps": [ { "outDir": "dist",
فایل | توضیح |
inline.bundle.js | WebPack runtime از آن برای بارگذاری ماژولهای برنامه و چسباندن قسمتهای مختلف به یکدیگر استفاده میشود. |
main.bundle.js | شامل تمام کدهای ما است. |
polyfills.bundle.js | Polyfills - جهت پشتیبانی از مرورگرهای مختلف. |
styles.bundle.js | شامل بسته بندی تمام شیوه نامههای برنامه است |
vendor.bundle.js | کدهای کتابخانههای ثالث مورد استفاده و همچنین خود Angular، در اینجا بسته بندی میشوند. |
روشی برای بررسی محتوای bundleهای تولید شده
تولید bundleها در جهت کاهش رفت و برگشتهای به سرور و بالا بردن کارآیی برنامه ضروری هستند؛ اما دقیقا این بسته بندیها شامل چه اطلاعاتی میشوند؟ این اطلاعات را میتوان از فایلهای source map تولیدی استخراج کرد و برای این منظور میتوان از برنامهی source-map-explorer استفاده کرد.
روش نصب عمومی آن:
> npm install -g source-map-explorer
> source-map-explorer dist/main.bundle.js
یک مثال: ساخت برنامهی مثال قسمت چهارم - تنظیمات مسیریابی در حالت dev
در ادامه، کار Build همان مثالی را که در قسمت قبل توضیح داده شد، بررسی میکنیم. برای این منظور از طریق خط فرمان به ریشهی پوشهی اصلی پروژه وارد شده و دستور ng build را صادر کنید. یک چنین خروجی را مشاهده خواهید کرد:
D:\Prog\angular-routing>ng build Hash: 123cae8bd8e571f44c31 Time: 33862ms chunk {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 158 kB {4} [initial] [rendered] chunk {1} main.bundle.js, main.bundle.js.map (main) 14.7 kB {3} [initial] [rendered] chunk {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial] [rendered] chunk {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.34 MB [initial] [rendered] chunk {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]
<!doctype html> <html> <head> <meta charset="utf-8"> <title>AngularRouting</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root>Loading...</app-root> <script type="text/javascript" src="inline.bundle.js"> </script><script type="text/javascript" src="polyfills.bundle.js"> </script><script type="text/javascript" src="styles.bundle.js"> </script><script type="text/javascript" src="vendor.bundle.js"> </script><script type="text/javascript" src="main.bundle.js"></script> </body> </html>
یک نکته: زمانیکه دستور ng serve -o صادر میشود، در پشت صحنه دقیقا همین دستور ng build صادر شده و اطلاعات را درون حافظه تشکیل میدهد. اما اگر کار ng build را دستی انجام دهیم، اینبار ng serve -o اطلاعات را از پوشهی dist دریافت میکند. بنابراین در حین کار با ng serve -o نیازی به build دستی پروژه نیست.
سؤال: چرا حجم فایل endor.bundle.js اینقدر بالا است و شامل چه اجزایی میشود؟
نکتهای که در اینجا وجود دارد، حجم بالای فایل vendor.bundle.js آن است که 2.34 MB میباشد:
چون دستور ng build بدون پارامتری ذکر شدهاست، برنامه را برای حالت توسعه Build میکند و به همین جهت هیچگونه بهینه سازی در این مرحله صورت نخواهد گرفت. برای بررسی محتوای این فایل میتوان دستور ذیل را در ریشهی اصلی پروژه صادر کرد:
> source-map-explorer dist/vendor.bundle.js
همانطور که مشاهده میکنید، در حالت بهینه سازی نشده و Build برای توسعه، کامپایلر Angular حدود 41 درصد حجم فایل vendor.bundle.js را تشکیل میدهد. به علاوه ماژولها و قسمتهایی را ملاحظه میکنید که اساسا برنامهی فعلی مثال ما از آنها استفاده نمیکند؛ مانند http، فرمها و غیره.
سفارشی سازی Build برای محیطهای مختلف
اگر به پروژهی تولید شدهی توسط Angular CLI دقت کنید، حاوی پوشهای است به نام src\environments
هدف از فایلهای environment برای نمونه تغییر آدرس توزیع برنامه در حالت توسعه و ارائه نهایی است.
همچنین در اینجا میتوان نحوهی بهینه سازی فایلهای تولیدی را توسط Build Targets مشخص کرد و اینکار توسط ذکر پرچم prod-- (مخفف production) صورت میگیرد.
در ادامه، تفاوتهای دستورهای ng build و ng build --prod را ملاحظه میکنید:
- با اجرای ng build، از فایل environment.ts استفاده میشود؛ برخلاف حالت اجرای ng build --prod که از فایل environment.prod.ts استفاده میکند.
- Cache-busting در حالت ارائهی نهایی، به تمام اجزای پروژه اعمال میشود؛ اما در حالت توسعه فقط برای تصاویر قید شدهی در فایلهای css.
- فایلهای source map فقط برای حالت توسعه تولید میشوند.
- در حالت توسعه، cssها داخل فایلهای js تولیدی قرار میگیرند؛ اما در حالت ارائهی نهایی به صورت فایلهای css بسته بندی میشوند.
- در حالت توسعه برخلاف حالت ارائهی نهایی، کار uglification انجام نمیشود.
- در حالت توسعه برخلاف حالت ارائهی نهایی، کار tree-shaking یا حذف کدهای مرده و بدون ارجاع، انجام نمیشود.
- در حالت توسعه برخلاف حالت ارائهی نهایی، کار AOT انجام نمیشود. در اینجا AOT به معنای Ahead of time compilation است.
- در هر دو حالت توسعه و ارائهی نهایی کار bundling و دسته بندی فایلها انجام خواهد شد.
به همین جهت است که ng build سریع است؛ اما حجم بالاتری را هم تولید میکند. چون بسیاری از بهینه سازیهای حالت ارائهی نهایی را به همراه ندارد.
دستورات build برای حالت توسعه و ارائهی نهایی
برای حالت توسعه، هر 4 دستور ذیل یک مفهوم را دارند و به همین جهت مورد ng build متداولتر است:
>ng build --target=development --environment=dev >ng build --dev -e=dev >ng build --dev >ng build
برای حالت ارائهی نهایی، هر 3 دستور ذیل یک مفهوم را دارند و به همین جهت مورد ng build --prod متداولتر است:
>ng build --target=production --environment=prod >ng build --prod -e=prod >ng build --prod
همچنین هر کدام از این دستورات را توسط پرچمهای ذیل نیز میتوان سفارشی سازی کرد:
پرچم | مخفف | توضیح |
sourcemap-- | sm- | تولید سورسمپ |
aot-- | Ahead of Time compilation | |
watch-- | w- | تحت نظر قرار دادن فایلها و ساخت مجدد |
environment-- | e- | محیط ساخت |
target-- | t- | نوع ساخت |
dev-- | مخفف نوع ساخت جهت توسعه | |
prod-- | مخفف نوع ساخت جهت ارائه نهایی |
برای مثال در حالت prod، سورسمپها تولید نخواهند شد. اگر علاقمندید تا این فایلها نیز تولید شوند، پرچم souremap را نیز ذکر کنید.
و یا اگر برای حالت dev میخواهید AOT را فعالسازی کنید، پرچم aot-- را در آنجا قید کنید.
یک مثال: ساخت برنامهی مثال قسمت چهارم - تنظیمات مسیریابی در حالت prod
تا اینجا خروجی حالت dev ساخت برنامهی قسمت چهارم را بررسی کردیم. در ادامه دستور ng build --prod را در ریشهی پروژه صادر میکنیم:
D:\Prog\angular-routing>ng build --prod Hash: f5bd7fd555a85af8a86f Time: 39932ms chunk {0} polyfills.18173234f9641113b9fe.bundle.js (polyfills) 158 kB {4} [initial] [rendered] chunk {1} main.c6958def7c5f51c45261.bundle.js (main) 50.3 kB {3} [initial] [rendered] chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 69 bytes {4} [initial] [rendered] chunk {3} vendor.b426ba6883193375121e.bundle.js (vendor) 1.37 MB [initial] [rendered] chunk {4} inline.8cec210370dd3af5f1a0.bundle.js (inline) 0 bytes [entry] [rendered]
همانطور که ملاحظه میکنید، اینبار نه تنها حجم فایلها به میزان قابل ملاحظهای کاهش پیدا کردهاند، بلکه این نامها به همراه یک سری hash هم هستند که کار cache-busting (منقضی کردن کش مرورگر، با ارائهی نگارشی جدید) را انجام میدهند.
در ادامه اگر بخواهیم مجددا برنامهی source-map-explorer را جهت بررسی محتوای فایلهای js اجرا کنیم، به خطای عدم وجود sourcemapها خواهیم رسید (چون در حالت prod، به صورت پیش فرض غیرفعال هستند). به همینجهت برای این مقصود خاص نیاز است از پرچم فعالسازی موقت آن استفاده کرد:
> ng build --prod --sourcemap > source-map-explorer dist/vendor.b426ba6883193375121e.bundle.js
همانطور که در تصویر نیز مشخص است، اینبار کامپایلر Angular به همراه تمام ماژولهایی که در برنامه ارجاعی به آنها وجود نداشتهاست، حذف شدهاند و کل حجم بستهی Angular به 366 KB کاهش یافتهاست.
بررسی دستور ng serve
تا اینجا برای اجرای برنامه در حالت dev از دستور ng serve -o استفاده کردهایم. کار ارائهی برنامه توسط این دستور، از محتوای کامپایل شدهی درون حافظه با مدیریت webpack انجام میشود. به همین جهت بسیار سریع بوده و قابلیت live reload را ارائه میدهد (نمایش آنی تغییرات در مرورگر، با تغییر فایلها).
همانند تمام دستورات دیگر، اطلاعات بیشتری را در مورد این دستور، از طریق راهنمای آن میتوان به دست آورد:
> ng serve --help
که شامل این موارد هستند (علاوه بر تمام مواردی را که در حالت ng build میتوان مشخص کرد؛ مثلا ng serve --prod -o):
پرچم | مخفف | توضیح |
open-- | o- | بازکردن خودکار مرورگر پیش فرض. حالت پیش فرض آن گشودن مرورگر توسط خودتان است و سپس مراجعهی دستی به آدرس برنامه. |
port-- | p- | تغییر پورت پیش فرض مانند ng server -p 8626 |
live-reload-- | lr- |
فعال است مگر اینکه آنرا با false مقدار دهی کنید. |
ssl-- | ارائه به صورت HTTPS | |
proxy-config-- | pc- | Proxy configuration file |
استخراج فایل تنظیمات webpack از Angular CLI
Angular CLI برای مدیریت build، در پشت صحنه از webpack استفاده میکند. فایل تنظیمات آن نیز جزئی از فایلهای توکار این ابزار است و قرار نیست به صورت پیش فرض و مستقیم توسط پروژهی جاری ویرایش شود. به همین جهت آنرا در ساختار پروژهی تولید شده، مشاهده نمیکنید.
اگر علاقمند به سفارشی سازی بیشتر این تنظیمات پیش فرض باشید، ابتدا باید آنرا اصطلاحا eject کنید و سپس میتوان آنرا ویرایش کرد:
> ng eject Ejection was successful. To run your builds, you now need to do the following commands: - "npm run build" to build. - "npm run test" to run unit tests. - "npm start" to serve the app using webpack-dev-server. - "npm run e2e" to run protractor. Running the equivalent CLI commands will result in an error. ============================================ Some packages were added. Please run "npm install".
در این حالت است که فایل webpack.config.js به ریشهی پروژه جهت سفارشی سازی شما اضافه خواهد شد. همچنین فایلهای .angular-cli.json، package.json نیز جهت درج این تغییرات ویرایش میشوند.
و اگر در این لحظه پشیمان شدهاید (!) فقط کافی است تا این مرحلهی جدید commit شدهی به مخزن کد را لغو کنید و باز هم به همان Angular CLI قبلی میرسید.