ساختن PWA در گوگل، قسمت 1

آنچه که تیم بولتن در مورد کارگران خدماتی هنگام توسعه PWA یاد گرفت.

داگلاس پارکر
Douglas Parker
دیکلا کوهن
Dikla Cohen

این اولین مورد از مجموعه پست‌های وبلاگ در مورد درس‌هایی است که تیم Google Bulletin هنگام ساختن یک PWA خارجی آموخته است. در این پست‌ها برخی از چالش‌هایی که با آن‌ها روبرو بودیم، رویکردهایی که برای غلبه بر آن‌ها در پیش گرفتیم و توصیه‌های کلی برای اجتناب از دام‌ها را به اشتراک می‌گذاریم. این به هیچ وجه یک نمای کلی از PWA ها نیست. هدف این است که آموخته های حاصل از تجربه تیم ما را به اشتراک بگذاریم.

برای این پست اول، ابتدا اطلاعات پیش زمینه کمی را پوشش می دهیم و سپس به تمام چیزهایی که در مورد کارگران خدمات آموخته ایم، می پردازیم.

زمینه

بولتن از اواسط سال 2017 تا اواسط سال 2019 در حال توسعه فعال بود.

چرا ما ساخت PWA را انتخاب کردیم

قبل از اینکه به فرآیند توسعه بپردازیم، بیایید بررسی کنیم که چرا ساختن PWA یک گزینه جذاب برای این پروژه بود:

  • قابلیت تکرار سریع به خصوص ارزشمند است زیرا بولتن در چندین بازار به صورت آزمایشی اجرا می شود.
  • پایه کد واحد . کاربران ما تقریباً به طور مساوی بین Android و iOS تقسیم شدند. PWA به این معنی است که می‌توانیم یک برنامه وب واحد بسازیم که روی هر دو پلتفرم کار کند. این باعث افزایش سرعت و تاثیر تیم شد.
  • به‌سرعت و مستقل از رفتار کاربر به‌روزرسانی شد . PWA ها می توانند به طور خودکار به روز شوند که باعث کاهش تعداد کلاینت های قدیمی در طبیعت می شود. ما توانستیم تغییرات اساسی را با مدت زمان بسیار کوتاهی برای مهاجرت برای مشتریان انجام دهیم.
  • به راحتی با برنامه های شخص اول و شخص ثالث ادغام می شود. چنین ادغام هایی یک الزام برای برنامه بود. با PWA اغلب به معنای باز کردن یک URL است.
  • اصطکاک نصب یک برنامه را از بین برد.

چارچوب ما

برای Bulletin، از Polymer استفاده کردیم، اما هر چارچوب مدرن و با پشتیبانی خوب کار خواهد کرد.

آنچه در مورد کارگران خدماتی آموختیم

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

اگر می توانید آن را تولید کنید

از نوشتن اسکریپت کارگر خدماتی با دست خودداری کنید. نوشتن دستی کارگران خدماتی مستلزم مدیریت دستی منابع ذخیره شده و منطق بازنویسی است که در اکثر کتابخانه های کارکنان خدماتی مانند Workbox مشترک است.

با این حال، به دلیل پشته فناوری داخلی ما، نمی‌توانیم از کتابخانه برای تولید و مدیریت سرویس‌کارمان استفاده کنیم. آموخته های ما در زیر گاهی منعکس کننده آن خواهد بود. برای خواندن بیشتر به Pitfalls for non-generated services بروید.

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

برخی از کتابخانه‌های JS مفروضاتی را مطرح می‌کنند که وقتی توسط یک سرویس‌دهنده اجرا می‌شوند، آن‌طور که انتظار می‌رود کار نمی‌کنند. به عنوان مثال، با فرض در دسترس بودن window یا document ، یا استفاده از یک API که در دسترس کارکنان خدمات نیست ( XMLHttpRequest ، حافظه محلی، و غیره). اطمینان حاصل کنید که هر کتابخانه مهمی که برای برنامه خود نیاز دارید، با سرویس کارساز سازگار است. برای این PWA خاص، ما می‌خواستیم از gapi.js برای احراز هویت استفاده کنیم، اما نتوانستیم زیرا از سرویس‌دهندگان پشتیبانی نمی‌کرد. نویسندگان کتابخانه همچنین باید فرضیات غیرضروری در مورد زمینه جاوا اسکریپت را تا جایی که ممکن است برای پشتیبانی از موارد استفاده کارکنان سرویس، مانند اجتناب از APIهای ناسازگار با سرویسکار و اجتناب از وضعیت جهانی ، کاهش دهند یا حذف کنند.

از دسترسی به IndexedDB در طول مقداردهی اولیه خودداری کنید

هنگام راه اندازی اسکریپت Service Worker خود، IndexedDB را مطالعه نکنید، در غیر این صورت می توانید به این وضعیت نامطلوب وارد شوید:

  1. کاربر دارای برنامه وب با IndexedDB (IDB) نسخه N است
  2. برنامه وب جدید با IDB نسخه N+1 ارائه شده است
  3. کاربر از PWA بازدید می کند، که باعث دانلود سرویس کار جدید می شود
  4. سرویس‌کار جدید قبل از ثبت‌کننده رویداد install ، از IDB می‌خواند و چرخه ارتقا IDB را برای رفتن از N به N+1 راه‌اندازی می‌کند.
  5. از آنجایی که کاربر دارای کلاینت قدیمی با نسخه N است، فرآیند ارتقای سرویس کارمند متوقف می شود زیرا اتصالات فعال هنوز به نسخه قدیمی پایگاه داده باز هستند.
  6. کارگر سرویس آویزان است و هرگز نصب نمی کند

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

آن را مقاوم کنید

اگرچه اسکریپت‌های Service Worker در پس‌زمینه اجرا می‌شوند، اما می‌توان آن‌ها را در هر زمانی خاتمه داد، حتی زمانی که در وسط عملیات I/O (شبکه، IDB، و غیره) هستند. هر فرآیند طولانی مدت باید در هر نقطه ای قابل از سرگیری باشد.

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

به دولت جهانی وابسته نباش

از آنجایی که کارکنان خدمات در زمینه متفاوتی وجود دارند، بسیاری از نمادها که ممکن است انتظار وجود داشته باشند وجود ندارند. بسیاری از کدهای ما هم در زمینه window و هم در زمینه کارمند سرویس (مانند ورود به سیستم، پرچم‌ها، همگام‌سازی و غیره) اجرا می‌شوند. کد باید در مورد سرویس هایی که استفاده می کند، مانند ذخیره سازی محلی یا کوکی ها، دفاعی باشد. می توانید از globalThis برای ارجاع به شی جهانی به گونه ای استفاده کنید که در همه زمینه ها کار کند. همچنین از داده‌های ذخیره‌شده در متغیرهای سراسری به میزان کم استفاده کنید، زیرا هیچ تضمینی برای پایان یافتن اسکریپت و خروج وضعیت وجود ندارد.

توسعه محلی

یکی از اجزای اصلی کارکنان خدمات، ذخیره منابع محلی است. با این حال، در طول توسعه، این دقیقا برعکس چیزی است که شما می‌خواهید، به ویژه زمانی که به‌روزرسانی‌ها با تنبلی انجام می‌شوند. شما همچنان می‌خواهید که server worker نصب شود تا بتوانید مشکلات آن را رفع اشکال کنید یا با سایر APIها مانند همگام‌سازی پس‌زمینه یا اعلان‌ها کار کنید. در کروم می‌توانید از طریق Chrome DevTools با فعال کردن کادر بررسی Bypass برای شبکه (پنل برنامه > پنجره Service Workers ) به علاوه فعال کردن کادر انتخاب غیرفعال کردن حافظه پنهان در پانل شبکه به منظور غیرفعال کردن حافظه پنهان، به این کار دست یابید. به منظور پوشش مرورگرهای بیشتر، راه حل متفاوتی را با اضافه کردن پرچمی برای غیرفعال کردن حافظه پنهان در سرویس‌کار خود انتخاب کردیم که به طور پیش‌فرض در ساخت‌های توسعه‌دهنده فعال است. این تضمین می کند که توسعه دهندگان همیشه آخرین تغییرات خود را بدون هیچ مشکلی در حافظه پنهان دریافت می کنند. مهم است که هدر Cache-Control: no-cache نیز لحاظ کنید تا از ذخیره هر گونه دارایی توسط مرورگر جلوگیری شود .

فانوس دریایی

Lighthouse تعدادی ابزار اشکال زدایی مفید برای PWA ها ارائه می دهد. این سایت یک سایت را اسکن می کند و گزارش هایی را در مورد PWA ها، عملکرد، دسترسی، SEO و سایر بهترین شیوه ها تولید می کند. ما توصیه می کنیم Lighthouse را روی یکپارچه سازی مداوم اجرا کنید تا در صورت زیر پا گذاشتن یکی از معیارهای PWA به شما هشدار دهد. این در واقع یک بار برای ما اتفاق افتاد، جایی که کارگر سرویس در حال نصب نبود و ما قبل از یک فشار تولید متوجه آن نشدیم. داشتن Lighthouse به عنوان بخشی از CI ما از آن جلوگیری می کرد.

تحویل مداوم را بپذیرید

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

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

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

گاهی اوقات لازم است به مقادیر کوکی در زمینه یک سرویسکار دسترسی داشته باشید. در مورد ما، ما نیاز به دسترسی به مقادیر کوکی برای ایجاد یک توکن برای احراز هویت درخواست‌های API شخص اول داشتیم. در یک سرویس دهنده، APIهای همزمان مانند document.cookies در دسترس نیستند. همیشه می‌توانید برای درخواست مقادیر کوکی‌ها از سرویس‌کار به مشتریان فعال (پنجره‌دار) پیامی ارسال کنید، اگرچه این امکان وجود دارد که سرویس‌کار بدون هیچ کلاینت پنجره‌ای در پس‌زمینه اجرا شود، مانند هنگام همگام‌سازی پس‌زمینه. برای حل این مشکل، یک نقطه پایانی در سرور frontend خود ایجاد کردیم که به سادگی مقدار کوکی را به مشتری بازتاب می داد. کارمند سرویس یک درخواست شبکه به این نقطه پایانی ارائه کرد و برای دریافت مقادیر کوکی پاسخ را خواند.

با انتشار API فروشگاه کوکی ، این راه‌حل دیگر برای مرورگرهایی که از آن پشتیبانی می‌کنند لازم نیست، زیرا دسترسی ناهمزمان به کوکی‌های مرورگر را فراهم می‌کند و می‌تواند مستقیماً توسط کارمند سرویس استفاده شود.

دام برای کارگران خدماتی که تولید نشده اند

مطمئن شوید که اسکریپت Service Worker در صورت تغییر هر فایل ذخیره شده ایستا تغییر می کند

یک الگوی رایج PWA این است که یک سرویس‌کار تمام فایل‌های برنامه استاتیک را در مرحله install خود نصب می‌کند، که مشتریان را قادر می‌سازد تا برای تمام بازدیدهای بعدی مستقیماً حافظه پنهان API ذخیره‌سازی حافظه پنهان را وارد کنند. Service Workers فقط زمانی نصب می شوند که مرورگر تشخیص دهد که اسکریپت Service Worker به نحوی تغییر کرده است، بنابراین باید مطمئن می شدیم که فایل اسکریپت Service Worker به نحوی با تغییر یک فایل کش تغییر کرده است. ما این کار را به صورت دستی با جاسازی یک هش از مجموعه فایل‌های منبع استاتیک در اسکریپت Service Worker خود انجام دادیم، بنابراین هر نسخه یک فایل جاوا اسکریپت سرویس‌کار مجزا تولید می‌کرد. کتابخانه های سرویس دهنده مانند Workbox این فرآیند را برای شما خودکار می کنند.

تست واحد

APIهای Service Worker با افزودن شنوندگان رویداد به شی سراسری عمل می کنند. مثلا:

self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));

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

import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));

با توجه به مشکلات واحد تست یک اسکریپت Service Worker، ما اسکریپت core service worker را تا حد امکان خالی نگه داشتیم و بیشتر پیاده سازی را به ماژول های دیگر تقسیم کردیم. از آنجایی که آن فایل‌ها فقط ماژول‌های استاندارد JS بودند، می‌توان آن‌ها را راحت‌تر با کتابخانه‌های تست استاندارد واحد آزمایش کرد.

منتظر قسمت های 2 و 3 باشید

در قسمت های 2 و 3 این مجموعه در مورد مدیریت رسانه و مسائل مربوط به iOS صحبت خواهیم کرد. اگر می‌خواهید درباره ساخت PWA در Google از ما سؤال کنید، از نمایه‌های نویسنده ما دیدن کنید تا نحوه تماس با ما را بیابید: