تکنیک‌هایی برای بارگیری سریع یک برنامه وب، حتی در تلفن همراه

چگونه از تقسیم کد، درون‌سازی کد و رندر سمت سرور در PROXX استفاده کردیم.

در Google I/O 2019، ماریکو، جیک و من PROXX را ارسال کردیم، یک مین‌روب مدرن برای وب. چیزی که PROXX را متمایز می کند تمرکز بر دسترسی (شما می توانید آن را با یک صفحه خوان بازی کنید!) و توانایی اجرای آن روی یک تلفن همراه مانند یک دستگاه رومیزی پیشرفته است. تلفن های دارای ویژگی به چند روش محدود می شوند:

  • CPU های ضعیف
  • پردازنده‌های گرافیکی ضعیف یا موجود نیستند
  • صفحه نمایش های کوچک بدون ورودی لمسی
  • حافظه بسیار محدود

اما آنها یک مرورگر مدرن اجرا می کنند و بسیار مقرون به صرفه هستند. به همین دلیل، تلفن های همراه در بازارهای نوظهور دوباره احیا می کنند. قیمت آنها به مخاطبان کاملاً جدیدی که قبلاً توانایی پرداخت آن را نداشتند، اجازه می دهد تا آنلاین شوند و از وب مدرن استفاده کنند. برای سال 2019 پیش‌بینی می‌شود که حدود 400 میلیون تلفن هوشمند تنها در هند فروخته شود ، بنابراین کاربران تلفن‌های ویژگی ممکن است بخش قابل توجهی از مخاطبان شما باشند. علاوه بر آن، سرعت اتصال مشابه 2G در بازارهای در حال ظهور عادی است. چگونه توانستیم PROXX را تحت شرایط تلفن همراه به خوبی کار کنیم؟

گیم پلی PROXX.

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

این قسمت 1 از یک مجموعه دو قسمتی است. بخش 1 بر عملکرد بارگذاری تمرکز دارد و بخش 2 بر عملکرد زمان اجرا تمرکز خواهد کرد.

تسخیر وضعیت موجود

آزمایش عملکرد بارگیری خود در یک دستگاه واقعی بسیار مهم است. اگر دستگاه واقعی در دسترس ندارید، من WebPageTest را توصیه می‌کنم، مخصوصاً راه‌اندازی «ساده» را. WPT یک باتری از تست های بارگیری را روی یک دستگاه واقعی با اتصال 3G شبیه سازی شده اجرا می کند.

3G سرعت خوبی برای اندازه گیری است. در حالی که ممکن است به 4G، LTE یا به زودی حتی 5G عادت کرده باشید، واقعیت اینترنت موبایل کاملاً متفاوت به نظر می رسد. شاید در قطار، کنفرانس، کنسرت یا پرواز هستید. آنچه شما در آنجا تجربه خواهید کرد به احتمال زیاد به 3G نزدیکتر است و گاهی اوقات حتی بدتر.

همانطور که گفته شد، ما در این مقاله بر روی 2G تمرکز خواهیم کرد زیرا PROXX به صراحت تلفن های همراه و بازارهای نوظهور را در مخاطبان هدف خود هدف قرار می دهد. هنگامی که WebPageTest آزمایش خود را انجام داد، یک آبشار (مشابه آنچه در DevTools می بینید) و همچنین یک نوار فیلم در بالا دریافت می کنید. نوار فیلم نشان می دهد که کاربر شما هنگام بارگیری برنامه شما چه می بیند. در 2G، تجربه بارگیری نسخه بهینه نشده PROXX بسیار بد است:

ویدیوی نوار فیلم نشان می دهد که کاربر هنگام بارگیری PROXX بر روی یک دستگاه واقعی و ارزان قیمت از طریق اتصال 2G شبیه سازی شده، چه می بیند.

وقتی کاربر از طریق 3G بارگذاری می شود، 4 ثانیه عدم وجود سفید را می بیند. بیش از 2G کاربر برای بیش از 8 ثانیه مطلقاً چیزی نمی بیند. اگر دلیل اهمیت عملکرد را می خوانید، می دانید که ما اکنون بخش خوبی از کاربران بالقوه خود را به دلیل بی حوصلگی از دست داده ایم. کاربر باید تمام 62 کیلوبایت جاوا اسکریپت را دانلود کند تا هر چیزی روی صفحه نمایش داده شود. پوشش نقره ای در این سناریو این است که دومین چیزی که روی صفحه ظاهر می شود، تعاملی است. یا هست؟

[First Meaningful Paint][FMP] در نسخه بهینه نشده PROXX از نظر فنی [تعاملی][TTI] است اما برای کاربر بی فایده است.

پس از دانلود حدود 62 کیلوبایت gzip'd JS و ایجاد DOM، کاربر برنامه ما را مشاهده می کند. این برنامه از نظر فنی تعاملی است. با این حال، نگاه به تصویر، واقعیت دیگری را نشان می دهد. فونت‌های وب همچنان در پس‌زمینه بارگذاری می‌شوند و تا زمانی که آماده شوند، کاربر نمی‌تواند متنی را ببیند. در حالی که این حالت به عنوان اولین رنگ معنی دار (FMP) واجد شرایط است، مطمئناً واجد شرایط تعامل مناسب نیست، زیرا کاربر نمی تواند بگوید که هر یک از ورودی ها در مورد چیست. یک ثانیه دیگر در 3G و 3 ثانیه در 2G طول می کشد تا برنامه آماده اجرا شود. در مجموع، این برنامه 6 ثانیه در 3G و 11 ثانیه در 2G طول می کشد تا تعاملی شود.

تجزیه و تحلیل آبشار

اکنون که می دانیم کاربر چه چیزی را می بیند، باید دلیل آن را بفهمیم. برای این ما می توانیم به آبشار نگاه کنیم و تحلیل کنیم که چرا منابع خیلی دیر بار می شوند. در ردیابی 2G ما برای PROXX می توانیم دو پرچم قرمز اصلی را ببینیم:

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

کاهش تعداد اتصالات

هر خط نازک ( dns , connect , ssl ) مخفف ایجاد یک اتصال HTTP جدید است. راه اندازی یک اتصال جدید پرهزینه است زیرا در 3G حدود 1 ثانیه و در 2G تقریباً 2.5 ثانیه طول می کشد. در آبشار ما یک اتصال جدید برای:

  • درخواست شماره 1: index.html ما
  • درخواست شماره 5: سبک های فونت از fonts.googleapis.com
  • درخواست شماره 8: Google Analytics
  • درخواست شماره 9: یک فایل فونت از fonts.gstatic.com
  • درخواست شماره 14: مانیفست برنامه وب

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

با این حال، دو فونت و سبک آنها مشکل ساز هستند زیرا رندر و همچنین تعامل را مسدود می کنند. اگر به CSS ارائه شده توسط fonts.googleapis.com نگاه کنیم، این فقط دو قانون @font-face است، یکی برای هر فونت. سبک های فونت در واقع آنقدر کوچک هستند که تصمیم گرفتیم آن را در HTML خود قرار دهیم و یک اتصال غیر ضروری را حذف کنیم. برای جلوگیری از هزینه راه‌اندازی اتصال برای فایل‌های فونت، می‌توانیم آنها را در سرور خود کپی کنیم.

موازی کردن بارها

با نگاهی به آبشار، می بینیم که پس از بارگذاری اولین فایل جاوا اسکریپت، فایل های جدید بلافاصله بارگذاری می شوند. این برای وابستگی های ماژول معمولی است. ماژول اصلی ما احتمالا دارای واردات ثابت است، بنابراین جاوا اسکریپت نمی تواند تا زمانی که آن واردات بارگذاری شود اجرا شود. نکته مهمی که در اینجا باید بدانیم این است که این نوع وابستگی ها در زمان ساخت شناخته می شوند. می‌توانیم از تگ‌های <link rel="preload"> استفاده کنیم تا مطمئن شویم که همه وابستگی‌ها در ثانیه‌ای که HTML خود را دریافت می‌کنیم شروع به بارگیری می‌کنند.

نتایج

بیایید نگاهی بیندازیم که تغییرات ما چه دستاوردهایی داشته است. مهم است که هیچ متغیر دیگری را در تنظیمات تست خود تغییر ندهید که می تواند نتایج را تغییر دهد، بنابراین ما از تنظیمات ساده WebPageTest برای بقیه این مقاله استفاده می کنیم و به نوار فیلم نگاه می کنیم:

ما از نوار فیلم WebPageTest استفاده می کنیم تا ببینیم تغییرات ما به چه چیزی رسیده است.

این تغییرات TTI ما را از 11 به 8.5 کاهش داد ، که تقریباً 2.5 ثانیه زمان تنظیم اتصال است که ما قصد حذف آن را داشتیم. آفرین به ما

پیش اجرا

در حالی که ما فقط TTI خود را کاهش دادیم، اما واقعاً روی صفحه سفید طولانی و ابدی که کاربر باید برای 8.5 ثانیه تحمل کند، تأثیری نداشته است. مسلماً بزرگترین پیشرفت ها برای FMP را می توان با ارسال نشانه گذاری سبک در index.html خود به دست آورد . تکنیک‌های رایج برای دستیابی به این هدف، رندر از قبل و رندر سمت سرور است که ارتباط نزدیکی با هم دارند و در Rendering در وب توضیح داده شده‌اند. هر دو تکنیک برنامه وب را در Node اجرا می کنند و DOM حاصل را به HTML سریال می کنند. رندر سمت سرور این کار را به ازای هر درخواست در سمت سرور انجام می دهد، در حالی که پیش رندر این کار را در زمان ساخت انجام می دهد و خروجی را به عنوان index.html جدید شما ذخیره می کند. از آنجایی که PROXX یک برنامه JAMStack است و هیچ سمت سروری ندارد، تصمیم گرفتیم پیش اجرا را اجرا کنیم.

راه های زیادی برای پیاده سازی پیش اجرا وجود دارد. در PROXX ما استفاده از Puppeteer را انتخاب کردیم که Chrome را بدون هیچ رابط کاربری راه‌اندازی می‌کند و به شما امکان می‌دهد آن نمونه را با یک Node API از راه دور کنترل کنید. ما از این برای تزریق نشانه گذاری و جاوا اسکریپت خود استفاده می کنیم و سپس DOM را به عنوان رشته ای از HTML بازخوانی می کنیم. از آنجایی که ما از ماژول‌های CSS استفاده می‌کنیم، سبک‌هایی را که به آن نیاز داریم به صورت رایگان در قالب CSS دریافت می‌کنیم.

  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setContent(rawIndexHTML);
  await page.evaluate(codeToRun);
  const renderedHTML = await page.content();
  browser.close();
  await writeFile("index.html", renderedHTML);

با وجود این، می‌توانیم انتظار بهبودی برای FMP خود داشته باشیم. ما هنوز باید همان مقدار جاوا اسکریپت قبلی را بارگذاری و اجرا کنیم، بنابراین نباید انتظار داشته باشیم که TTI تغییر زیادی کند. در هر صورت، index.html ما بزرگتر شده است و ممکن است کمی TTI ما را به عقب براند. تنها یک راه برای پیدا کردن وجود دارد: اجرای WebPageTest.

نوار فیلم بهبود واضحی را برای متریک FMP ما نشان می دهد. TTI عمدتاً تحت تأثیر قرار نمی گیرد.

اولین رنگ معنادار ما از 8.5 ثانیه به 4.9 ثانیه رسیده است که یک پیشرفت عظیم است. TTI ما هنوز در حدود 8.5 ثانیه اتفاق می افتد، بنابراین تا حد زیادی تحت تأثیر این تغییر قرار نگرفته است. کاری که ما اینجا انجام دادیم یک تغییر ادراکی است. برخی حتی ممکن است آن را یک اهمال کاری خطاب کنند. با ارائه تصویری متوسط ​​از بازی، عملکرد بارگذاری درک شده را برای بهتر شدن تغییر می دهیم.

خط کشی

معیار دیگری که هم DevTools و هم WebPageTest به ما می دهند، Time To First Byte (TTFB) است. این مدت زمانی است که از اولین بایت درخواست ارسال شده تا اولین بایت پاسخ دریافت شده طول می کشد. این زمان اغلب یک زمان رفت و برگشت (RTT) نیز نامیده می شود، اگرچه از نظر فنی بین این دو عدد تفاوت وجود دارد: RTT شامل زمان پردازش درخواست در سمت سرور نمی شود. DevTools و WebPageTest TTFB را با رنگ روشن در بلوک درخواست/پاسخ تجسم می کنند.

بخش نور یک درخواست نشان می دهد که درخواست در انتظار دریافت اولین بایت پاسخ است.

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

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

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

با جاوا اسکریپت خود، TTI خود را از 8.5 به 7.2 کاهش دادیم.

این 1 ثانیه از TTI ما کم کرد. اکنون به نقطه‌ای رسیده‌ایم که index.html ما حاوی هر چیزی است که برای رندر اولیه و تعاملی شدن لازم است. HTML می تواند در حالی که هنوز در حال بارگیری است رندر شود و FMP ما را ایجاد کند. لحظه ای که HTML تجزیه و اجرا می شود، برنامه تعاملی است.

تقسیم کد تهاجمی

بله، index.html ما شامل همه چیزهایی است که برای تعاملی شدن لازم است. اما با بررسی دقیق‌تر معلوم می‌شود که شامل هر چیز دیگری نیز می‌شود. index.html ما حدود 43 کیلوبایت است. بیایید آن را در رابطه با آنچه کاربر می‌تواند در ابتدا با آن تعامل داشته باشد، در نظر بگیریم: ما یک فرم برای پیکربندی بازی داریم که شامل چند مؤلفه، یک دکمه شروع و احتمالاً مقداری کد برای تداوم و بارگیری تنظیمات کاربر است. تقریباً همین است. 43 کیلوبایت زیاد به نظر می رسد.

صفحه فرود PROXX. در اینجا فقط از اجزای حیاتی استفاده می شود.

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

تجزیه و تحلیل محتویات «index.html» PROXX منابع غیر ضروری زیادی را نشان می دهد. منابع حیاتی برجسته می شوند.

کاری که ما باید انجام دهیم تقسیم کد است. تقسیم کد، بسته یکپارچه شما را به قطعات کوچک‌تری تقسیم می‌کند که می‌توانند در صورت تقاضا بارگذاری شوند. باندلرهای محبوب مانند Webpack ، Rollup و Parcel از تقسیم کد با استفاده از import() پویا پشتیبانی می‌کنند. باندلر کد شما را تجزیه و تحلیل می‌کند و همه ماژول‌هایی را که به صورت ایستا وارد می‌شوند، درون خط می‌کند . هر چیزی که به صورت پویا وارد می‌کنید در فایل خودش قرار می‌گیرد و تنها زمانی که فراخوانی import() اجرا شود، از شبکه واکشی می‌شود. البته ضربه زدن به شبکه هزینه دارد و تنها در صورتی باید انجام شود که وقت کافی داشته باشید. مانترا در اینجا این است که ماژول هایی را که در زمان بارگذاری به شدت مورد نیاز هستند وارد کنیم و هر چیز دیگری را به صورت پویا بارگذاری کنیم. اما نباید تا آخرین لحظه منتظر ماژول‌های تنبلی باشید که قطعاً استفاده می‌شوند. فیلم Idle Until Urgent از Phil Walton یک الگوی عالی برای یک میانه سالم بین بارگیری تنبل و بارگیری مشتاق است.

در PROXX ما یک فایل lazy.js ایجاد کردیم که به صورت ایستا هر چیزی را که به آن نیاز نداریم وارد می کند. در فایل اصلی خود، می توانیم lazy.js به صورت پویا وارد کنیم. با این حال، برخی از مؤلفه‌های Preact ما به lazy.js ختم شدند، که معلوم شد کمی پیچیده است زیرا Preact نمی‌تواند مؤلفه‌های تنبل بارگذاری شده را خارج از جعبه کنترل کند. به همین دلیل، ما یک بسته بندی کامپوننت deferred کوچک نوشتیم که به ما اجازه می دهد تا زمانی که کامپوننت واقعی بارگذاری شود، یک مکان نگهدار را ارائه کنیم.

export default function deferred(componentPromise) {
  return class Deferred extends Component {
    constructor(props) {
      super(props);
      this.state = {
        LoadedComponent: undefined
      };
      componentPromise.then(component => {
        this.setState({ LoadedComponent: component });
      });
    }

    render({ loaded, loading }, { LoadedComponent }) {
      if (LoadedComponent) {
        return loaded(LoadedComponent);
      }
      return loading();
    }
  };
}

با وجود این، می توانیم از Promise یک جزء در توابع render() خود استفاده کنیم. برای مثال، مؤلفه <Nebula> ، که تصویر پس‌زمینه متحرک را ارائه می‌کند، در حین بارگیری مؤلفه با یک <div> خالی جایگزین می‌شود. هنگامی که کامپوننت بارگیری شد و آماده استفاده شد، <div> با کامپوننت واقعی جایگزین می شود.

const NebulaDeferred = deferred(
  import("/components/nebula").then(m => m.default)
);

return (
  // ...
  <NebulaDeferred
    loading={() => <div />}
    loaded={Nebula => <Nebula />}
  />
);

با همه این موارد، index.html خود را به 20 کیلوبایت کاهش دادیم، کمتر از نیمی از اندازه اصلی. این چه تأثیری بر FMP و TTI دارد؟ WebPageTest خواهد گفت!

نوار فیلم تایید می کند: TTI ما اکنون در 5.4 ثانیه است. یک پیشرفت چشمگیر نسبت به 11 های اصلی ما.

FMP و TTI ما فقط 100 میلی‌ثانیه از هم فاصله دارند، زیرا فقط بحث تجزیه و اجرای جاوا اسکریپت خطی است. پس از تنها 5.4 ثانیه در 2G، برنامه کاملاً تعاملی است. همه ماژول‌های کمتر ضروری دیگر در پس‌زمینه بارگذاری می‌شوند.

Sleight of Hand بیشتر

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

نتیجه گیری

اندازه گیری مهم است. برای جلوگیری از صرف زمان برای مشکلاتی که واقعی نیستند، توصیه می کنیم همیشه قبل از اجرای بهینه سازی ابتدا اندازه گیری کنید. علاوه بر این، اگر دستگاه واقعی در دسترس نباشد، اندازه‌گیری‌ها باید روی دستگاه‌های واقعی در اتصال 3G یا در WebPageTest انجام شود.

نوار فیلم می‌تواند بینشی در مورد احساس بارگیری برنامه شما برای کاربر ارائه دهد. آبشار می تواند به شما بگوید چه منابعی مسئول زمان بارگیری طولانی هستند. در اینجا چک لیستی از کارهایی است که می توانید برای بهبود عملکرد بارگیری انجام دهید:

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

با بخش 2 همراه باشید، جایی که در مورد چگونگی بهینه سازی عملکرد زمان اجرا در دستگاه های دارای محدودیت بیش از حد بحث می کنیم.

،

چگونه از تقسیم کد، درون‌سازی کد و رندر سمت سرور در PROXX استفاده کردیم.

در Google I/O 2019، ماریکو، جیک و من PROXX را ارسال کردیم، یک مین‌روب مدرن برای وب. چیزی که PROXX را متمایز می کند تمرکز بر دسترسی (شما می توانید آن را با یک صفحه خوان بازی کنید!) و توانایی اجرای آن روی یک تلفن همراه مانند یک دستگاه رومیزی پیشرفته است. تلفن های دارای ویژگی به چند روش محدود می شوند:

  • CPU های ضعیف
  • پردازنده‌های گرافیکی ضعیف یا موجود نیستند
  • صفحه نمایش های کوچک بدون ورودی لمسی
  • حافظه بسیار محدود

اما آنها یک مرورگر مدرن اجرا می کنند و بسیار مقرون به صرفه هستند. به همین دلیل، تلفن های همراه در بازارهای نوظهور دوباره احیا می کنند. قیمت آنها به مخاطبان کاملاً جدیدی که قبلاً توانایی پرداخت آن را نداشتند، اجازه می دهد تا آنلاین شوند و از وب مدرن استفاده کنند. برای سال 2019 پیش‌بینی می‌شود که حدود 400 میلیون تلفن هوشمند تنها در هند فروخته شود ، بنابراین کاربران تلفن‌های ویژگی ممکن است بخش قابل توجهی از مخاطبان شما باشند. علاوه بر آن، سرعت اتصال مشابه 2G در بازارهای در حال ظهور عادی است. چگونه توانستیم PROXX را تحت شرایط تلفن همراه به خوبی کار کنیم؟

گیم پلی PROXX.

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

این قسمت 1 از یک مجموعه دو قسمتی است. بخش 1 بر عملکرد بارگذاری تمرکز دارد و بخش 2 بر عملکرد زمان اجرا تمرکز خواهد کرد.

تسخیر وضعیت موجود

آزمایش عملکرد بارگیری خود در یک دستگاه واقعی بسیار مهم است. اگر دستگاه واقعی در دسترس ندارید، من WebPageTest را توصیه می‌کنم، مخصوصاً راه‌اندازی «ساده» را. WPT یک باتری از تست های بارگیری را روی یک دستگاه واقعی با اتصال 3G شبیه سازی شده اجرا می کند.

3G سرعت خوبی برای اندازه گیری است. در حالی که ممکن است به 4G، LTE یا به زودی حتی 5G عادت کرده باشید، واقعیت اینترنت موبایل کاملاً متفاوت به نظر می رسد. شاید در قطار، کنفرانس، کنسرت یا پرواز هستید. آنچه شما در آنجا تجربه خواهید کرد به احتمال زیاد به 3G نزدیکتر است و گاهی اوقات حتی بدتر.

همانطور که گفته شد، ما در این مقاله بر روی 2G تمرکز خواهیم کرد زیرا PROXX به صراحت تلفن های همراه و بازارهای نوظهور را در مخاطبان هدف خود هدف قرار می دهد. هنگامی که WebPageTest آزمایش خود را انجام داد، یک آبشار (مشابه آنچه در DevTools می بینید) و همچنین یک نوار فیلم در بالا دریافت می کنید. نوار فیلم نشان می دهد که کاربر شما هنگام بارگیری برنامه شما چه می بیند. در 2G، تجربه بارگیری نسخه بهینه نشده PROXX بسیار بد است:

ویدیوی نوار فیلم نشان می دهد که کاربر هنگام بارگیری PROXX بر روی یک دستگاه واقعی و ارزان قیمت از طریق اتصال 2G شبیه سازی شده، چه می بیند.

وقتی کاربر از طریق 3G بارگذاری می شود، 4 ثانیه عدم وجود سفید را می بیند. بیش از 2G کاربر برای بیش از 8 ثانیه مطلقاً چیزی نمی بیند. اگر دلیل اهمیت عملکرد را می خوانید، می دانید که ما اکنون بخش خوبی از کاربران بالقوه خود را به دلیل بی حوصلگی از دست داده ایم. کاربر باید تمام 62 کیلوبایت جاوا اسکریپت را دانلود کند تا هر چیزی روی صفحه نمایش داده شود. پوشش نقره ای در این سناریو این است که دومین چیزی که روی صفحه ظاهر می شود، تعاملی است. یا هست؟

[First Meaningful Paint][FMP] در نسخه بهینه نشده PROXX از نظر فنی [تعاملی][TTI] است اما برای کاربر بی فایده است.

پس از دانلود حدود 62 کیلوبایت gzip'd JS و ایجاد DOM، کاربر برنامه ما را مشاهده می کند. این برنامه از نظر فنی تعاملی است. با این حال، نگاه به تصویر، واقعیت دیگری را نشان می دهد. فونت‌های وب همچنان در پس‌زمینه بارگذاری می‌شوند و تا زمانی که آماده شوند، کاربر نمی‌تواند متنی را ببیند. در حالی که این حالت به عنوان اولین رنگ معنی دار (FMP) واجد شرایط است، مطمئناً واجد شرایط تعامل مناسب نیست، زیرا کاربر نمی تواند بگوید که هر یک از ورودی ها در مورد چیست. یک ثانیه دیگر در 3G و 3 ثانیه در 2G طول می کشد تا برنامه آماده اجرا شود. در مجموع، این برنامه 6 ثانیه در 3G و 11 ثانیه در 2G طول می کشد تا تعاملی شود.

تجزیه و تحلیل آبشار

اکنون که می دانیم کاربر چه چیزی را می بیند، باید دلیل آن را بفهمیم. برای این ما می توانیم به آبشار نگاه کنیم و تحلیل کنیم که چرا منابع خیلی دیر بار می شوند. در ردیابی 2G ما برای PROXX می توانیم دو پرچم قرمز اصلی را ببینیم:

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

کاهش تعداد اتصالات

هر خط نازک ( dns , connect , ssl ) مخفف ایجاد یک اتصال HTTP جدید است. راه اندازی یک اتصال جدید پرهزینه است زیرا در 3G حدود 1 ثانیه و در 2G تقریباً 2.5 ثانیه طول می کشد. در آبشار ما یک اتصال جدید برای:

  • درخواست شماره 1: index.html ما
  • درخواست شماره 5: سبک های فونت از fonts.googleapis.com
  • درخواست شماره 8: Google Analytics
  • درخواست شماره 9: یک فایل فونت از fonts.gstatic.com
  • درخواست شماره 14: مانیفست برنامه وب

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

با این حال، دو فونت و سبک آنها مشکل ساز هستند زیرا رندر و همچنین تعامل را مسدود می کنند. اگر به CSS ارائه شده توسط fonts.googleapis.com نگاه کنیم، این فقط دو قانون @font-face است، یکی برای هر فونت. سبک های فونت در واقع آنقدر کوچک هستند که تصمیم گرفتیم آن را در HTML خود قرار دهیم و یک اتصال غیر ضروری را حذف کنیم. برای جلوگیری از هزینه راه‌اندازی اتصال برای فایل‌های فونت، می‌توانیم آنها را در سرور خود کپی کنیم.

موازی کردن بارها

با نگاهی به آبشار، می بینیم که پس از بارگذاری اولین فایل جاوا اسکریپت، فایل های جدید بلافاصله بارگذاری می شوند. این برای وابستگی های ماژول معمولی است. ماژول اصلی ما احتمالا دارای واردات ثابت است، بنابراین جاوا اسکریپت نمی تواند تا زمانی که آن واردات بارگذاری شود اجرا شود. نکته مهمی که در اینجا باید بدانیم این است که این نوع وابستگی ها در زمان ساخت شناخته می شوند. می‌توانیم از تگ‌های <link rel="preload"> استفاده کنیم تا مطمئن شویم که همه وابستگی‌ها در ثانیه‌ای که HTML خود را دریافت می‌کنیم شروع به بارگیری می‌کنند.

نتایج

بیایید نگاهی بیندازیم که تغییرات ما چه دستاوردهایی داشته است. مهم است که هیچ متغیر دیگری را در تنظیمات تست خود تغییر ندهید که می تواند نتایج را تغییر دهد، بنابراین ما از تنظیمات ساده WebPageTest برای بقیه این مقاله استفاده می کنیم و به نوار فیلم نگاه می کنیم:

ما از نوار فیلم WebPageTest استفاده می کنیم تا ببینیم تغییرات ما به چه چیزی رسیده است.

این تغییرات TTI ما را از 11 به 8.5 کاهش داد ، که تقریباً 2.5 ثانیه زمان تنظیم اتصال است که ما قصد حذف آن را داشتیم. آفرین به ما

پیش اجرا

در حالی که ما فقط TTI خود را کاهش دادیم، اما واقعاً روی صفحه سفید طولانی و ابدی که کاربر باید برای 8.5 ثانیه تحمل کند، تأثیری نداشته است. مسلماً بزرگترین پیشرفت ها برای FMP را می توان با ارسال نشانه گذاری سبک در index.html خود به دست آورد . تکنیک‌های رایج برای دستیابی به این هدف، رندر از قبل و رندر سمت سرور است که ارتباط نزدیکی با هم دارند و در Rendering در وب توضیح داده شده‌اند. هر دو تکنیک برنامه وب را در Node اجرا می کنند و DOM حاصل را به HTML سریال می کنند. رندر سمت سرور این کار را به ازای هر درخواست در سمت سرور انجام می دهد، در حالی که پیش رندر این کار را در زمان ساخت انجام می دهد و خروجی را به عنوان index.html جدید شما ذخیره می کند. از آنجایی که PROXX یک برنامه JAMStack است و هیچ سمت سروری ندارد، تصمیم گرفتیم پیش اجرا را اجرا کنیم.

راه های زیادی برای پیاده سازی پیش اجرا وجود دارد. در PROXX ما استفاده از Puppeteer را انتخاب کردیم که Chrome را بدون هیچ رابط کاربری راه‌اندازی می‌کند و به شما امکان می‌دهد آن نمونه را با یک Node API از راه دور کنترل کنید. ما از این برای تزریق نشانه گذاری و جاوا اسکریپت خود استفاده می کنیم و سپس DOM را به عنوان رشته ای از HTML بازخوانی می کنیم. از آنجایی که ما از ماژول‌های CSS استفاده می‌کنیم، سبک‌هایی را که به آن نیاز داریم به صورت رایگان در قالب CSS دریافت می‌کنیم.

  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setContent(rawIndexHTML);
  await page.evaluate(codeToRun);
  const renderedHTML = await page.content();
  browser.close();
  await writeFile("index.html", renderedHTML);

با وجود این، می‌توانیم انتظار بهبودی برای FMP خود داشته باشیم. ما هنوز باید همان مقدار جاوا اسکریپت قبلی را بارگذاری و اجرا کنیم، بنابراین نباید انتظار داشته باشیم که TTI تغییر زیادی کند. در هر صورت، index.html ما بزرگتر شده است و ممکن است کمی TTI ما را به عقب براند. تنها یک راه برای پیدا کردن وجود دارد: اجرای WebPageTest.

نوار فیلم بهبود واضحی را برای متریک FMP ما نشان می دهد. TTI عمدتاً تحت تأثیر قرار نمی گیرد.

اولین رنگ معنادار ما از 8.5 ثانیه به 4.9 ثانیه رسیده است که یک پیشرفت عظیم است. TTI ما هنوز در حدود 8.5 ثانیه اتفاق می افتد، بنابراین تا حد زیادی تحت تأثیر این تغییر قرار نگرفته است. کاری که ما اینجا انجام دادیم یک تغییر ادراکی است. برخی حتی ممکن است آن را یک اهمال کاری خطاب کنند. با ارائه تصویری متوسط ​​از بازی، عملکرد بارگذاری درک شده را برای بهتر شدن تغییر می دهیم.

خط کشی

معیار دیگری که هم DevTools و هم WebPageTest به ما می دهند، Time To First Byte (TTFB) است. این مدت زمانی است که از اولین بایت درخواست ارسال شده تا اولین بایت پاسخ دریافت شده طول می کشد. این زمان اغلب یک زمان رفت و برگشت (RTT) نیز نامیده می شود، اگرچه از نظر فنی بین این دو عدد تفاوت وجود دارد: RTT شامل زمان پردازش درخواست در سمت سرور نمی شود. DevTools و WebPageTest TTFB را با رنگ روشن در بلوک درخواست/پاسخ تجسم می کنند.

بخش نور یک درخواست نشان می دهد که درخواست در انتظار دریافت اولین بایت پاسخ است.

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

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

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

با جاوا اسکریپت ما TTI خود را از 8.5 به 7.2 کاهش داده ایم.

این 1 ثانیه از TTI ما کم کرد. اکنون به نقطه‌ای رسیده‌ایم که index.html ما حاوی هر چیزی است که برای رندر اولیه و تعاملی شدن لازم است. HTML می تواند در حالی که هنوز در حال بارگیری است رندر شود و FMP ما را ایجاد کند. لحظه ای که HTML تجزیه و اجرا می شود، برنامه تعاملی است.

تقسیم کد تهاجمی

بله، index.html ما شامل همه چیزهایی است که برای تعاملی شدن لازم است. اما با بررسی دقیق‌تر معلوم می‌شود که شامل هر چیز دیگری نیز می‌شود. index.html ما حدود 43 کیلوبایت است. بیایید آن را در رابطه با آنچه کاربر می‌تواند در ابتدا با آن تعامل داشته باشد، در نظر بگیریم: ما یک فرم برای پیکربندی بازی داریم که شامل چند مؤلفه، یک دکمه شروع و احتمالاً مقداری کد برای تداوم و بارگیری تنظیمات کاربر است. تقریباً همین است. 43 کیلوبایت زیاد به نظر می رسد.

صفحه فرود PROXX. در اینجا فقط از اجزای حیاتی استفاده می شود.

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

تجزیه و تحلیل محتویات «index.html» PROXX منابع غیر ضروری زیادی را نشان می دهد. منابع حیاتی برجسته می شوند.

کاری که ما باید انجام دهیم تقسیم کد است. تقسیم کد، بسته یکپارچه شما را به قطعات کوچک‌تری تقسیم می‌کند که می‌توانند در صورت تقاضا بارگذاری شوند. باندلرهای محبوب مانند Webpack ، Rollup و Parcel از تقسیم کد با استفاده از import() پویا پشتیبانی می‌کنند. باندلر کد شما را تجزیه و تحلیل می‌کند و همه ماژول‌هایی را که به صورت ایستا وارد می‌شوند، درون خط می‌کند . هر چیزی که به صورت پویا وارد می‌کنید در فایل خودش قرار می‌گیرد و تنها زمانی که فراخوانی import() اجرا شود، از شبکه واکشی می‌شود. البته ضربه زدن به شبکه هزینه دارد و تنها در صورتی باید انجام شود که وقت کافی داشته باشید. مانترا در اینجا این است که ماژول هایی را که در زمان بارگذاری به شدت مورد نیاز هستند وارد کنیم و هر چیز دیگری را به صورت پویا بارگذاری کنیم. اما نباید تا آخرین لحظه منتظر ماژول‌های تنبلی باشید که قطعاً استفاده می‌شوند. فیلم Idle Until Urgent از Phil Walton یک الگوی عالی برای یک میانه سالم بین بارگیری تنبل و بارگیری مشتاق است.

در PROXX ما یک فایل lazy.js ایجاد کردیم که به صورت ایستا هر چیزی را که به آن نیاز نداریم وارد می کند. در فایل اصلی خود، می توانیم lazy.js به صورت پویا وارد کنیم. با این حال، برخی از مؤلفه‌های Preact ما به lazy.js ختم شدند، که معلوم شد کمی پیچیده است زیرا Preact نمی‌تواند مؤلفه‌های تنبل بارگذاری شده را خارج از جعبه کنترل کند. به همین دلیل، ما یک بسته بندی کامپوننت deferred کوچک نوشتیم که به ما اجازه می دهد تا زمانی که کامپوننت واقعی بارگذاری شود، یک مکان نگهدار را ارائه کنیم.

export default function deferred(componentPromise) {
  return class Deferred extends Component {
    constructor(props) {
      super(props);
      this.state = {
        LoadedComponent: undefined
      };
      componentPromise.then(component => {
        this.setState({ LoadedComponent: component });
      });
    }

    render({ loaded, loading }, { LoadedComponent }) {
      if (LoadedComponent) {
        return loaded(LoadedComponent);
      }
      return loading();
    }
  };
}

با وجود این، می توانیم از Promise یک جزء در توابع render() خود استفاده کنیم. برای مثال، مؤلفه <Nebula> ، که تصویر پس‌زمینه متحرک را ارائه می‌کند، در حین بارگیری مؤلفه با یک <div> خالی جایگزین می‌شود. هنگامی که کامپوننت بارگیری شد و آماده استفاده شد، <div> با کامپوننت واقعی جایگزین می شود.

const NebulaDeferred = deferred(
  import("/components/nebula").then(m => m.default)
);

return (
  // ...
  <NebulaDeferred
    loading={() => <div />}
    loaded={Nebula => <Nebula />}
  />
);

با همه این موارد، index.html خود را به 20 کیلوبایت کاهش دادیم، کمتر از نیمی از اندازه اصلی. این چه تأثیری بر FMP و TTI دارد؟ WebPageTest خواهد گفت!

نوار فیلم تایید می کند: TTI ما اکنون در 5.4 ثانیه است. یک پیشرفت چشمگیر نسبت به 11 های اصلی ما.

FMP و TTI ما فقط 100 متر از هم فاصله دارند ، زیرا فقط مسئله تجزیه و اجرای جاوا اسکریپت است. پس از فقط 5.4s در 2G ، برنامه کاملاً تعاملی است. همه ماژول های کمتر ضروری در پس زمینه بارگیری می شوند.

خواب بیشتر دست

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

نتیجه گیری

اندازه گیری مهم است. برای جلوگیری از گذراندن وقت بر روی مشکلاتی که واقعی نیستند ، توصیه می کنیم همیشه قبل از اجرای بهینه سازی ، ابتدا اندازه گیری کنید. علاوه بر این ، در صورت عدم وجود دستگاه واقعی ، باید در دستگاه های واقعی در اتصال 3G یا در وب سایت انجام شود.

FilmStrip می تواند در مورد نحوه بارگذاری برنامه شما برای کاربر بینشی ارائه دهد. آبشار می تواند به شما بگوید چه منابعی مسئول بارگذاری بالقوه طولانی مدت است. در اینجا یک لیست چک از کارهایی که می توانید برای بهبود عملکرد بارگذاری انجام دهید وجود دارد:

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

برای قسمت 2 با ما در ارتباط باشید که در مورد نحوه بهینه سازی عملکرد زمان اجرا در دستگاه های محدود شده بیش از حد بحث می کنیم.

،

چگونه ما از تقسیم کد ، کدگذاری کد و ارائه سمت سرور در Proxx استفاده کردیم.

در Google I/O 2019 Mariko ، Jake و I Proxx ، یک کلون مدرن مین برای وب ارسال کردیم. چیزی که proxx را از هم جدا می کند ، تمرکز بر روی دسترسی است (می توانید آن را با یک صفحه نمایش پخش کنید!) و امکان اجرای آن و همچنین در یک تلفن ویژگی مانند یک دستگاه دسک تاپ با کیفیت بالا. تلفن های ویژگی به روش های مختلفی محدود می شوند:

  • CPU های ضعیف
  • GPU های ضعیف یا غیر موجود
  • صفحه های کوچک بدون ورودی لمسی
  • مقادیر بسیار محدودی از حافظه

اما آنها یک مرورگر مدرن را اداره می کنند و بسیار مقرون به صرفه هستند. به همین دلیل ، تلفن های ویژگی در بازارهای نوظهور تجدید حیات می کنند. قیمت قیمت آنها به مخاطبان کاملاً جدید ، که قبلاً نتوانسته اند آن را بپردازند ، به اینترنت می آید و از وب مدرن استفاده می کند. برای سال 2019 پیش بینی شده است که حدود 400 میلیون تلفن ویژگی فقط در هند فروخته می شوند ، بنابراین کاربران تلفن های دارای ویژگی ممکن است به بخش قابل توجهی از مخاطبان شما تبدیل شوند. علاوه بر آن ، سرعت اتصال شبیه به 2G یک هنجار در بازارهای نوظهور است. چگونه ما توانستیم Proxx را در شرایط تلفن ویژگی به خوبی کار کنیم؟

گیم پلی Proxx.

عملکرد مهم است ، و این شامل عملکرد بارگیری و عملکرد زمان اجرا نیز می شود. نشان داده شده است که عملکرد خوب با افزایش احتباس کاربر ، بهبود تبدیل و مهمتر از همه - درگیری از رشد همبستگی دارد. جرمی واگنر اطلاعات و بینش بسیار بیشتری در مورد اهمیت عملکرد دارد.

این قسمت 1 از یک سری دو قسمتی است. قسمت 1 بر روی بارگذاری عملکرد متمرکز است و قسمت 2 بر عملکرد زمان اجرا متمرکز خواهد شد.

گرفتن وضع موجود

آزمایش عملکرد بارگیری خود بر روی یک دستگاه واقعی بسیار مهم است. اگر دستگاه واقعی در دست ندارید ، من WebPagetest ، به ویژه تنظیم "ساده" را توصیه می کنم. WPT باتری تست های بارگیری را بر روی یک دستگاه واقعی با اتصال 3G تقلید انجام می دهد.

3G سرعت خوبی برای اندازه گیری است. در حالی که ممکن است شما به 4G ، LTE یا حتی به زودی 5G عادت داشته باشید ، واقعیت اینترنت موبایل کاملاً متفاوت به نظر می رسد. شاید شما در یک قطار ، در یک کنفرانس ، در یک کنسرت یا پرواز باشید. آنچه در آنجا تجربه خواهید کرد به احتمال زیاد به 3G نزدیکتر است و گاهی اوقات حتی بدتر.

گفته می شود ، ما در این مقاله می خواهیم روی 2G تمرکز کنیم زیرا Proxx صریحاً تلفن های ویژگی و بازارهای نوظهور را در مخاطبان هدف خود هدف قرار می دهد. هنگامی که WebPagetest آزمایش خود را انجام داد ، یک آبشار (شبیه به آنچه در Devtools می بینید) و همچنین یک فیلمبرداری در بالا دریافت می کنید. نوار فیلم نشان می دهد که کاربر شما هنگام بارگیری برنامه شما چه چیزی را می بیند. در 2G ، تجربه بارگذاری نسخه بهینه نشده Proxx بسیار بد است:

فیلم FilmStrip نشان می دهد که کاربر هنگام بارگیری در یک دستگاه واقعی و کم مصرف از طریق اتصال 2G تقلید شده ، چه چیزی را می بیند.

هنگامی که بیش از 3G بارگیری می شود ، کاربر 4 ثانیه از هیچ چیز سفیدی را می بیند. بیش از 2G کاربر بیش از 8 ثانیه چیزی نمی بیند. اگر می خوانید چرا عملکرد مهم است می دانید که ما اکنون به دلیل بی حوصلگی بخش خوبی از کاربران بالقوه خود را از دست داده ایم. کاربر برای نمایش هر چیزی در صفحه ، باید تمام 62 کیلوبایت جاوا اسکریپت را بارگیری کند. پوشش نقره ای در این سناریو این است که دومین هر چیزی که روی صفحه نمایش ظاهر می شود نیز تعاملی است. یا هست؟

[اولین رنگ معنی دار] [FMP] در نسخه بهینه نشده Proxx از نظر فنی_ [تعاملی] [TTI] اما برای کاربر بی فایده است.

پس از حدود 62 کیلوبایت از GZIP'D JS بارگیری شده و DOM تولید شده است ، کاربر می تواند برنامه ما را ببیند. برنامه از نظر فنی تعاملی است. با این حال ، با نگاهی به بصری ، واقعیت متفاوتی را نشان می دهد. فونت های وب هنوز در پس زمینه بارگیری می شوند و تا زمانی که آماده نشوند ، کاربر نمی تواند متنی را ببیند. در حالی که این حالت به عنوان اولین رنگ معنی دار (FMP) واجد شرایط است ، مطمئناً واجد شرایط به درستی تعاملی نیست ، زیرا کاربر نمی تواند بگوید که هر یک از ورودی ها چیست. ثانیه دیگر در 3G و 3 ثانیه در 2G طول می کشد تا برنامه آماده شود. در کل ، برنامه در 3G و 11 ثانیه در 2G 6 ثانیه طول می کشد تا تعاملی شود.

تجزیه و تحلیل آبشار

اکنون که می دانیم کاربر چه چیزی را می بیند ، باید بدانیم که چرا . برای این کار می توانیم به آبشار نگاه کنیم و تجزیه و تحلیل کنیم که چرا منابع خیلی دیر بارگیری می شوند. در ردیابی 2G ما برای Proxx می توانیم دو پرچم اصلی قرمز را ببینیم:

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

کاهش تعداد اتصال

هر خط نازک ( dns ، connect ، ssl ) مخفف ایجاد اتصال HTTP جدید است. تنظیم یک اتصال جدید پرهزینه است زیرا حدود 1s در 3G و تقریباً 2.5s در 2G طول می کشد. در آبشار ما ارتباط جدیدی برای:

  • درخواست شماره 1: index.html ما
  • درخواست شماره 5: سبک های قلم از fonts.googleapis.com
  • درخواست شماره 8: Google Analytics
  • درخواست شماره 9: یک پرونده قلم از fonts.gstatic.com
  • درخواست شماره 14: آشکار برنامه وب

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

با این حال ، این دو قلم و سبک های آنها مشکلی است زیرا آنها را مسدود می کنند و همچنین تعامل دارند. اگر به CSS که توسط fonts.googleapis.com تحویل داده می شود نگاه کنیم ، این فقط دو قانون @font-face است ، یکی برای هر قلم. در واقع سبک های قلم آنقدر کوچک هستند که تصمیم گرفتیم آن را به HTML خود وارد کنیم و یک ارتباط غیر ضروری را از بین ببریم. برای جلوگیری از هزینه تنظیم اتصال برای پرونده های قلم ، می توانیم آنها را در سرور خودمان کپی کنیم.

بارهای موازی سازی

با نگاهی به آبشار ، می بینیم که پس از بارگیری اولین پرونده JavaScript ، پرونده های جدید بلافاصله بارگیری می شوند. این برای وابستگی ماژول معمولی است. ماژول اصلی ما احتمالاً دارای واردات استاتیک است ، بنابراین JavaScript تا زمان بارگیری آن واردات نمی تواند اجرا شود. نکته مهمی که در اینجا متوجه می شوید این است که این نوع وابستگی ها در زمان ساخت شناخته شده است. ما می توانیم از برچسب های <link rel="preload"> استفاده کنیم تا اطمینان حاصل کنیم که همه وابستگی ها بارگیری دوم را که ما HTML را دریافت می کنیم ، شروع می کنند.

نتایج

بیایید نگاهی بیندازیم که تغییرات ما به چه چیزی دست یافته است. مهم است که هیچ متغیرهای دیگری را در تنظیمات تست خود تغییر ندهیم که می تواند نتایج را به دست آورد ، بنابراین ما برای بقیه این مقاله از تنظیم ساده WebPagetest استفاده خواهیم کرد و به FilmStrip نگاه خواهیم کرد:

ما از FilmStrip WebPagetest استفاده می کنیم تا ببینیم تغییرات ما به چه چیزی دست یافته است.

این تغییرات TTI ما را از 11 به 8.5 کاهش می دهد ، که تقریباً 2.5s از زمان تنظیم اتصال است که ما قصد داریم حذف کنیم. آفرین ما

پیش اجرا

در حالی که ما فقط TTI خود را کاهش داده ایم ، ما واقعاً روی صفحه نمایش سفید ابدی که کاربر باید 8.5 ثانیه تحمل کند ، تأثیر نگذاشته ایم. احتمالاً بزرگترین پیشرفت برای FMP را می توان با ارسال نشانه گذاری به سبک در index.html شما بدست آورد . تکنیک های متداول برای دستیابی به این هدف ، ارائه پیش نویس و ارائه سمت سرور است که از نزدیک مرتبط هستند و در ارائه در وب توضیح داده شده است. هر دو تکنیک برنامه وب را در گره اجرا می کنند و DOM حاصل را به HTML سریال می کنند. رندر سمت سرور این کار را در هر درخواست در طرف سرور انجام می دهد ، در حالی که پیش نویس این کار را در زمان ساخت انجام می دهد و خروجی را به عنوان index.html خود ذخیره می کند. از آنجا که Proxx یک برنامه JAMSTACK است و طرف سرور ندارد ، ما تصمیم گرفتیم که پیش نویس را پیاده سازی کنیم.

روش های زیادی برای اجرای یک پیش نویس وجود دارد. در Proxx تصمیم گرفتیم از Puppeteer استفاده کنیم ، که Chrome را بدون هیچ گونه UI شروع می کند و به شما امکان می دهد کنترل از راه دور آن نمونه را با API گره انجام دهید. ما از این استفاده می کنیم تا نشانه گذاری و جاوا اسکریپت خود را تزریق کنیم و سپس DOM را به عنوان رشته ای از HTML بخوانیم. از آنجا که ما از ماژول های CSS استفاده می کنیم ، CSS را از سبک هایی که برای رایگان نیاز داریم ، دریافت می کنیم.

  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setContent(rawIndexHTML);
  await page.evaluate(codeToRun);
  const renderedHTML = await page.content();
  browser.close();
  await writeFile("index.html", renderedHTML);

با این کار ، ما می توانیم انتظار پیشرفت برای FMP خود را داشته باشیم. ما هنوز هم باید همان مقدار جاوا اسکریپت را بارگیری و اجرا کنیم ، بنابراین نباید انتظار داشته باشیم که TTI زیاد تغییر کند. در هر صورت ، index.html ما بزرگتر شده است و ممکن است TTI ما را کمی عقب بکشد. فقط یک راه برای پیدا کردن وجود دارد: اجرای WebPagetest.

فیلم Strip پیشرفت روشنی را برای متریک FMP ما نشان می دهد. TTI بیشتر بی تأثیر است.

اولین رنگ معنی دار ما از 8.5 ثانیه به 4.9 ثانیه منتقل شده است که پیشرفت گسترده ای است. TTI ما هنوز در حدود 8.5 ثانیه اتفاق می افتد ، بنابراین تا حد زیادی تحت تأثیر این تغییر قرار گرفته است. کاری که ما در اینجا انجام دادیم یک تغییر ادراکی است. برخی حتی ممکن است آن را خواب آور بنامیم. با ارائه تصویری واسطه ای از بازی ، ما عملکرد بارگذاری درک شده را برای بهتر تغییر می دهیم.

بی پروا

متریک دیگری که هم Devtools و هم WebPagetest به ما می دهند زمان آن است که ابتدا بایت (TTFB) داشته باشیم . این زمانی است که از اولین بایت درخواست ارسال شده به اولین بایت پاسخ دریافتی می شود. این زمان نیز اغلب زمان سفر دور (RTT) خوانده می شود ، اگرچه از نظر فنی بین این دو شماره تفاوت وجود دارد: RTT شامل زمان پردازش درخواست در سمت سرور نیست. DevTools و WebPagetest TTFB را با رنگ روشن در بلوک درخواست/پاسخ تجسم می کنند.

بخش نور یک درخواست نشان می دهد که درخواست در انتظار دریافت اولین بایت پاسخ است.

با نگاهی به آبشار ما ، می بینیم که همه درخواست ها بیشتر وقت خود را صرف انتظار اولین بایت پاسخ می کنند.

این مشکل همان چیزی بود که فشار HTTP/2 در ابتدا برای آن تصور می شد. توسعه دهنده برنامه می داند که منابع خاصی لازم است و می تواند آنها را به سمت سیم سوق دهد . تا زمانی که مشتری متوجه می شود که نیاز به منابع اضافی دارد ، آنها در حال حاضر در ذخیره های مرورگر قرار دارند. فشار HTTP/2 به نظر می رسد خیلی سخت است و به نظر می رسد دلسردانه محسوب می شود. این فضای مشکل در طول استاندارد سازی HTTP/3 دوباره مورد بررسی قرار می گیرد. در حال حاضر ، ساده ترین راه حل این است که تمام منابع مهم را با هزینه کارآیی ذخیره سازی وارد کنید .

CSS بحرانی ما به لطف ماژول های CSS و Prerenderer مبتنی بر Puppeteer ما در حال حاضر وارد شده است. برای JavaScript ما باید ماژول های بحرانی و وابستگی های آنها را وارد کنیم. این کار بر اساس دسته ای که از آن استفاده می کنید ، مشکل مختلفی دارد.

با استفاده از جاوا اسکریپت ما TTI خود را از 8.5s به 7.2s کاهش داده ایم.

این 1 ثانیه از TTI ما تراشیده است. ما اکنون به جایی رسیده ایم که index.html شامل همه چیزهایی است که برای ارائه اولیه لازم است و تعاملی می شود. HTML می تواند در حالی که هنوز در حال بارگیری است ، ارائه می دهد و FMP ما را ایجاد می کند. لحظه ای که HTML تجزیه و اجرا می شود ، برنامه تعاملی است.

تقسیم کد تهاجمی

بله ، index.html ما حاوی همه چیزهایی است که برای تعاملی لازم است. اما در بازرسی دقیق تر معلوم می شود که شامل همه چیز دیگر نیز می شود. index.html ما در حدود 43 کیلوبایت است. بیایید این موضوع را در رابطه با آنچه کاربر می تواند در ابتدا با آن تعامل داشته باشد ، بیان کنیم: ما یک فرم برای پیکربندی بازی حاوی چند مؤلفه ، یک دکمه شروع و احتمالاً برخی از کد ها برای ادامه تنظیمات کاربر داریم. تقریباً همین است. 43 کیلوبایت بسیار زیاد به نظر می رسد.

صفحه فرود Proxx. فقط در اینجا اجزای بحرانی استفاده می شود.

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

تجزیه و تحلیل محتویات `index.html 'Proxx منابع غیر ضروری زیادی را نشان می دهد. منابع بحرانی برجسته می شوند.

کاری که ما باید انجام دهیم تقسیم کد است. تقسیم کد بسته نرم افزاری یکپارچه شما را به قسمت های کوچکتر که می تواند در صورت تقاضا تنبل باشد ، جدا می کند. بسته های محبوب مانند Webpack ، Rollup و Parcel Code Code با استفاده از import() تقسیم می شوند. این دسته ، کد شما را تجزیه و تحلیل می کند و تمام ماژول هایی را که به صورت آماری وارد می شوند ، تجزیه و تحلیل می کند . هر آنچه را که به صورت پویا وارد می کنید ، در پرونده خود قرار می گیرد و پس از اجرای تماس import() فقط از شبکه خارج می شود. البته ضربه زدن به شبکه هزینه ای دارد و فقط در صورت داشتن وقت برای پس انداز کردن باید انجام شود. مانترا در اینجا واردات ماژول هایی است که در زمان بار به شدت مورد نیاز هستند و به طور پویا همه چیز را بارگیری می کنند. اما شما نباید منتظر آخرین لحظه به ماژول های تنبل باشید که قطعاً مورد استفاده قرار می گیرند. بیکار فیل والتون تا زمانی که فوری الگوی خوبی برای یک میانه سالم بین بارگذاری تنبل و بارگذاری مشتاق باشد.

در Proxx ما یک پرونده lazy.js ایجاد کردیم که به صورت استاتیک هر آنچه را که ما به آن احتیاج نداریم وارد می کند. در پرونده اصلی ما ، ما می توانیم به صورت پویا lazy.js وارد کنیم. با این حال ، برخی از مؤلفه های Preact ما به lazy.js پایان یافتند ، که معلوم شد کمی عارضه است زیرا Preact نمی تواند اجزای تنبل را از جعبه خارج کند. به همین دلیل ما یک بسته بندی مؤلفه deferred را نوشتیم که به ما امکان می دهد تا یک مکان نگهدارنده را تا زمانی که مؤلفه واقعی بارگیری شود ، ارائه دهیم.

export default function deferred(componentPromise) {
  return class Deferred extends Component {
    constructor(props) {
      super(props);
      this.state = {
        LoadedComponent: undefined
      };
      componentPromise.then(component => {
        this.setState({ LoadedComponent: component });
      });
    }

    render({ loaded, loading }, { LoadedComponent }) {
      if (LoadedComponent) {
        return loaded(LoadedComponent);
      }
      return loading();
    }
  };
}

با استفاده از این کار ، می توانیم از یک وعده از یک مؤلفه در توابع render() خود استفاده کنیم. به عنوان مثال ، مؤلفه <Nebula> ، که تصویر پس زمینه انیمیشن را ارائه می دهد ، در حالی که مؤلفه در حال بارگیری است ، توسط یک <div> خالی جایگزین می شود. پس از بارگیری مؤلفه و آماده استفاده ، <div> با مؤلفه واقعی جایگزین می شود.

const NebulaDeferred = deferred(
  import("/components/nebula").then(m => m.default)
);

return (
  // ...
  <NebulaDeferred
    loading={() => <div />}
    loaded={Nebula => <Nebula />}
  />
);

با وجود همه این موارد ، ما index.html خود را کاهش دادیم. html به 20 کیلوبایت فقط ، کمتر از نیمی از اندازه اصلی. این چه تاثیری در FMP و TTI دارد؟ WebPagetest خواهد گفت!

The FilmStrip تأیید می کند: TTI ما اکنون در 5.4s است. یک پیشرفت شدید از 11s اصلی ما.

FMP و TTI ما فقط 100 متر از هم فاصله دارند ، زیرا فقط مسئله تجزیه و اجرای جاوا اسکریپت است. پس از فقط 5.4s در 2G ، برنامه کاملاً تعاملی است. همه ماژول های کمتر ضروری در پس زمینه بارگیری می شوند.

خواب بیشتر دست

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

نتیجه گیری

اندازه گیری مهم است. برای جلوگیری از گذراندن وقت بر روی مشکلاتی که واقعی نیستند ، توصیه می کنیم همیشه قبل از اجرای بهینه سازی ، ابتدا اندازه گیری کنید. علاوه بر این ، در صورت عدم وجود دستگاه واقعی ، باید در دستگاه های واقعی در اتصال 3G یا در وب سایت انجام شود.

FilmStrip می تواند در مورد نحوه بارگذاری برنامه شما برای کاربر بینشی ارائه دهد. آبشار می تواند به شما بگوید چه منابعی مسئول بارگذاری بالقوه طولانی مدت است. در اینجا یک لیست چک از کارهایی که می توانید برای بهبود عملکرد بارگذاری انجام دهید وجود دارد:

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

برای قسمت 2 با ما در ارتباط باشید که در مورد نحوه بهینه سازی عملکرد زمان اجرا در دستگاه های محدود شده بیش از حد بحث می کنیم.