جاوا اسکریپت با تقسیم کد

بارگیری منابع بزرگ جاوا اسکریپت به طور قابل توجهی بر سرعت صفحه تأثیر می گذارد. تقسیم جاوا اسکریپت خود به تکه های کوچکتر و دانلود فقط مواردی که برای عملکرد صفحه در هنگام راه اندازی لازم است، می تواند پاسخگویی بارگذاری صفحه شما را تا حد زیادی بهبود بخشد، که به نوبه خود می تواند تعامل صفحه شما با رنگ بعدی (INP) را بهبود بخشد.

از آنجایی که یک صفحه فایل‌های جاوا اسکریپت بزرگ را دانلود، تجزیه و کامپایل می‌کند، می‌تواند برای مدتی پاسخگو نباشد. عناصر صفحه قابل مشاهده هستند، زیرا آنها بخشی از HTML اولیه صفحه هستند و توسط CSS استایل داده می شوند. با این حال، به دلیل اینکه جاوا اسکریپت مورد نیاز برای تقویت آن عناصر تعاملی - و همچنین سایر اسکریپت های بارگذاری شده توسط صفحه - ممکن است جاوا اسکریپت را برای عملکرد آنها تجزیه و اجرا کند. نتیجه این است که کاربر ممکن است احساس کند که تعامل به طور قابل توجهی به تأخیر افتاده یا حتی به طور کامل شکسته شده است.

این اغلب به این دلیل اتفاق می افتد که رشته اصلی مسدود شده است، زیرا جاوا اسکریپت در رشته اصلی تجزیه و کامپایل می شود. اگر این فرآیند بیش از حد طول بکشد، عناصر صفحه تعاملی ممکن است به اندازه کافی سریع به ورودی کاربر پاسخ ندهند. یکی از راه‌حل‌ها این است که فقط جاوا اسکریپتی را که برای عملکرد صفحه نیاز دارید بارگیری کنید، در حالی که جاوا اسکریپت دیگر را برای بارگذاری بعداً از طریق تکنیکی به نام تقسیم کد به تعویق بیندازید. این ماژول بر روی دومی از این دو تکنیک تمرکز دارد.

کاهش تجزیه و اجرای جاوا اسکریپت در هنگام راه اندازی از طریق تقسیم کد

زمانی که اجرای جاوا اسکریپت بیش از 2 ثانیه طول بکشد، Lighthouse یک اخطار می دهد و زمانی که بیش از 3.5 ثانیه طول بکشد با شکست مواجه می شود . تجزیه و اجرای بیش از حد جاوا اسکریپت یک مشکل بالقوه در هر نقطه از چرخه عمر صفحه است، زیرا اگر زمانی که کاربر با صفحه تعامل برقرار می کند با لحظه ای که وظایف رشته اصلی مسئول پردازش همزمان باشد، می تواند تاخیر ورودی تعامل را افزایش دهد. و در حال اجرای جاوا اسکریپت در حال اجرا هستند.

علاوه بر این، اجرای بیش از حد جاوا اسکریپت و تجزیه به ویژه در طول بارگذاری اولیه صفحه مشکل ساز است، زیرا این نقطه ای در چرخه عمر صفحه است که کاربران به احتمال زیاد با صفحه تعامل دارند. در واقع، زمان انسداد کل (TBT) - یک معیار پاسخگویی بار - به شدت با INP همبستگی دارد، و نشان می دهد که کاربران تمایل زیادی به انجام تعامل در طول بارگذاری اولیه صفحه دارند.

ممیزی Lighthouse که زمان صرف شده برای اجرای هر فایل جاوا اسکریپتی را که صفحه شما درخواست می‌کند گزارش می‌کند، از این جهت مفید است که می‌تواند به شما کمک کند دقیقاً کدام اسکریپت‌ها ممکن است کاندیدای تقسیم کد باشند. سپس می‌توانید با استفاده از ابزار پوشش در Chrome DevTools جلوتر بروید تا دقیقاً مشخص کنید کدام بخش از جاوا اسکریپت صفحه در طول بارگذاری صفحه بدون استفاده می‌ماند.

تقسیم کد یک تکنیک مفید است که می تواند بارهای اولیه جاوا اسکریپت صفحه را کاهش دهد. این به شما امکان می دهد یک بسته جاوا اسکریپت را به دو قسمت تقسیم کنید:

  • جاوا اسکریپت در زمان بارگیری صفحه مورد نیاز است و بنابراین نمی توان آن را در زمان دیگری بارگیری کرد.
  • جاوا اسکریپت باقی مانده که می تواند در زمان بعدی بارگذاری شود، اغلب در نقطه ای که کاربر با یک عنصر تعاملی معین در صفحه تعامل دارد.

تقسیم کد را می توان با استفاده از دستور dynamic import() انجام داد. این نحو - بر خلاف عناصر <script> که یک منبع جاوا اسکریپت را در حین راه‌اندازی درخواست می‌کند - درخواستی برای یک منبع جاوا اسکریپت در مرحله بعدی در طول چرخه عمر صفحه می‌دهد.

document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
  // Get the form validation named export from the module through destructuring:
  const { validateForm } = await import('/validate-form.mjs');

  // Validate the form:
  validateForm();
}, { once: true });

در قطعه جاوا اسکریپت قبلی، ماژول validate-form.mjs تنها زمانی دانلود، تجزیه و اجرا می شود که کاربر هر یک از فیلدهای <input> فرم را محو کند . در این شرایط، منبع جاوا اسکریپت که مسئولیت هدایت منطق اعتبار سنجی فرم را بر عهده دارد، تنها زمانی با صفحه درگیر می شود که احتمال استفاده واقعی از آن وجود داشته باشد.

بسته‌های جاوا اسکریپت مانند webpack ، Parcel ، Rollup و esbuild را می‌توان به گونه‌ای پیکربندی کرد که بسته‌های جاوا اسکریپت را هر زمان که با یک فراخوانی import() پویا در کد منبع شما مواجه می‌شوند، به قطعات کوچک‌تر تقسیم کنند. اکثر این ابزارها این کار را به صورت خودکار انجام می دهند، اما esbuild به طور خاص از شما می خواهد که این بهینه سازی را انتخاب کنید.

نکات مفید در مورد تقسیم کد

در حالی که تقسیم کد یک روش مؤثر برای کاهش اختلاف موضوع اصلی در طول بارگذاری صفحه اولیه است، اگر تصمیم دارید کد منبع جاوا اسکریپت خود را برای فرصت‌های تقسیم کد بررسی کنید، باید چند نکته را در نظر داشته باشید.

اگر می توانید از باندلر استفاده کنید

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

مهمتر از همه، شما باید از یک باندلر برای پردازش و بهینه سازی کد منبع خود استفاده کنید، از جمله ماژول هایی که قصد دارید کدهای آن را تقسیم کنید. باندلرها نه تنها در اعمال بهینه سازی در کد منبع جاوا اسکریپت بسیار موثر هستند، بلکه در متعادل کردن ملاحظات عملکرد مانند اندازه بسته در برابر نسبت فشرده سازی نیز بسیار مؤثر هستند. اثر فشرده سازی با اندازه بسته افزایش می یابد، اما باندلرها همچنین سعی می کنند اطمینان حاصل کنند که بسته ها آنقدر بزرگ نیستند که به دلیل ارزیابی اسکریپت کارهای طولانی را انجام دهند.

باندلرها همچنین از مشکل ارسال تعداد زیادی ماژول جدا نشده از طریق شبکه جلوگیری می کنند. معماری‌هایی که از ماژول‌های جاوا اسکریپت استفاده می‌کنند، معمولا درخت‌های بزرگ و پیچیده‌ای دارند. وقتی درخت‌های ماژول جدا می‌شوند، هر ماژول یک درخواست HTTP جداگانه را نشان می‌دهد و اگر ماژول‌ها را بسته‌بندی نکنید، تعامل در برنامه وب شما ممکن است به تأخیر بیفتد. در حالی که امکان استفاده از راهنمایی منبع <link rel="modulepreload"> برای بارگیری درختان ماژول های بزرگ در اسرع وقت وجود دارد، بسته های جاوا اسکریپت همچنان از نقطه نظر عملکرد بارگیری ترجیح داده می شوند.

به طور ناخواسته کامپایل جریان را غیرفعال نکنید

موتور جاوا اسکریپت V8 Chromium تعدادی بهینه‌سازی را ارائه می‌کند تا اطمینان حاصل شود که کد جاوا اسکریپت تولیدی شما تا حد امکان کارآمد بارگیری می‌شود. یکی از این بهینه‌سازی‌ها به عنوان کامپایل جریانی شناخته می‌شود که - مانند تجزیه تدریجی HTML استریم شده به مرورگر - تکه‌های جریانی جاوا اسکریپت را با ورود از شبکه کامپایل می‌کند.

برای اطمینان از اینکه کامپایل جریان برای برنامه وب شما در Chromium چند راه دارید:

  • کد تولید خود را تغییر دهید تا از ماژول های جاوا اسکریپت خودداری کنید. باندلرها می توانند کد منبع جاوا اسکریپت شما را بر اساس یک هدف کامپایل تغییر دهند و هدف اغلب مختص یک محیط معین است. V8 کامپایل جریانی را برای هر کد جاوا اسکریپتی که از ماژول‌ها استفاده نمی‌کند اعمال می‌کند و می‌توانید باندلر خود را طوری پیکربندی کنید که کد ماژول جاوا اسکریپت شما را به نحوی تبدیل کند که از ماژول‌های جاوا اسکریپت و ویژگی‌های آنها استفاده نمی‌کند.
  • اگر می خواهید ماژول های جاوا اسکریپت را برای تولید ارسال کنید، از پسوند .mjs استفاده کنید. چه جاوا اسکریپت تولیدی شما از ماژول ها استفاده کند یا نه، هیچ نوع محتوای خاصی برای جاوا اسکریپت وجود ندارد که از ماژول ها در مقابل جاوا اسکریپت استفاده نمی کند. در مورد V8، وقتی ماژول‌های جاوا اسکریپت را با استفاده از پسوند .js در حال تولید می‌فرستید، عملاً از کامپایل جریانی خودداری می‌کنید. اگر از پسوند .mjs برای ماژول های جاوا اسکریپت استفاده می کنید، V8 می تواند اطمینان حاصل کند که کامپایل جریان برای کد جاوا اسکریپت مبتنی بر ماژول خراب نیست.

اجازه ندهید این ملاحظات شما را از استفاده از تقسیم کد منصرف کند. تقسیم کد یک روش موثر برای کاهش بارهای اولیه جاوا اسکریپت برای کاربران است، اما با استفاده از یک بسته‌کننده و دانستن اینکه چگونه می‌توانید رفتار تلفیقی جریان V8 را حفظ کنید، می‌توانید اطمینان حاصل کنید که کد جاوا اسکریپت تولیدی شما به همان سرعتی که می‌تواند برای کاربران باشد.

نسخه ی نمایشی واردات پویا

بسته وب

بسته وب با افزونه‌ای به نام SplitChunksPlugin عرضه می‌شود که به شما امکان می‌دهد نحوه تقسیم فایل‌های جاوا اسکریپت توسط بسته‌کننده را پیکربندی کنید. webpack هر دو عبارت dynamic import() و static import را می شناسد. رفتار SplitChunksPlugin را می توان با مشخص کردن گزینه chunks در پیکربندی آن تغییر داد:

  • chunks: async مقدار پیش‌فرض است و به فراخوانی‌های import() پویا اشاره دارد.
  • chunks: initial به تماس های import ثابت اشاره دارد.
  • chunks: all هم import() و هم واردات استاتیک را پوشش می دهد، به شما امکان می دهد تکه ها را بین واردات async و initial به اشتراک بگذارید.

به‌طور پیش‌فرض، هر زمان که webpack با دستور import() دینامیک مواجه شد. یک قطعه جداگانه برای آن ماژول ایجاد می کند:

/* main.js */

// An application-specific chunk required during the initial page load:
import myFunction from './my-function.js';

myFunction('Hello world!');

// If a specific condition is met, a separate chunk is downloaded on demand,
// rather than being bundled with the initial chunk:
if (condition) {
  // Assumes top-level await is available. More info:
  // https://v8.dev/features/top-level-await
  await import('/form-validation.js');
}

پیکربندی پیش‌فرض بسته وب برای قطعه کد قبلی به دو بخش جداگانه منجر می‌شود:

  • قطعه main.js - که بسته وب به عنوان یک تکه initial طبقه بندی می شود - که شامل main.js و ماژول ./my-function.js است.
  • قطعه async که فقط شامل form-validation.js است (در صورت پیکربندی حاوی هش فایل در نام منبع). این قطعه فقط در صورت صحت بودن condition دانلود می شود.

این پیکربندی به شما امکان می دهد بارگیری قطعه form-validation.js تا زمانی که واقعاً مورد نیاز باشد به تعویق بیندازید. این می تواند با کاهش زمان ارزیابی اسکریپت در طول بارگذاری صفحه اولیه، پاسخگویی بار را بهبود بخشد. دانلود و ارزیابی اسکریپت برای قطعه form-validation.js زمانی اتفاق می‌افتد که یک شرط مشخص شده برآورده شود، در این صورت، ماژول وارد شده به صورت پویا دانلود می‌شود. یک مثال ممکن است شرایطی باشد که در آن یک polyfill فقط برای یک مرورگر خاص دانلود می‌شود، یا - مانند مثال قبلی - ماژول وارد شده برای تعامل کاربر ضروری است.

از سوی دیگر، تغییر پیکربندی SplitChunksPlugin برای مشخص کردن chunks: initial تضمین می‌کند که کد فقط در تکه‌های اولیه تقسیم می‌شود. این‌ها تکه‌هایی مانند آنهایی هستند که به‌صورت ایستا وارد شده‌اند یا در ویژگی entry webpack فهرست شده‌اند. با نگاهی به مثال قبل، قطعه حاصل ترکیبی از form-validation.js و main.js در یک فایل اسکریپت واحد خواهد بود که منجر به عملکرد بالقوه بدتر بارگذاری اولیه صفحه می شود.

گزینه‌های SplitChunksPlugin را نیز می‌توان به گونه‌ای پیکربندی کرد که اسکریپت‌های بزرگ‌تر را به چند اسکریپت کوچک‌تر تفکیک کند - به عنوان مثال با استفاده از گزینه maxSize برای دستور دادن به وب‌پک برای تقسیم تکه‌ها به فایل‌های جداگانه در صورتی که از مقدار تعیین شده توسط maxSize فراتر رود. تقسیم فایل‌های اسکریپت بزرگ به فایل‌های کوچک‌تر می‌تواند پاسخگویی بار را بهبود بخشد ، زیرا در برخی موارد کار ارزیابی اسکریپت فشرده CPU به وظایف کوچک‌تری تقسیم می‌شود، که کمتر احتمال دارد رشته اصلی را برای مدت زمان طولانی‌تری مسدود کند.

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

از سوی دیگر، فایل‌های اسکریپت کوچک‌تر، احتمال بازیابی منابع از حافظه پنهان را افزایش می‌دهد و در نتیجه در بازدیدهای مکرر صفحه سریع‌تر بارگذاری می‌شود. با این حال، فایل‌های کوچک‌تر از فشرده‌سازی کمتری نسبت به فایل‌های بزرگ‌تر بهره می‌برند و ممکن است زمان رفت و برگشت شبکه را در بارگذاری صفحه با حافظه پنهان مرورگر بدون پرایم افزایش دهند. باید مراقب بود که تعادلی بین کارایی ذخیره سازی، اثر فشرده سازی و زمان ارزیابی اسکریپت ایجاد شود.

نسخه نمایشی بسته وب

وب بسته نسخه ی نمایشی SplitChunksPlugin .

دانش خود را تست کنید

هنگام انجام تقسیم کد از کدام نوع دستور import استفاده می شود؟

import() .
درسته!
import استاتیک
دوباره امتحان کنید.

کدام نوع دستور import باید در بالای ماژول جاوا اسکریپت باشد و در هیچ مکان دیگری وجود نداشته باشد؟

import() .
دوباره امتحان کنید.
import استاتیک
درسته!

هنگام استفاده از SplitChunksPlugin در وب پک، تفاوت بین یک قطعه async و یک قطعه initial چیست؟

تکه‌های async با استفاده از import() و تکه‌های initial با استفاده از import static بارگذاری می‌شوند.
درسته!
تکه‌های async با استفاده از import static و تکه‌های initial با استفاده از dynamic import() بارگذاری می‌شوند.
دوباره امتحان کنید.

بعدی: بارگذاری تنبل تصاویر و عناصر <iframe>

اگرچه جاوا اسکریپت نوع نسبتاً گرانی از منابع است، اما جاوا اسکریپت تنها نوع منبعی نیست که می‌توانید بارگذاری آن را به تعویق بیندازید. عناصر تصویر و <iframe> در نوع خود منابع بالقوه پرهزینه ای هستند. مشابه جاوا اسکریپت، می توانید بارگذاری تصاویر و عنصر <iframe> را با بارگذاری تنبل آنها به تعویق بیندازید که در ماژول بعدی این دوره توضیح داده شده است.