بهینه سازی اپلیکیشن های انگولار (Angular) — به زبان ساده
انگولار محبوبترین فریمورک برای ساختن وب اپلیکیشنهای تکصفحهای است. با این حال منظور از تکصفحهای این نیست که اپلیکیشن شما باید حتماً دارای یک صفحه باشد. میتوان با استفاده از انگولار وبسایتهایی با دهها صفحه ساخت. خود فریمورک (جدیدترین نسخهها) به لطف تیم و جامعه خارقالعادهای که پیرامون آن شکل گرفته بهینهسازی زیادی یافته است؛ با این وجود زمانی که در مورد عملکرد انگولار صحبت میکنیم، چند نکته وجود دارد که همواره باید رعایت شوند تا اپلیکیشنها سریعتر و بهتر اجرا شوند.
در همین راستا در ادامه در مورد بهینه سازی اپلیکیشن های انگولار به صورت مفصلتری صحبت خواهیم کرد.
بهینهسازی بسته اصلی با Lazy Loading
زمانی که اپلیکیشن در مرحله Production بدون گزینه «بارگذاری با تأخیر» (Lazy Loading) ساخته شود، این احتمال بالا است که این فایلها در پوشه dist ایجاد شوند:
- polyfills.js
- scripts.js
- runtime.js
- styles.css
- main.js
polyfills.js برای ساختن اپلیکیشنهایی طراحی شده است که با مرورگرهای مختلف سازگاری داشته باشند. چون ما کد خود را با جدیدترین ویژگیها مینویسیم و ممکن است همه مرورگرها از برخی از این ویژگیها پشتیبانی نکنند.
scripts.js شامل اسکریپتی است که در بخش اسکریپتهای فایل angular.json اعلان میشود.
1"scripts": [
2 "myScript.js",
3]
runtime.js بارگذار webpack است. این فایل شامل ابزارهای webpack است که برای بارگذاری فایلهای دیگر مورد نیاز است. Style.css شامل همه استایلهایی است که در بخش styles فایل angular.json اعلان میشوند.
1"styles": [
2 "src/styles.css",
3 "src/my-custom.css"
4],
main.js همه کدها شامل کامپوننتها (کدهای ts، Html و CSS) همچنین pipe-ها، directive-ها، سرویسها و دیگر ماژولهای ایمپورت شده از جمله انواع شخص ثالث را در بر میگیرد.
همان طور که میبینید main.js در طی زمان بزرگ و بزرگتر شده است و این وضعیت خود یک معضل از دید مرورگر است که باید آن را دانلود، تجزیه، اجرا و رندر کند. این وضعیت نه تنها برای کاربران موبایل با سرعت اینترنت محدود؛ بلکه حتی برای کاربران دسکتاپ نیز مشکل عمدهای محسوب میشود.
سادهترین روش برای حل این مشکل، افراز کردن اپلیکیشن به چند ماژول lazy است. زمانی که از ماژولهای lazy برای هر ماژول استفاده میکنیم، انگولار کد خود را به این منظور ایجاد میکند و این کد نیز تا زمانی که نیازی به آن نباشد (معمولاً از طریق یک route مطلع میشود) بارگذاری نخواهد شد.
برای نشان دادن این موضوع دو کامپوننت ایجاد کردهایم که یکی app.component و دیگری second.component نام دارد. هر دو آنها در app.madule قرار دارند و از این رو هیچ چیز lazy در اینجا وجود ندارد. App.component بسیار ساده است و دو دکمه برای ناوبری به Second.componen بازگشت به App.component دارد.
با این وجود کامپوننت دوم شامل متن بسیار زیادی (در حدود 1 مگابایت) در قالب خود است.
از آنجا که از هیچ خصوصیت lazy استفاده نشده است زمانی که این اپلیکیشن build شود با یک فایل main.js بزرگ مواجه میشویم که شامل همه کد از هر دو فایل app.component و second.component است.
اینک اگر به برگه network در بخش ابزارهای توسعهدهنده مرورگر کروم بروید، میبینید که main.js بسیار بزرگ است و اندازهای در حدود 1.3 مگابایت دارد.
مشکل این است که در اغلب موارد، کاربران از صفحه اصلی بازدید میکنند و نه از یک صفحه خاص و از این رو بارگذاری همه صفحههای دیگر راهحل مناسبی محسوب نمیشود. ما میتوانیم یک ماژول lazy برای کامپوننت دوم تولید کنیم که تنها در مواقع نیاز فعال شود. یعنی صرفاً زمانهایی بارگذاری شود که کاربر به آن صفحه مراجعه میکند. بدین ترتیب فایل main.js ما بسیار کوچکتر میشود و سرعت بارگذاری صفحه اصلی کاهش مییابد.
هنگامی که از بارگذاری با تأخیر استفاده میکنیم، پس از فرایند buid یک فایل جدید مانند 4.386205799sfghe4.js ایجاد میشود. این آن بخشی است که ماژول lazy ساخته است و در آغاز کار بارگذاری نخواهد شد. در ادامه وقتی که اپلیکیشن باز شود، میبینیم که main.js بسیار کوچک (266 کیلوبایت) است.
و تنها زمانی که به صفحه دوم برویم فایل جدید (1 مگابایت) بارگذاری میشود.
با این وجود بارگذاری هر بخش به این ترتیب روی عملکرد ناوبری نیز تأثیر میگذارد، چون کندتر شده است. خوشبختانه انگولار روشی برای حل این مشکل نیز ارائه کرده است که با استفاده از راهکار PreloadingStrategy اجرا میشود. ما میتوانیم به انگولار بگوییم که ماژول اصلی (main.js) ما را بارگذاری کند و وقتی که بارگذاری کامل و اجرا شد، بلافاصله اقدام به بارگذاری ماژولهای lazy در پسزمینه بکند و بدین ترتیب زمانی که کاربران به صفحههای lazy میروند، همه چیز از قبل دانلود شده است. کد مثال برای پیش-بارگذاری همه ماژولها به صورت زیر است:
1import { PreloadAllModules, RouterModule } from '@angular/router';
2RouterModule.forRoot(
3[
4 {
5 path: 'second',
6 loadChildren: './second/second.module#SecondModule'
7 }
8], {preloadingStrategy: PreloadAllModules})
بنابراین همواره باید در نظر داشته باشید که تا آنجایی که امکان دارد از ماژولهای lazy بیشتری با نوعی راهبرد پیش–بارگذاری استفاده کنید. بدین ترتیب فایل main.js شما کوچک میماند و این به آن معنی است که سرعت دانلود افزایش یافته و صفحه اصلی سریعتر رندر میشود.
دیباگ کردن بستهها با Webpack Bundle Analyzer
حتی اگر پس از افراز منطق اپلیکیشن به ماژولهای lazy همچنان اندازهی بسته اصلی شما بالا بود، میتوانید از Webpack Bundle Analyzer برای بهینهسازی بیشتر آن استفاده کنید. دقت کنید که منظور ما از اندازه بزرگ، در واقع حجم بالاتر از 1 مگابایت برای اپلیکیشنهای کوچک رو به متوسط است. این بسته npm امکان بصریسازی اندازه فایلهای خروجی Webpack را با یک نقشه درختی قابل بزرگنمایی تعاملپذیر میدهد. قبل از هر چیز این افزونه را به عنوان یک وابستگی dev در پروژه انگولار خود نصب کنید:
npm install --save-dev webpack-bundle-analyzer
سپس فایل package.json را با افزودن این خط زیر بخش scripts تغییر دهید:
1"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json"
توجه داشته باشید که dist/stats.json در پروژه شما ممکن است متفاوت باشد. برای نمونه اگر فایلهای بسته شما در مسیر dist/browser ایجاد شده باشند، میتوانید دستور را به صورت زیر اصلاح کنید:
dist/browser/stats.json
در نهایت دستور زیر را اجرا کنید:
npm run bundle-report
بدین ترتیب یک build پروداکشن ایجاد میشود که آماری در مورد هر بسته ارائه میکند و به کمک webpack bundle analyzer میتوانیم یک بصریسازی از فایلهای خود به صورت یک نقشه درختی قابل بزرگنمایی و تعاملپذیر داشته باشیم:
در این نقشه میبینیم که کدام ماژول یا فایلها در هر بسته مورد استفاده قرار گرفتهاند. این وضعیت موجب میشود که بتوانیم به سرعت متوجه شویم چه چیزهایی باید در بستهها قرار گیرند یا حذف شوند.
ایجاد چند ماژول مشترک کوچک
داشتن ماژولهای مشترک با توجه به اصل DRY یعنی «عدم تکرار کد» رویه مناسبی محسوب میشود؛ اما در پارهای موارد ماژول مشترک نیز میتواند بزرگ و بزرگتر شود. برای نمونه اگر یک SharedModule داشته باشیم که شامل ماژول/کامپوننت/pipe-های زیاد دیگری باشد، ایمپورت کردن آن در app.module موجب افزایش اندازه بسته main.js میشود، چون ما نه تنها نیازهای ماژول اصلی را ایمپورت خواهیم کرد؛ بلکه همه موارد غیرضروری دیگر نیز همراه با SharedModule وارد پروژه میشوند. برای اجتناب از این وضعیت میتوان ماژول مشترک دیگری مانند HomeSharedModule ایجاد کرد که صرفاً شامل کامپوننتهایی باشد که ماژول اصلی و کامپوننتهای آن نیاز دارند.
داشتن چندین ماژول مشترک کوچک بهتر از یک ماژول مشترک بزرگ است.
استفاده از بارگذاری با تأخیر برای تصاویری که در صفحه دیده نمیشوند
وقتی که صفحه اصلی را برای دفعه نخست بارگذاری میکنیم، ممکن است تصاویری داشته باشیم که در دید کاربر نباشند و کاربر باید اسکرول کند تا بتواند این تصاویر را ببیند. با این وجود این تصاویر هم در زمان بارگذاری صفحه دانلود میشوند و در صورتی که تعداد آنها زیاد باشد، میتواند موجب تأثیر منفی روی عملکرد شود. برای جلوگیری از این مشکل میتوانیم تصاویر را به صورت lazy بارگذاری بکنیم تا صرفاً زمانی که کاربر به آنها میرسد دانلود شوند. یک API جاوا اسکریپت به نام Intersection Observer API وجود دارد که امکان بارگذاری با تأخیر محتوا را به سادگی ارائه میکند. به علاوه میتوانیم یک دایرکتیو برای ایجاد قابلیت استفاده مجدد بسازیم.
استفاده از اسکرول مجازی برای لیستهای بزرگ
نسخه 7 فریمورک انگولار قابلیت اسکرول مجازی را در CDK ارائه کرده است. اسکرول مجازی عناصر را بر اساس بخشهای قابل مشاهده یک لیست بارگذاری کرده یا از بارگذاری خارج میکند و بدین ترتیب اپلیکیشن بسیار سریعتر میشود.