ارزیابی اسکریپت و کارهای طولانی

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

وقتی صحبت از بهینه‌سازی تعامل برای رنگ‌آمیزی بعدی (INP) می‌شود، بیشتر توصیه‌هایی که با آنها مواجه می‌شوید، بهینه‌سازی خود تعاملات است. برای مثال، در راهنمای بهینه‌سازی وظایف طولانی ، تکنیک‌هایی مانند yielding با setTimeout و موارد دیگر مورد بحث قرار گرفته‌اند. این تکنیک‌ها مفید هستند، زیرا با اجتناب از وظایف طولانی، به نخ اصلی کمی فضای تنفس می‌دهند که می‌تواند فرصت‌های بیشتری را برای تعاملات و سایر فعالیت‌ها فراهم کند تا زودتر اجرا شوند، به جای اینکه مجبور باشند برای یک وظیفه طولانی منتظر بمانند.

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

ارزیابی فیلمنامه چیست؟

اگر برنامه‌ای را که مقدار زیادی جاوا اسکریپت ارسال می‌کند، بررسی کرده باشید، ممکن است وظایف طولانی‌ای را دیده باشید که در آن‌ها مقصر با برچسب «ارزیابی اسکریپت» مشخص شده است.

کار ارزیابی اسکریپت همانطور که در ابزار توسعه کروم (Chrome DevTools) نمایش داده شده است. این کار باعث ایجاد یک وظیفه طولانی در هنگام راه‌اندازی می‌شود که توانایی رشته اصلی (main thread) را برای پاسخگویی به تعاملات کاربر مسدود می‌کند.
کار ارزیابی اسکریپت همانطور که در پروفایل عملکرد در Chrome DevTools نشان داده شده است. در این حالت، کار کافی است تا یک کار طولانی ایجاد شود که مانع از انجام کارهای دیگر توسط نخ اصلی می‌شود - از جمله کارهایی که تعاملات کاربر را هدایت می‌کنند.

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

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

رابطه بین اسکریپت‌ها و وظایفی که آنها را ارزیابی می‌کنند

نحوه شروع وظایف مسئول ارزیابی اسکریپت بستگی به این دارد که آیا اسکریپتی که بارگذاری می‌کنید با یک عنصر <script> معمولی بارگذاری شده است یا اینکه اسکریپت یک ماژول بارگذاری شده با type=module است. از آنجایی که مرورگرها تمایل دارند مسائل را به طور متفاوتی مدیریت کنند، نحوه مدیریت ارزیابی اسکریپت توسط موتورهای اصلی مرورگر، در جایی که رفتارهای ارزیابی اسکریپت در آنها متفاوت است، مورد بحث قرار خواهد گرفت.

اسکریپت‌های بارگذاری شده با عنصر <script>

تعداد وظایفی که برای ارزیابی اسکریپت‌ها ارسال می‌شوند، عموماً رابطه مستقیمی با تعداد عناصر <script> در یک صفحه دارد. هر عنصر <script> وظیفه‌ای را برای ارزیابی اسکریپت درخواستی آغاز می‌کند تا بتوان آن را تجزیه، کامپایل و اجرا کرد. این مورد در مورد مرورگرهای مبتنی بر Chromium، Safari و Firefox صدق می‌کند.

چرا این موضوع مهم است؟ فرض کنید از یک bundler برای مدیریت اسکریپت‌های تولیدی خود استفاده می‌کنید و آن را طوری پیکربندی کرده‌اید که هر چیزی را که صفحه شما برای اجرا نیاز دارد، در یک اسکریپت واحد دسته‌بندی کند. اگر این مورد برای وب‌سایت شما صدق می‌کند، می‌توانید انتظار داشته باشید که یک وظیفه واحد برای ارزیابی آن اسکریپت ارسال شود. آیا این چیز بدی است؟ لزوماً نه - مگر اینکه آن اسکریپت بزرگ باشد.

شما می‌توانید با اجتناب از بارگذاری بخش‌های بزرگ جاوا اسکریپت، کار ارزیابی اسکریپت را تقسیم کنید و با استفاده از عناصر <script> اضافی، اسکریپت‌های کوچک‌تر و منفردتری را بارگذاری کنید.

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

وظایف چندگانه شامل ارزیابی اسکریپت همانطور که در نمایه‌ساز عملکرد Chrome DevTools نمایش داده شده است. از آنجا که چندین اسکریپت کوچکتر به جای تعداد کمتری اسکریپت بزرگتر بارگذاری می‌شوند، احتمال تبدیل شدن وظایف به وظایف طولانی کمتر می‌شود و به رشته اصلی اجازه می‌دهد تا سریعتر به ورودی کاربر پاسخ دهد.
چندین وظیفه برای ارزیابی اسکریپت‌ها در نتیجه وجود چندین عنصر <script> در HTML صفحه ایجاد می‌شود. این روش نسبت به ارسال یک بسته بزرگ اسکریپت به کاربران که احتمال مسدود شدن نخ اصلی را بیشتر می‌کند، ارجحیت دارد.

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

اسکریپت‌های بارگذاری شده با عنصر <script> و ویژگی type=module

اکنون می‌توان ماژول‌های ES را به صورت بومی در مرورگر با استفاده از ویژگی type=module در عنصر <script> بارگذاری کرد. این رویکرد برای بارگذاری اسکریپت، مزایایی برای تجربه توسعه‌دهندگان دارد، مانند عدم نیاز به تبدیل کد برای استفاده در محیط عملیاتی - به خصوص هنگامی که در ترکیب با نقشه‌های ایمپورت استفاده می‌شود. با این حال، بارگذاری اسکریپت‌ها به این روش، وظایفی را زمان‌بندی می‌کند که از مرورگری به مرورگر دیگر متفاوت است.

مرورگرهای مبتنی بر کرومیوم

در مرورگرهایی مانند کروم - یا مرورگرهای مشتق شده از آن - بارگذاری ماژول‌های ES با استفاده از ویژگی type=module انواع مختلفی از وظایف را نسبت به آنچه معمولاً هنگام عدم استفاده type=module می‌بینید، ایجاد می‌کند. برای مثال، یک وظیفه برای هر اسکریپت ماژول اجرا می‌شود که شامل فعالیتی با برچسب Compile module است.

کامپایل ماژول در چندین وظیفه انجام می‌شود، همانطور که در Chrome DevTools نمایش داده شده است.
رفتار بارگذاری ماژول در مرورگرهای مبتنی بر Chromium. هر اسکریپت ماژول قبل از ارزیابی، یک فراخوانی ماژول Compile برای کامپایل محتویات آن ایجاد می‌کند.

پس از کامپایل ماژول‌ها، هر کدی که متعاقباً در آنها اجرا شود، فعالیتی با عنوان Evaluate module را آغاز می‌کند.

ارزیابی لحظه‌ای یک ماژول که در پنل عملکرد Chrome DevTools به صورت بصری نمایش داده می‌شود.
وقتی کدی در یک ماژول اجرا می‌شود، آن ماژول به صورت خودکار ارزیابی می‌شود.

تأثیر اینجا - حداقل در کروم و مرورگرهای مرتبط - این است که مراحل کامپایل هنگام استفاده از ماژول‌های ES تقسیم می‌شوند. این یک مزیت آشکار از نظر مدیریت وظایف طولانی است. با این حال، کار ارزیابی ماژول حاصل از آن هنوز به این معنی است که شما متحمل هزینه‌های اجتناب‌ناپذیری می‌شوید. در حالی که باید تلاش کنید تا حد امکان جاوا اسکریپت کمتری ارسال کنید، استفاده از ماژول‌های ES - صرف نظر از مرورگر - مزایای زیر را ارائه می‌دهد:

  • تمام کد ماژول به طور خودکار در حالت strict اجرا می‌شود، که امکان بهینه‌سازی‌های بالقوه‌ای را توسط موتورهای جاوا اسکریپت فراهم می‌کند که در غیر این صورت نمی‌توانستند در یک زمینه غیر strict انجام شوند.
  • اسکریپت‌هایی که با استفاده از type=module بارگذاری می‌شوند، طوری رفتار می‌شوند که انگار به طور پیش‌فرض به تعویق افتاده‌اند . می‌توان از ویژگی async در اسکریپت‌هایی که با type=module بارگذاری شده‌اند، برای تغییر این رفتار استفاده کرد.

سافاری و فایرفاکس

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

اسکریپت‌های بارگذاری شده با تابع import() پویا

تابع import() پویا روش دیگری برای بارگذاری اسکریپت‌ها است. برخلاف دستورات import استاتیک که باید در بالای یک ماژول ES باشند، فراخوانی تابع import() import پویا می‌تواند در هر جایی از اسکریپت ظاهر شود تا در صورت نیاز، بخشی از جاوا اسکریپت را بارگذاری کند. این تکنیک، تقسیم کد نامیده می‌شود.

import() پویا در بهبود INP دو مزیت دارد:

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

فراخوانی‌های پویای import() در تمام موتورهای مرورگر اصلی رفتار مشابهی دارند: وظایف ارزیابی اسکریپت که نتیجه می‌دهند، همان تعداد ماژول‌هایی خواهند بود که به صورت پویا وارد می‌شوند.

اسکریپت‌های بارگذاری شده در یک وب ورکر

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

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

بده‌بستان‌ها و ملاحظات

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

راندمان فشرده‌سازی

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

باندلرها ابزارهای ایده‌آلی برای مدیریت اندازه خروجی اسکریپت‌هایی هستند که وب‌سایت شما به آنها وابسته است:

  • در مورد webpack، افزونه SplitChunksPlugin آن می‌تواند کمک کند. برای گزینه‌هایی که می‌توانید برای کمک به مدیریت اندازه‌های دارایی تنظیم کنید، به مستندات SplitChunksPlugin مراجعه کنید.
  • برای سایر bundlerها مانند Rollup و esbuild ، می‌توانید اندازه فایل‌های اسکریپت را با استفاده از فراخوانی‌های پویای import() در کد خود مدیریت کنید. این bundlerها - و همچنین webpack - به طور خودکار دارایی وارد شده پویا را به فایل خود تجزیه می‌کنند و در نتیجه از اندازه‌های اولیه بسته بزرگتر جلوگیری می‌کنند.

نامعتبرسازی حافظه پنهان

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

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

ماژول‌های تو در تو و عملکرد بارگذاری

اگر ماژول‌های ES را در محیط عملیاتی ارسال می‌کنید و آنها را با ویژگی type=module بارگذاری می‌کنید، باید از چگونگی تأثیر تودرتو بودن ماژول‌ها بر زمان راه‌اندازی آگاه باشید. تودرتو بودن ماژول به زمانی اشاره دارد که یک ماژول ES به صورت ایستا ماژول ES دیگری را که آن ماژول ES دیگری را به صورت ایستا وارد می‌کند، وارد می‌کند:

// a.js
import {b} from './b.js';

// b.js
import {c} from './c.js';

اگر ماژول‌های ES شما با هم بسته‌بندی نشده باشند، کد قبلی منجر به یک زنجیره درخواست شبکه می‌شود: وقتی a.js از یک عنصر <script> درخواست می‌شود، درخواست شبکه دیگری برای b.js ارسال می‌شود که سپس شامل درخواست دیگری برای c.js می‌شود. یک راه برای جلوگیری از این امر، استفاده از یک بسته‌بندی‌کننده است - اما مطمئن شوید که بسته‌بندی‌کننده خود را طوری پیکربندی می‌کنید که اسکریپت‌ها را به بخش‌های مختلف تقسیم کند تا کار ارزیابی اسکریپت پخش شود.

اگر نمی‌خواهید از bundler استفاده کنید، راه دیگر برای دور زدن فراخوانی‌های ماژول تو در تو، استفاده از modulepreload resource hint است که ماژول‌های ES را از قبل بارگذاری می‌کند تا از زنجیره‌های درخواست شبکه جلوگیری شود.

نتیجه‌گیری

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

برای خلاصه، در اینجا چند کاری که می‌توانید برای تقسیم وظایف بزرگ ارزیابی اسکریپت انجام دهید، آورده شده است:

  • هنگام بارگذاری اسکریپت‌ها با استفاده از عنصر <script> بدون ویژگی type=module ، از بارگذاری اسکریپت‌های بسیار بزرگ خودداری کنید، زیرا این اسکریپت‌ها وظایف ارزیابی اسکریپت با منابع فشرده را آغاز می‌کنند که نخ اصلی را مسدود می‌کنند. اسکریپت‌های خود را روی عناصر <script> بیشتری پخش کنید تا این کار را تجزیه کنید.
  • استفاده از ویژگی type=module برای بارگذاری ماژول‌های ES به صورت بومی در مرورگر، وظایف جداگانه‌ای را برای ارزیابی هر اسکریپت ماژول جداگانه آغاز می‌کند.
  • با استفاده از فراخوانی‌های پویای import() ، اندازه بسته‌های اولیه خود را کاهش دهید. این روش در بسته‌بندها نیز کار می‌کند، زیرا بسته‌بندها هر ماژول وارد شده پویا را به عنوان یک "نقطه تقسیم" در نظر می‌گیرند، در نتیجه برای هر ماژول وارد شده پویا، یک اسکریپت جداگانه تولید می‌شود.
  • حتماً مواردی مانند راندمان فشرده‌سازی و نامعتبرسازی حافظه پنهان را بسنجید. اسکریپت‌های بزرگتر بهتر فشرده می‌شوند، اما احتمال بیشتری دارد که شامل کار ارزیابی اسکریپت گران‌تر در وظایف کمتر باشند و منجر به نامعتبرسازی حافظه پنهان مرورگر شوند که منجر به راندمان کلی پایین‌تر حافظه پنهان می‌شود.
  • اگر از ماژول‌های ES به صورت بومی و بدون بسته‌بندی استفاده می‌کنید، از راهنمای منبع modulepreload برای بهینه‌سازی بارگذاری آنها در هنگام راه‌اندازی استفاده کنید.
  • مثل همیشه، تا حد امکان کدهای جاوا اسکریپت کمتری ارسال کنید.

مطمئناً این یک عمل متعادل‌سازی است - اما با تقسیم اسکریپت‌ها و کاهش حجم اولیه بار با استفاده از import() ، می‌توانید به عملکرد بهتر در هنگام راه‌اندازی دست یابید و تعاملات کاربر را در آن دوره حیاتی راه‌اندازی بهتر مدیریت کنید. این به شما کمک می‌کند تا در معیار INP امتیاز بهتری کسب کنید و در نتیجه تجربه کاربری بهتری ارائه دهید.