سرویس‌ورکر در پروژه CRA؛ مزایا و چالش‌ها

معمولا به منظور صرفه‌جویی در وقت برای شروع یک پروژه react، از create-react-app یا به طور مخفف CRA استفاده می‌کنیم. تا قبل از ورژن ۴ cra، وقتی پروژه جدیدی رو ایجاد می‌کردید، به صورت پیش فرض برای تمامی پروژه‌ها فایل service-worker.js در ساختار پروژه وجود داشت (که معمولا هم خودمون پاکش می‌کردیم ?). این سرویس‌ورکر رجیستر نشده بود و کاربر بسته به نیازش میتونست اون رو رجیستر کنه و ازش استفاده کنه. از ورژن ۴ به بعد سرویس‌ورکر به یک ویژگی انتخابی در پروژه cra تبدیل شده و از طریق تمپلیتی مجزا قابل نصب خواهد بود. به همین دلیل اگر با دستور زیر اقدام به ساخت پروژه کنید، پروژه شما فاقد فایل‌ها و تنظیمات سرویس‌ورکر خواهد بود.

npx create-react-app my-app

اما اگر کاربر قصد داشته باشه از سرویس‌ورکر در پروژه خودش استفاده کنه و دوست داشته باشه که پروژه‌اش با این تنظیمات ایجاد بشه، cra یک تمپلیت مجزا آماده کرده که مخصوص برنامه‌های PWA است و سرویس‌ورکر درون اون تمپلیت پیاده‌سازی شده و تنظیمات ابتدایی اون انجام شده. برای ایجاد پروژه با تمپلیت pwa باید به شکل زیر عمل کنیم.

ساخت پروژه CRA با تمپلیت PWA
ساخت پروژه CRA با تمپلیت PWA

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

توجه داشته باشید بعضی از مفاهیمی که در این مقاله مطرح می‌شن صرفا مربوط به تمپلیت pwa میشن و نباید این مفاهیم رو به کل سرویس‌ورکر تعمیم داد.

ساختار پروژه و تغییراتش

ساختار پروژه cra با تمپلیت pwa
ساختار پروژه cra با تمپلیت pwa

تصویر بالا ساختار پروژه ایجاد شده به وسیله تمپلیت pwa رو نشون میده. تفاوت بین پروژه ساخته شده از طریق تمپلیت pwa نسبت به پروژه‌ای که از طریق تمپلیت ساده cra ساخته میشه، شامل موارد زیر میشه:

  • تغییرات درون فایل index.js
  • ایجاد فایل serviceWorkerRegistration.js (ثبت‌نام سرویس‌ورکر)
  • ایجاد فایل service-worker.js (پیاده‌سازی سرویس‌ورکر)

علاوه بر این تغییرات، تنظیماتی هم در وب‌پک (webpack) اعمال میشه و پکیج‌هایی هم نصب میشن که در ادامه به اون‌ها اشاره خواهیم کرد.

دست‌های پشت پرده تمپلیت pwa ??

در این بخش میخوایم تغییراتی رو که به واسطه استفاده از تمپلیت pwa در پروژه امون ایجاد شده رو با جزئیات بیشتری مورد بررسی قرار بدیم و ببینیم هرکدوم از این تغییرات چه کاری انجام میدن و پیاده‌سازی سرویس‌ورکر به چه صورت انجام شده و این سرویس‌ورکر چه امکاناتی در اختیار ما قرار میده.

فایل index.js

تغییرات اعمال شده در فایل index.js به واسطه استفاده از تمپلیت pwa، محدود میشه به یک تنها یک خط کد
که وظیفه فراخوانی تابع رجیستر سرویس‌ورکر رو برعهده داره. اگر به داخل فایل index.js نگاهی بندازیم، میبینیم که به طور پیش‌فرض تابع unregister استفاده شده تا اگر کاربری نیاز به استفاده از سرویس‌ورکر نداشت، سرویس‌ورکری براش نصب نشه. اگر اطمینان دارید که درون برنامه‌اتون از امکانات سرویس‌ورکر استفاده نخواهید کرد، میتونید این خط کد رو به همین صورت نگه دارید. اما در صورت نیاز به استفاده از سرویس‌ورکر و امکانات پیاده‌سازی شده داخلش، نیاز داریم تا تابع unregister رو به register تغییر بدیم تا فرآیند ثبت سرویس‌ورکر از داخل فایل index.js شروع بشه.

تغییرات ایجاد شده درون فایل index
تغییرات ایجاد شده درون فایل index
به طور پیش‌فرض سرویس‌ورکر براتون نصب نخواهد شد. برای استفاده از اون نیاز دارید که تابع unregister رو به register تغییر بدید.

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

منطق پیاده‌سازی شده در تمپلیت pwa برای ثبت سرویس به این صورت هست که در تابع register یک event برای load شدن صفحه رجیستر میشه و داخل هندلر اون روند رجیستر شدن سرویس‌ورکر عملا شروع میشه. در صورتی که ما فراخوانی تابع regitster رو به کامپوننت‌های داخلی پروژه react منتقل کنیم، این event پس از load شدن صفحه رجیستر میشه و در نتیجه هیچ‌گاه فراخوانی نخواهد شد چون صفحه از قبل load شده.

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

فایل serviceWorkerRegistration.js

درون این فایل پیاده‌سازی توابع register و unregister انجام شده. تابع register وظیفه ثبت‌نام سرویس‌ورکر نزد بروزر رو برعهده داره. هر سرویس‌ورکر یک چرخه زندگی (life-cycle) داره که از سه مرحله ‌ثبت‌نام(register)، نصب(install) و فعال(active) تشکیل شده. مرحله اول یعنی رجیستر شدن که اولین مرحله از مراحل چرخه زندگی سرویس‌ورکر هست، مرحله‌ایه که ما به بروزر اعلام می‌کنیم که قصد استفاده از سرویس‌ورکر رو داریم و همچنین فایلی رو که کدهای مربوط به سرویس‌ورکر داخل اون قرار دارن رو به بروزر معرفی می‌کنیم.

فایل سرویس‌ورکر معرفی شده به بروزر توسط تمپلیت pwa، فایل service-worker.js است.

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

  1. پشتیبانی بروزر از سرویس‌ورکر (بروزرهای پشتیبانی کننده)
  2. پروداکشن بودن محیط (به منظور جلوگیری از ساخت سرویس‌ورکر و ایجاد cache در محیط dev)
  3. اطمینان از معرفی فایل سرویس‌ورکر و موجود بودن آن فایل (در صورتی که لوکال باشه)
  4. اطمینان از لود شدن کامل صفحه (به منظور جلوگیری از تداخل فرآیند نصب سرویس‌ورکر و لود صفحه)

چک کردن این شرایط مختص به تمپلیت ‌pwa هست و میشه این شرایط رو بسته به نیازمون تغییر بدیم یا بهشون اضافه کنیم.

دو سرویس ورکر در یک اقلیم نمیگنجن ?

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

  1. از قبل سرویس‌ورکر دیگری برای domain ما ثبت نشده باشه.
  2. از قبل سرویس‌ورکری فعال وجود داشته باشه.

اگر از قبل سرویس‌ورکری وجود نداشته باشه، سرویس‌ورکر جدید، بدون هیچ مشکل و مزاحمتی، وارد مرحله active میشه و آماده است که به پیام‌ها (messages) و eventهای ارسالی پاسخ بده. اما اگر سرویس‌ورکر دیگری از قبل وجود داشته باشه، از اونجایی که ما فقط می‌تونیم یک سرویس‌ورکر فعال داشته باشیم، سرویس‌ورکر جدید به حالت waiting میره و منتظر میمونه تا کار سرویس‌ورکر قدیمی به اتمام برسه. کار سرویس‌ورکر قدیمی تنها زمانی به پایان میرسه که صفحه مورد نظر در بروزر بسته بشه (اگر چندین tab از یک سایت باز باشه، باید تمامی tab ها بسته بشن). پس از اون، سرویس‌ورکر جدید میتونه جایگزین سرویس‌ورکر قدیمی بشه. این رفتاری هست که بروزر به طور پیش فرض از خودش نشون میده. و همین رفتار هم در تمپلیت پیش‌فرض pwa اتفاق میوفته.

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

کانفیگ تابع رجیستر

تابع رجیستری که در قسمت قبل توضیحش دادیم، یک پارامتر اختیاری میگیره به اسم config که یک ابجکت هست. این ابجکت میتونه پراپرتی‌هایی با نام onUpdate و onSuccess داشته باشه که هر کدوم یک callback function هستند. این توابع در صورتی که توسط کانفیگ به تابع رجیستر پاس بدیمشون، در زمانی که نصب سرویس‌ورکر با موفقیت انجام شد، فراخوانی میشن.

  1. اگر از قبل سرویس‌ورکر دیگری برای domain ما ثبت نشده باشه، تابع onSuccess به همراه پارامتر registration فراخوانی میشه.
  2. اگر از قبل سرویس‌ورکری فعال باشه، در این صورت تابع onUpdate به همراه ابجکت registration فراخوانی میشه.
config object
config object

پارامتر registration که به عنوان ورودی به هر دو تابع onsuccess و onupdate پاس داده میشه، ابجکتی هست حاوی اطلاعات مربوط به ثبت‌نام سرویس‌ورکر. اطلاعاتی نظیر اینکه ثبت‌نام الان در چه مرحله‌ای قرار داره، اسکوپ سرویس‌ورکر چی هست، سرویس‌ورکر درون چه فایلی قرار داره و ...

به طور پیش‌فرض، تمپلیت pwa کانفیگ رو به تابع رجیستر پاس نمیده. در نتیجه وقتی که سرویس‌ورکر جدید نصب میشه، اگر سرویس‌ورکر قدیمی در حال فعالیت باشه، باعث میشه که سرویس‌ورکر جدید به حالت waiting بره. در صورتی که بخوایم سرویس‌ورکر جدید بلافاصله جایگزین سرویس‌ورکر قدیمی بشه، می‌تونیم داخل کال‌بک onUpdate اقدام به ارسال پیام skip waiting به سرویس‌ورکر جدید کنیم. این کار باعث میشه که سرویس‌ورکر جدید مرحله waiting رو نادیده بگیره و بلافاصله سرویس‌ورکر قدیمی رو kill کنه و خودش رو جایگزین اون کنه. پس از ارسال پیام و جایگزین شدن سرویس‌ورکر جدید با سرویس‌ورکر قدیمی احتیاج داریم تا صفحه رو رفرش کنیم تا اگر منابع برنامه تغییر کرده باشن، منابع دریافت بشن. تصویر زیر نحوه ارسال پیام skip waiting از داخل تابع onUpdate رو نشون میده:

ارسال پیام skip waiting
ارسال پیام skip waiting

همونطور که توضیح داده شد، ابجکت registration حاوی اطلاعات ثبت‌نام سرویس‌ورکر هست. یکی از این اطلاعات status هست که معرف وضعیتی هست سرویس‌ورکر در اون لحظه توش قرار داره. اگر سرویس‌ورکر تازه نصب شده به حالت waiting رفته باشه، از طریق registration.waitnig قابل دسترسی هست. از این طریق به سرویس‌ورکر در حال انتظار دسترسی داریم و بهش پیامی ارسال می‌کنیم. این پیام از نوع SKIP_WAITING خواهد بود که نحوه گوش‌دادن و پاسخگویی به این پیام توسط تمپلیت pwa از قبل پیاده‌سازی شده و نیازی نیست که ما کاری انجام بدیم.

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

چالش آپدیت شدن بدون بستن برنامه ?

در قسمت قبل دیدیم که با پاس دادن تابع onUpdate در داخل ابجکت config به تابع رجیستر، می‌تونیم به سرویس‌ورکر جدید اعلام کنیم که ‌به حالت waiting نرو و بلافاصله پس از نصب، جایگزین سرویس‌ورکر قدیمی بشو. ولی با تمپلیت pwa که در حال حاضر توسط cra ارائه میشه، در بروزرهای سافاری با چالش مواجه می‌شیم. مشکلی که باهاش روبرو هستیم این هستش که بروزرهای سافاری (به ویژه در ورژن‌های قدیمی‌تر) متوجه آپدیت شدن سرویس‌ورکر نمی‌شن.

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

اما در IOS، به نظر میرسه که جستجو برای سرویس‌ورکر جدید قبل از اجرای برنامه اصلی اتفاق میوفته. به همین دلیل ابتدا سرویس‌ورکر جدید نصب میشه (این کار در زمان نمایش صفحه‌ای موسوم به splashscreen رخ میده) و پس از اون برنامه اصلی اجرا میشه و onupdatefound رو روی سرویس‌ورکر قدیمی رجیستر میکنه (رجیستر شدن event پس از وقوع اون اتفاق میوفته). در نتیجه سرویس‌ورکر قدیمی هیچگاه متوجه وقوع onupdatefound نمیشه، چون سرویس ورکر جدید زودتر نصب شده و به حالت waiting رفته. این مشکل منجر به این میشه که تابع onUpdate فراخوانی نشه و الزاما نیاز داشته باشیم تا برنامه رو به صورت دستی ببندیم تا آپدیت جدید اعمال بشه. علاوه بر مطالب ذکر شده در بالا، onupdatefound event در ورژن‌های قدیمی‌تر IOS به طور کلی وجود نداره و در نتیجه هیچگاه تریگیر نخواهد شد. به همین دلیل بروزر متوجه آپدیت شدن سرویس‌ورکر نمیشه.

این مشکل به صورت یک issue باز در مخزن گیت‌هاب cra وجود داره و براش راه حل‌هایی هم پیشنهاد شده، که میتونید این issue رو در اینجا مشاهده کنید.

فایل service-worker.js ⚙

فایل دیگری که توسط تمپلیت pwa ساخته میشه، فایل service-worker.js هست. در مرحله رجیستر این فایل به عنوان فایلی که کدهای مربوط به سرویس‌ورکر درون اون قرار دارن، به بروزر معرفی شد. در واقع لاجیک سرویس‌ورکر و اینکه چه کار میخواد بکنه، در این فایل پیاده‌سازی شده. اگر فایل service-worker.js رو باز کنید مشاهده می‌کنید که یکسری عملکردهای پیش فرض برای سرویس‌ورکر توسط تمپلیت pwa داخل این فایل پیاده سازی شده. این عملکردها شامل موارد زیر میشه:

  1. پیش کش (pre-cache)
  2. پیاده‌سازی یک routing-cache (به منظور ذخیره‌سازی فایل‌های png داخل cache)
  3. رجیستر کردن event listener برای message events

این پیاده‌سازی‌ها که به صورت پیش‌فرض انجام شدن، این امکان رو به پروژهای ساخته شده از طریق تمپلیت pwa میدن که قابلیت موسوم به offline-first رو داشته باشن. نکته حائز اهمیت این هست که در پیاده‌سازی‌ این فیچرها از پکیج workbox استفاده شده. پکیچ workbox توسط شرکت گوگل توسعه داده شده و امروزه به یک استاندارد برای پیاده‌سازی سرویس‌ورکر تبدیل شده. workbox از چندین پکیج و پلاگین تشکیل شده که پیاده‌سازی سرویس‌ورکرها رو بسیار راحت می‌کنن و این امکان رو به ما میدن که به فرآیند توسعه سرویس‌ورکر سرعت بدیم. پرداختن به این پکیج و امکاناتش از موضوع این بحث خارج هست ولی ما در ادامه مباحثی رو که در cra از اون استفاده شده، به طور مختصر توضیح خواهیم داد. با ساخت پروژه از طریق تمپلیت pwa کلیه پکیج‌های ورک‌باکس به پروژه اضافه خواهد شد.

اگر علاقه‌مند به آشنایی با پکیج workbox هستید می‌تونید از طریق این لینک بیشتر باهاش آشنا بشید.

در ادامه امکاناتی رو که درون فایل سرویس‌ورکر پیاده‌سازی شدن رو مورد بررسی قرار میدیم.

مفهوم pre-caching

مفهوم پیش-کش (pre-cache) به این معنی هست که با استفاده از سرویس‌ورکر (که روی یک thread مجزا اجرا میشه) کلیه قسمت‌های کد پروژه (chunkها) رو که در مرحله بیلد توسط وب‌پک ساخته میشه رو دانلود کنیم و داخل یک کش در سمت کلاینت ذخیره کنیم. این کار باعث میشه کاربر بتونه، در شرایطی که به اینترنت دسترسی ندارد و یا ارتباطش سرعت مناسبی نداره هم از وب اپلیکیشن ما استفاده کنه.

برای پیاده‌سازی سرویس‌ورکر و pre-caching در پروژه‌هایی که از webpack به عنوان ماژول باندلر استفاده میکنن، workbox از پکیجی استفاده میکنه به اسم workbox-webpack-plugin. این پکیج از دو پلاگین با نام های GenerateSW و InjectManifest ساخته شده که به وبپک اضافه میشن و ساخت سرویس ورکر رو به یکی از مراحل پروسه بیلد تبدیل میکنن. CRA به طور پیش فرض از پلاگین InjectManifest استفاده میکنه.

اگر مقایسه بین این دو پلاگین و کاربرد هرکدوم رو بخونید به این لینک مراجعه کنید.

پلاگین InjectManifest ?

به صورت یک پلاگین به وب‌پک اضافه میشه و از ما انتظار داره که یک فایل بهش معرفی کنیم به عنوان سرویس ورکر (که این فایل به طور پیش‌فرض service-worker.js هست در تمپلیت pwa). سپس در مرحله بیلد پروژه، لیست فایلهای تولید شده در پروسه بیلد رو به فایل سرویس‌ورکر که از قبل بهش اعلام کردیم، تزریق (inject) میکنه. لیست فایل‌های ایجاد شده توسط وب‌پک در مرحله build رو میتونید از طریق پوشه build و درون فایل asset-manifest.json مشاهده کنید. تصویر زیر نمونه‌ای از این فایل رو به نمایش گذاشته:

این لیست توسط پلاگین InjectManifest به داخل فایل‌ سرویس‌ورکر تزریق میشه و ما درون این فایل از طریق self.__WB_MANIFEST به این لیست دسترسی داریم. از همین مقدار به منظور شناسایی فایل‌های مورد نیاز برای pre-cache توسط سرویس‌ورکر استفاده میشه.

فایل‌های که پسوند map دارن، در این فرآیند نادیده گرفته میشن.

هر بار که تغییری در فایلهای پروژه به وجود بیاد، هش چانک‌ها در زمان بیلد تغییر میکنه و همین امر باعث میشه که لیستی که InjectManifest به داخل سرویس‌ورکر تزریق میکنه عوض بشه و درنتیجه منجر به این میشه که بروزر متوجه اپدیت بشه و اقدام به نصب یک سرویس‌ورکر جدید کنه.

اگر تصویر بالا رو به دقت نگاه کنید مشاهده خواهید کرد که دو فایل index.html و service-worker.js نیز در داخل لیست فایلهای خروجی وب پک وجود داره و در نتیجه از طریق injectManifest به داخل فایل سرویس‌ورکر تزریق میشه و در نتیجه کش خواهد شد. اما برخلاف چانک‌های برنامه نام این دو فایل به صورت هش شده نیست. پس ممکنه این سوال پیش بیاد که سرویس ورکر از کجا متوجه تغییرات این دو فایل میشه؟

گفتیم که پلاگین injectManifest آرایه‌ای از فایلهای تولید شده از مرحله بیلد رو به داخل فایل سرویس‌ورکر تزریق میکنه. این آرایه از ابجکت تشکیل شده که یک url دارن و یک revision. فایل‌هایی مثل index.html محتویات داخلشون هش میشه و داخل مقدار revision قرار میگیره. از این طریق تغییرات این فایل‌ها میتونه شناسایی بشه. باقی فایل‌ها اطلاعات مربوط به revision اشون داخل url لحاظ شده.

توجه داشته باشید که منطق و لاجیک pre-caching به طور کامل توسط پکیج ورک باکس پیاده سازی شده. وظیفه ما صرفا این هست که فایل‌های تولید شده در مرحله بیلد رو که نیاز به کش شدن هست رو بهش معرفی کنیم که این کار از طریق پاس دادن متغیر self.__WB_MANIFEST به تابع precacheAndRoute انجام میشه.

پیاده‌سازی pre-cache توسط پکیج workbox
پیاده‌سازی pre-cache توسط پکیج workbox
برای آشنایی بیشتر با متد precacheAndRoute و دیگر امکانتش میتونید به این لینک رجوع کنید.

مفهوم route-caching

یک سرویس‌ورکر به عنوان واسط بین کلاینت و سرور قرار میگیره و تمامی درخواست‌ها و پاسخ‌هایی رو که بین این دو رد و بدل میشه رو میتونه رصد کنه و مداخله کنه. زمانی که یک network request فرستاده میشه، یک fetch event ارسال میشه. ما میتونیم سرویس‌ورکرمون رو طوری برنامه‌ریزی کنیم که درخواست‌های ارسالی رو رصد کنه و در صورتی که درخواست ما برای دریافت یک source (مثلا یک عکس) بود، پاسخ دریافتی رو بگیره و درون کش ذخیره کنه.

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

یک route در ورک‌باکس از دو تابع تشکیل شده. تابع matching و تابع handling.

درون تابع matching به url، request و پارامترهای ارسالی دسترسی داریم و از این طریق میتونی بررسی کنیم که آیا این درخواست مد نظر ما هست یا خیر. در صورتی که تابع matching مقدار true رو برگردونه، یعنی match اتفاق افتاده و تابع handler فراخوانی میشه.

نکته مهم: تنها محدودیتی که برای matching وجود داره این هست که باید حتما synchronous باشه و هیچ کار asynchronous نمیتونیم داخلش انجام بدیم.

درون تابع هندلر میتونیم تصمیم بگیریم که با request دریافت شده چه کار کنیم. به طور مثال از ارسال اون جلوگیری کنیم، مقداری رو به هدر درخواست اضافه کنیم و یا منتظر جواب بمونیم و جواب رو داخل کش ذخیره کنیم و ... . این امکان رو داریم که لاجیکش رو خودمون بنویسیم و یا میتونیم از استراتژی‌های از پیش نوشته شده ورک‌باکس استفاده کنیم، این استراتژی‌ها سناریو‌های رایجی رو که مورد استفاده قرار میگیرن رو پیاده‌سازی کردن و ما صرفا با ساخت یک instance از کلاس مربروطه‌اشون ازشون استفاده میکنیم به جای تابع هندلر. این استراتژی ‌ها درون پکیج workbox-strategies پیاده‌سازی شدن.

حال که با تابع registerRoute در ورکباکس تا حدودی آشنا شدیم، میتونید نحوه استفاده از این تابع رو در تمپلیت pwa در تصویر زیر مشاهده کنید.

همونطور که در تصویر مشخص هست درون تابع matching بررسی شده که اگر url که بهش درخواست ارسال میشه با url فعلی ما برابر هست و پسوند فایل درخواستی برابر با png هست، در این صورت تابع هندلر اجرا بشه. برای تابع هندلر نیز از یکی از استراتژی‌های از ورک‌باکس استفاده شده به نام StaleWhileRevalidate و برای کانفیگ ابجکتی بهش پاس داده و نام کش رو تعیین کرده و برای کش محدودیت تعداد 50 رو قرار داده.

رجیستر کردن message event ?

آخرین اقدامی که در تمپلیت pwa برای عملکرد سرویس‌ورکر در نظر گرفتن، رجیستر کردن message event هست. سرویس ورکر چون در ترد مجزایی از ترد اصلی برنامه اجرا میشه، تنها از طریق ارسال message میتونه با ترد اصلی ارتباط برقرار کنه. ارسال پیام از طریق متد postMessage صورت میگیره. با ارسال پیام یک massage event تریگر میشه به همین دلیل در سمت سرویس‌ورکر یک event listener رجیستر شده تا به eventهای فراخوانی شده message واکنش نشون بده و هندلرش رو اجرا کنه. درون هندلر چک شده که اگر مسیج از نوع SKIP_WAITING بود، متد skipWaiting رو فراخوانی میکنه.

به سوی بینهایت و فراتر از آن ?

آنچه که تا اینجا گفته شده قابلیت‌هایی بود که به صورت پیش‌فرض توسط تمپلیت pwa پیاده سازی شده بود و در اختیار ما قرار گرفته بود، و این تنها نقطه ای برای شروع هست. ممکنه این قابلیت‌ها برای پروژه‌های کوچک و شاید متوسط کافی باشه ولی در پروژه‌های بزرگتر ما نیاز داریم تا علمکردها رو متناسب با نیازمون تغییر بدیم. میتونیم هر عملکردی دیگری رو که مدنظرمون هست داخل فایل service-worker.js اضافه کنیم. این پیاده‌سازی میتونه با استفاده از جاوااسکریپ و به وسیله توابع built-in صورت بگیره (که توصیه نمیشه) و هم میتونه به کمک پکیج workbox انجام بشه. در اینجا تنها محدودیتی که میتونه وجود داشته باشه، خود شما و خلاقیت شماست.

نتیجه‌گیری

همونطور که دیدیم، استفاده از تمپلیت pwa به ما کمک میکنه تا بدون یک خط کد زدن و به ساده‌ترین شکل ممکن یک سرویس‌ورکر ایجاد کنیم که قابلیت offline-first رو برای برنامه ما به ارمغان میاره. اما در کنار این مزیت نباید از معایب و چالش‌های همراهش غافل بود. اگر ما از اتفاقاتی که در داخل این تمپلیت میوفته خبر نداشته باشیم نمیتونیم به درستی ازش استفاده کنیم. همچنین دیدیم که این تمپلیت تنها یک boilerplate برای ما ایجاد میکنه و ما باید برحسب نیاز خودمون اون رو توسعه بدیم و تغییراتی رو که مد نظرمون هست اعمال کنیم. در نتیجه آگاهی بیشتر نسبت به فرآیند داخلی این تمپلیت میتونه کار رو برای ما راحت‌تر کنه.

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

امیدوارم که این مقاله تونسته باشه درک عمیق‌تر و درست‌تری از رفتار سرویس‌ورکر ارئه شده توسط cra بهتون داده باشه. ♥

منابع

https://create-react-app.dev/docs/making-a-progressive-web-app/
https://developers.google.com/web/tools/workbox/guides/get-started
https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle