اولین برنامه وب پیشرو شما

آخرین به روز رسانی: 30-04-2019

چه چیزی یک برنامه وب، یک برنامه وب پیشرفته را می سازد؟

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

سریع و قابل اعتماد

هر تجربه وب باید سریع باشد، و این به ویژه برای برنامه های وب پیشرفته صادق است. Fast به زمانی اشاره دارد که برای دریافت محتوای معنادار روی صفحه و ارائه یک تجربه تعاملی لازم است.

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

قابل نصب

برنامه های وب پیشرو می توانند در برگه مرورگر اجرا شوند، اما قابل نصب نیز هستند. نشانک کردن یک سایت فقط یک میانبر اضافه می کند، اما یک برنامه وب پیشرفته (PWA) نصب شده مانند همه برنامه های نصب شده دیگر ظاهر و رفتار می کند. از همان جایی که سایر برنامه ها راه اندازی می شوند، راه اندازی می شود. می‌توانید تجربه راه‌اندازی را کنترل کنید، از جمله صفحه نمایش سفارشی شده، نمادها و موارد دیگر. به عنوان یک برنامه، در یک پنجره برنامه بدون نوار آدرس یا دیگر رابط کاربری مرورگر اجرا می شود. مانند همه برنامه‌های نصب‌شده دیگر، این یک برنامه سطح بالا در Task Switcher است.

به یاد داشته باشید، بسیار مهم است که یک PWA قابل نصب سریع و قابل اعتماد باشد. کاربرانی که PWA را نصب می‌کنند، انتظار دارند که برنامه‌هایشان بدون توجه به نوع اتصال شبکه‌ای که دارند کار کنند. این یک انتظار پایه است که باید توسط هر برنامه نصب شده برآورده شود.

موبایل و دسکتاپ

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

چیزی که خواهی ساخت

در این نرم افزار کد، شما قصد دارید با استفاده از تکنیک های PWA یک اپلیکیشن وب آب و هوا بسازید. برنامه شما:

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

چیزی که یاد خواهید گرفت

  • نحوه ایجاد و افزودن مانیفست برنامه وب
  • نحوه ارائه یک تجربه آفلاین ساده
  • نحوه ارائه یک تجربه آفلاین کامل
  • چگونه اپلیکیشن خود را قابل نصب کنیم

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

آنچه شما نیاز دارید

  • نسخه اخیر کروم (74 یا بالاتر). PWA ها در همه مرورگرها کار می کنند، اما ما از چند ویژگی Chrome DevTools برای درک بهتر آنچه در سطح مرورگر اتفاق می افتد استفاده خواهیم کرد و از آن برای آزمایش تجربه نصب استفاده خواهیم کرد.
  • آشنایی با HTML، CSS، جاوا اسکریپت و ابزارهای توسعه کروم .

یک کلید برای Dark Sky API دریافت کنید

داده های آب و هوای ما از Dark Sky API می آید. برای استفاده از آن، باید یک کلید API درخواست کنید. استفاده از آن آسان است و برای پروژه های غیر تجاری رایگان است.

برای کلید API ثبت نام کنید

بررسی کنید که کلید API شما به درستی کار می کند

برای آزمایش اینکه کلید API شما به درستی کار می کند، یک درخواست HTTP به DarkSky API ارسال کنید. URL زیر را به‌روزرسانی کنید تا کلید API خود را جایگزین DARKSKY_API_KEY کنید. اگر همه چیز کار می کند، باید آخرین پیش بینی آب و هوا برای شهر نیویورک را ببینید.

https://api.darksky.net/forecast/DARKSKY_API_KEY/40.7720232,-73.9732319

کد را دریافت کنید

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

به شدت توصیه می شود: از Glitch برای وارد کردن مخزن استفاده کنید

استفاده از Glitch روش توصیه شده برای کار از طریق این کد لبه است.

  1. یک برگه مرورگر جدید باز کنید و به https://glitch.com بروید .
  2. اگر حساب کاربری ندارید، باید ثبت نام کنید.
  3. روی New Project و سپس Clone از Git Repo کلیک کنید.
  4. https://github.com/googlecodelabs/your-first-pwapp.git را کلون کنید و روی OK کلیک کنید.
  5. پس از بارگیری مخزن، فایل .env را ویرایش کنید و آن را با کلید DarkSky API خود به روز کنید.
  6. روی دکمه Show کلیک کنید، سپس In a New Window را انتخاب کنید تا PWA را در عمل ببینید.

جایگزین: دانلود کد و کار به صورت محلی

اگر می‌خواهید کد را دانلود کنید و به صورت محلی کار کنید، باید یک نسخه جدید از Node.js و یک ویرایشگر کد راه‌اندازی و آماده کار داشته باشید.

کد منبع را دانلود کنید

  1. فایل فشرده دانلود شده را باز کنید.
  2. برای نصب وابستگی های مورد نیاز برای اجرای سرور، npm install را اجرا کنید.
  3. server.js را ویرایش کنید و کلید DarkSky API خود را تنظیم کنید.
  4. برای راه اندازی سرور در پورت 8000، node server.js را اجرا کنید.
  5. یک تب مرورگر را به http://localhost:8000 باز کنید

نقطه شروع ما چیست؟

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

چند چیز برای امتحان کردن ...

  1. یک شهر جدید با دکمه + آبی در گوشه سمت راست پایین اضافه کنید.
  2. اطلاعات را با دکمه رفرش در گوشه سمت راست بالا بازخوانی کنید.
  3. یک شهر را با استفاده از x در سمت راست بالای هر کارت شهر حذف کنید.
  4. با استفاده از نوار ابزار جابه‌جایی دستگاه در Chrome DevTools، نحوه عملکرد آن را روی دسک‌تاپ و موبایل ببینید.
  5. با استفاده از پانل شبکه در Chrome DevTools، ببینید وقتی آفلاین می‌شوید چه اتفاقی می‌افتد.
  6. با استفاده از پانل شبکه در Chrome DevTools، ببینید وقتی شبکه به 3G آهسته کاهش می یابد چه اتفاقی می افتد.
  7. با تغییر مقدار server.js FORECAST_DELAY تاخیر به سرور پیش بینی اضافه کنید

حسابرسی با فانوس دریایی

Lighthouse یک ابزار آسان برای کمک به بهبود کیفیت سایت ها و صفحات شما است. Lighthouse ممیزی هایی را برای عملکرد، دسترسی، برنامه های وب مترقی و موارد دیگر اجرا می کند. هر ممیزی دارای یک سند مرجع است که دلیل اهمیت ممیزی و چگونگی رفع مشکلات را توضیح می دهد.

ما از Lighthouse برای بررسی برنامه هواشناسی خود و تأیید تغییراتی که ایجاد کرده‌ایم استفاده می‌کنیم.

بیایید Lighthouse را اجرا کنیم

  1. پروژه خود را در یک تب جدید باز کنید.
  2. Chrome DevTools را باز کنید و به پنل Audits بروید . همه انواع ممیزی را فعال بگذارید.
  3. روی اجرای ممیزی ها کلیک کنید. پس از مدتی، Lighthouse یک گزارش در صفحه به شما می دهد.

حسابرسی برنامه وب پیشرو

ما بر روی نتایج ممیزی برنامه وب پیشرفته تمرکز خواهیم کرد.

و رنگ قرمز زیادی برای تمرکز وجود دارد:

  • ❗شکست خورده: صفحه فعلی در حالت آفلاین با 200 پاسخ نمی دهد.
  • ❗شکست خورد: start_url در حالت آفلاین با 200 پاسخ نمی دهد.
  • ❗شکست خورده: یک سرویس کار که صفحه و start_url.
  • ❗شکست خورده: مانیفست برنامه وب الزامات قابلیت نصب را برآورده نمی کند.
  • ❗شکست خورده: برای صفحه نمایش اسپلش سفارشی پیکربندی نشده است.
  • ❗شکست خورده: رنگ تم نوار آدرس را تنظیم نمی کند.

بیایید وارد شویم و شروع به رفع برخی از این مشکلات کنیم!

در پایان این بخش، برنامه هواشناسی ما ممیزی های زیر را پشت سر می گذارد:

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

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

مانیفست برنامه وب یک فایل JSON ساده است که به شما، توسعه‌دهنده، این توانایی را می‌دهد تا نحوه نمایش برنامه خود را برای کاربر کنترل کنید.

با استفاده از مانیفست برنامه وب، برنامه وب شما می تواند:

  • به مرورگر بگویید که می‌خواهید برنامه شما در یک پنجره ( display ) مستقل باز شود.
  • وقتی برنامه برای اولین بار راه اندازی شد چه صفحه ای باز می شود ( start_url ) را مشخص کنید.
  • تعریف کنید که برنامه در داک یا راه‌انداز برنامه چگونه باشد ( short_name ، icons ).
  • یک صفحه نمایش ( name ، icons ، colors ) ایجاد کنید.
  • به مرورگر بگویید که پنجره را در حالت افقی یا عمودی ( orientation ) باز کند.
  • و خیلی بیشتر .

فایلی با نام public/manifest.json در پروژه خود ایجاد کنید. مطالب زیر را کپی و پیست کنید:

public/manifest.json

{
  "name": "Weather",
  "short_name": "Weather",
  "icons": [{
    "src": "/images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}

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

در مرحله بعد، ما باید با افزودن یک <link rel="manifest"... به هر صفحه در برنامه خود، درباره مانیفست خود به مرورگر بگوییم. خط زیر را به عنصر <head> در فایل index.html خود اضافه کنید.

public/index.html

<!-- CODELAB: Add link rel manifest -->
<link rel="manifest" href="/manifest.json">

انحراف ابزار DevTools

DevTools یک راه سریع و آسان برای بررسی فایل manifest.json ارائه می دهد. پنجره Manifest را در پنل Application باز کنید. اگر اطلاعات مانیفست را به درستی اضافه کرده باشید، می‌توانید آن را تجزیه شده و در قالبی مناسب برای انسان در این صفحه نمایش دهید.

متا تگ ها و آیکون های iOS را اضافه کنید

سافاری در iOS از مانیفست برنامه وب پشتیبانی نمی‌کند ( هنوز )، بنابراین باید meta تگ‌های سنتی را به <head> فایل index.html خود اضافه کنید:

public/index.html

<!-- CODELAB: Add iOS meta tags and icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Weather PWA">
<link rel="apple-touch-icon" href="/images/icons/icon-152x152.png">

امتیاز: رفع آسان فانوس دریایی

ممیزی فانوس دریایی ما چند مورد دیگر را فراخوانی کرد که رفع آنها بسیار آسان است، پس بیایید تا زمانی که اینجا هستیم از آنها مراقبت کنیم.

توضیحات متا را تنظیم کنید

تحت ممیزی SEO، لایت‌هاوس خاطرنشان کرد: " سند ما دارای توضیحات متا نیست. " توضیحات را می توان در نتایج جستجوی Google نمایش داد. توضیحات با کیفیت بالا و منحصر به فرد می تواند نتایج شما را به کاربران جستجو مرتبط تر کند و ترافیک جستجوی شما را افزایش دهد.

برای افزودن توضیحات، meta تگ زیر را به <head> سند خود اضافه کنید:

public/index.html

<!-- CODELAB: Add description here -->
<meta name="description" content="A sample weather app">

رنگ تم نوار آدرس را تنظیم کنید

در ممیزی PWA، Lighthouse به برنامه ما اشاره کرد که " رنگ تم نوار آدرس را تنظیم نمی کند ". قالب بندی نوار آدرس مرورگر برای مطابقت با رنگ های برند شما، تجربه کاربری فراگیرتری را ارائه می دهد.

برای تنظیم رنگ تم در موبایل، meta تگ زیر را به <head> سند خود اضافه کنید:

public/index.html

<!-- CODELAB: Add meta theme-color -->
<meta name="theme-color" content="#2F3BA2" />

تغییرات را با Lighthouse تأیید کنید

Lighthouse را دوباره اجرا کنید (با کلیک بر روی علامت + در گوشه سمت چپ بالای صفحه Audits) و تغییرات خود را تأیید کنید.

ممیزی SEO

  • ✅ تصویب شده: سند دارای توضیحات متا است.

ممیزی برنامه وب پیشرو

  • ❗شکست خورده: صفحه فعلی در حالت آفلاین با 200 پاسخ نمی دهد.
  • ❗شکست خورد: start_url در حالت آفلاین با 200 پاسخ نمی دهد.
  • ❗شکست خورده: یک سرویس کار که صفحه و start_url.
  • ✅ تصویب شد: مانیفست برنامه وب الزامات قابلیت نصب را برآورده می کند.
  • ✅ PASSED: برای یک صفحه نمایش اسپلش سفارشی پیکربندی شده است.
  • ✅ PASSED: رنگ تم نوار آدرس را تنظیم می کند.

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

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

  • صفحه فعلی در حالت آفلاین با 200 پاسخ نمی دهد.
  • start_url در حالت آفلاین با 200 پاسخ نمی دهد.
  • یک سرویس‌کار که صفحه و start_url.

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

خدمت کارگران برای نجات

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

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

کارگر خدماتی را ثبت کنید

اولین قدم ثبت نام کارگر خدماتی است. کد زیر را به فایل index.html خود اضافه کنید:

public/index.html

// CODELAB: Register service worker.
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
        .then((reg) => {
          console.log('Service worker registered.', reg);
        });
  });
}

این کد بررسی می‌کند که آیا API Service Worker در دسترس است یا خیر، و اگر وجود دارد، سرویس‌کار در /service-worker.js پس از بارگیری صفحه ثبت می‌شود.

توجه داشته باشید، سرویس‌کار از دایرکتوری ریشه ارائه می‌شود، نه از دایرکتوری /scripts/ . این ساده ترین راه برای تعیین scope کارمند خدماتی شما است. scope سرویس‌کار تعیین می‌کند که سرویس‌گر کدام فایل‌ها را کنترل می‌کند، به عبارت دیگر، سرویس‌کار از کدام مسیر درخواست‌ها را رهگیری می‌کند. scope پیش‌فرض محل فایل Service Worker است و به همه فهرست‌های زیر گسترش می‌یابد. بنابراین اگر service-worker.js در دایرکتوری ریشه قرار داشته باشد، سرویس‌کار درخواست‌ها را از تمام صفحات وب در این دامنه کنترل خواهد کرد.

پیش کش صفحه آفلاین

ابتدا باید به سرویس‌کار بگوییم چه چیزی را کش کند. ما قبلاً یک صفحه آفلاین ساده ( public/offline.html ) ایجاد کرده‌ایم که هر زمان که اتصال شبکه وجود نداشته باشد آن را نمایش خواهیم داد.

در service-worker.js خود، '/offline.html', را به آرایه FILES_TO_CACHE اضافه کنید، نتیجه نهایی باید به این صورت باشد:

public/service-worker.js

// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
  '/offline.html',
];

در مرحله بعد، باید کد زیر را به رویداد install اضافه کنیم تا به سرویس‌کار بگوییم صفحه آفلاین را از قبل کش کند:

public/service-worker.js

// CODELAB: Precache static resources here.
evt.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log('[ServiceWorker] Pre-caching offline page');
      return cache.addAll(FILES_TO_CACHE);
    })
);

رویداد install ما اکنون کش را با caches.open() باز می کند و نام کش را ارائه می دهد. ارائه یک نام کش به ما امکان می دهد فایل ها را نسخه نسخه کنیم یا داده ها را از منابع ذخیره شده جدا کنیم تا بتوانیم به راحتی یکی را به روز کنیم اما روی دیگری تأثیر نگذاریم.

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

انحراف ابزار DevTools

بیایید نگاهی به نحوه استفاده از DevTools برای درک و اشکال زدایی کارگران سرویس بیندازیم. قبل از بارگیری مجدد صفحه خود، DevTools را باز کنید و به بخش Service Workers در پنل Application بروید. می بایست شبیه به این باشه:

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

اکنون صفحه خود را مجدداً بارگیری کنید. بخش Service Workers اکنون باید به شکل زیر باشد:

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

در کنار برچسب وضعیت، یک عدد (در این مورد 34251 ) وجود دارد. همانطور که با کارگران خدماتی کار می کنید مراقب آن شماره باشید. این یک راه آسان برای تشخیص اینکه آیا سرویس دهنده شما به روز شده است یا خیر.

صفحات آفلاین قدیمی را پاک کنید

ما از رویداد activate برای پاکسازی داده‌های قدیمی در حافظه پنهان استفاده می‌کنیم. این کد تضمین می‌کند که هر زمان که هر یک از فایل‌های پوسته برنامه تغییر می‌کند، سرویس‌کار شما حافظه پنهان خود را به‌روزرسانی می‌کند. برای اینکه این کار انجام شود، باید متغیر CACHE_NAME را در بالای فایل Service Worker خود افزایش دهید.

کد زیر را به رویداد activate خود اضافه کنید:

public/service-worker.js

// CODELAB: Remove previous cached data from disk.
evt.waitUntil(
    caches.keys().then((keyList) => {
      return Promise.all(keyList.map((key) => {
        if (key !== CACHE_NAME) {
          console.log('[ServiceWorker] Removing old cache', key);
          return caches.delete(key);
        }
      }));
    })
);

انحراف ابزار DevTools

با باز شدن پنجره Service Workers، صفحه را بازخوانی کنید. می‌بینید که سرویس‌کار جدید نصب شده و شماره وضعیت افزایش یافته است.

سرویس‌کار به‌روزرسانی‌شده بلافاصله کنترل را در دست می‌گیرد زیرا رویداد install ما با self.skipWaiting() و رویداد activate با self.clients.claim() به پایان می‌رسد. بدون آن‌ها، سرویس‌کار قدیمی تا زمانی که یک برگه در صفحه باز باشد، به کنترل صفحه ادامه می‌دهد.

رسیدگی به درخواست های شبکه ناموفق

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

public/service-worker.js

// CODELAB: Add fetch event handler here.
if (evt.request.mode !== 'navigate') {
  // Not a page navigation, bail.
  return;
}
evt.respondWith(
    fetch(evt.request)
        .catch(() => {
          return caches.open(CACHE_NAME)
              .then((cache) => {
                return cache.match('offline.html');
              });
        })
);

کنترل کننده fetch فقط باید ناوبری صفحه را مدیریت کند، بنابراین سایر درخواست ها را می توان از کنترل کننده خارج کرد و مرورگر به طور معمول با آنها رسیدگی کرد. اما، اگر حالت . درخواست .mode است، از navigate fetch دریافت مورد از شبکه استفاده کنید. در صورت عدم موفقیت، کنترل کننده cache حافظه پنهان را با catch caches.open(CACHE_NAME) باز می کند و از cache.match('offline.html') برای دریافت صفحه آفلاین از پیش ذخیره شده استفاده می کند. سپس نتیجه با استفاده از evt.respondWith() به مرورگر ارسال می شود.

انحراف ابزار DevTools

بیایید بررسی کنیم تا مطمئن شویم همه چیز همانطور که انتظار داریم کار می کند. با باز شدن پنجره Service Workers ، صفحه را بازخوانی کنید. می‌بینید که سرویس‌کار جدید نصب شده و شماره وضعیت افزایش یافته است.

ما همچنین می توانیم بررسی کنیم تا ببینیم چه چیزی در حافظه پنهان شده است. در پنل Application DevTools به قسمت Cache Storage بروید. روی Cache Storage کلیک راست کنید، Refresh Caches را انتخاب کنید و بخش را گسترش دهید. شما باید نام کش استاتیک خود را در سمت چپ مشاهده کنید. روی نام کش کلیک کنید تا همه فایل هایی که کش هستند را ببینید.

حالا بیایید حالت آفلاین را آزمایش کنیم. به بخش Service Workers در پنل Application DevTools برگردید و کادر آفلاین را علامت بزنید. پس از بررسی آن، باید یک آیکون هشدار کوچک زرد رنگ در کنار تب پنل شبکه مشاهده کنید. این نماد نشان می دهد که شما آفلاین هستید.

صفحه خود را دوباره بارگذاری کنید و ... کار می کند! ما پاندا آفلاین خود را به جای داینو آفلاین کروم دریافت می کنیم!

نکاتی برای آزمایش کارگران خدمات

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

از DevTools استفاده کنید

در بخش Service Workers پنل Application ، چند چک باکس وجود دارد که زندگی شما را بسیار آسان تر می کند.

  • آفلاین - وقتی علامت زده می شود، یک تجربه آفلاین را شبیه سازی می کند و از رفتن هرگونه درخواست به شبکه جلوگیری می کند.
  • به‌روزرسانی هنگام بارگیری مجدد - وقتی علامت زده شد، آخرین سرویس‌کار را دریافت می‌کند، آن را نصب می‌کند و بلافاصله آن را فعال می‌کند.
  • Bypass برای شبکه - هنگامی که علامت زده می شود، درخواست ها از سرویس دهنده عبور می کنند و مستقیماً به شبکه ارسال می شوند.

تازه شروع کن

در برخی موارد، ممکن است متوجه شوید که داده‌های ذخیره‌شده را بارگیری می‌کنید یا موارد آن‌طور که انتظار دارید به‌روزرسانی نمی‌شوند. برای پاک کردن همه داده‌های ذخیره شده (localStorage، داده‌های indexedDB، فایل‌های کش) و حذف تمامی سرویس‌دهنده‌ها، از پنجره Clear Storage در پانل برنامه استفاده کنید. از طرف دیگر، می‌توانید در یک پنجره ناشناس نیز کار کنید.

نکات اضافی:

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

تغییرات را با Lighthouse تأیید کنید

بار دیگر Lighthouse را اجرا کنید و تغییرات خود را تأیید کنید. فراموش نکنید که قبل از تأیید تغییرات، علامت کادر آفلاین را بردارید!

ممیزی SEO

  • ✅ تصویب شده: سند دارای توضیحات متا است.

ممیزی برنامه وب پیشرو

  • ✅ PASSED: صفحه فعلی در حالت آفلاین با 200 پاسخ می دهد.
  • ✅ PASSED: start_url در حالت آفلاین با 200 پاسخ می دهد.
  • ✅ PASSED: یک سرویس کار را که صفحه و start_url.
  • ✅ تصویب شد: مانیفست برنامه وب الزامات قابلیت نصب را برآورده می کند.
  • ✅ PASSED: برای یک صفحه نمایش اسپلش سفارشی پیکربندی شده است.
  • ✅ PASSED: رنگ تم نوار آدرس را تنظیم می کند.

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

چرخه عمر کارگر خدماتی

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

رویداد install

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

معمولاً رویداد install برای ذخیره کردن همه چیزهایی که برای اجرای برنامه خود نیاز دارید استفاده می شود.

activate رویداد

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

fetch رویداد

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

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

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

انتخاب استراتژی ذخیره سازی مناسب

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

ذخیره منابع استاتیک

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

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

بیرون کشیدن از حافظه نهان محلی هر گونه تنوع شبکه را حذف می کند. مهم نیست کاربر در چه نوع شبکه ای است (WiFi، 5G، 3G یا حتی 2G)، منابع کلیدی که برای اجرا نیاز داریم تقریباً بلافاصله در دسترس هستند.

ذخیره داده های برنامه

استراتژی stale-while-veridate برای انواع خاصی از داده ها ایده آل است و برای برنامه ما به خوبی کار می کند. داده‌ها را در سریع‌ترین زمان ممکن روی صفحه دریافت می‌کند، سپس زمانی که شبکه آخرین داده‌ها را برگرداند، به‌روزرسانی می‌شود. Stale-while-Revalidate به این معنی است که ما باید دو درخواست ناهمزمان را آغاز کنیم، یکی به حافظه پنهان و دیگری به شبکه.

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

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

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

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

تابع getForecastFromCache() را به‌روزرسانی کنید تا بررسی کنید که آیا شیء caches در شیء window سراسری موجود است یا خیر، و اگر موجود است، داده‌ها را از کش درخواست کنید.

public/scripts/app.js

// CODELAB: Add code to get weather forecast from the caches object.
if (!('caches' in window)) {
  return null;
}
const url = `${window.location.origin}/forecast/${coords}`;
return caches.match(url)
    .then((response) => {
      if (response) {
        return response.json();
      }
      return null;
    })
    .catch((err) => {
      console.error('Error getting data from cache', err);
      return null;
    });

سپس، ما باید updateData() را تغییر دهیم تا دو تماس برقرار کند، یکی با getForecastFromNetwork() برای دریافت پیش‌بینی از شبکه و دیگری با getForecastFromCache() برای دریافت آخرین پیش‌بینی ذخیره‌شده:

public/scripts/app.js

// CODELAB: Add code to call getForecastFromCache.
getForecastFromCache(location.geo)
    .then((forecast) => {
      renderForecast(card, forecast);
    });

اپلیکیشن آب و هوای ما اکنون دو درخواست ناهمزمان برای داده، یکی از حافظه پنهان و دیگری از طریق fetch ، ارائه می‌کند. اگر داده‌ای در حافظه پنهان وجود داشته باشد، بسیار سریع (ده‌ها میلی‌ثانیه) برگردانده و رندر می‌شود. سپس، وقتی fetch پاسخ داد، کارت با جدیدترین داده‌ها مستقیماً از API آب‌وهوا به‌روزرسانی می‌شود.

توجه داشته باشید که چگونه درخواست کش و درخواست fetch هر دو با یک تماس برای به روز رسانی کارت پیش بینی پایان می یابند. چگونه برنامه متوجه می شود که آیا آخرین داده ها را نمایش می دهد یا خیر؟ این در کد زیر از renderForecast() مدیریت می شود:

public/scripts/app.js

// If the data on the element is newer, skip the update.
if (lastUpdated >= data.currently.time) {
  return;
}

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

منابع برنامه ما را از قبل ذخیره کنید

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

public/service-worker.js

// CODELAB: Update cache names any time any of the cached files change.
const CACHE_NAME = 'static-cache-v2';
const DATA_CACHE_NAME = 'data-cache-v1';

فراموش نکنید که CACHE_NAME را نیز به روز کنید. ما تمام منابع استاتیک خود را نیز تغییر خواهیم داد.

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

آرایه FILES_TO_CACHE را با لیست فایل ها به روز کنید:

public/service-worker.js

// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
  '/',
  '/index.html',
  '/scripts/app.js',
  '/scripts/install.js',
  '/scripts/luxon-1.11.4.js',
  '/styles/inline.css',
  '/images/add.svg',
  '/images/clear-day.svg',
  '/images/clear-night.svg',
  '/images/cloudy.svg',
  '/images/fog.svg',
  '/images/hail.svg',
  '/images/install.svg',
  '/images/partly-cloudy-day.svg',
  '/images/partly-cloudy-night.svg',
  '/images/rain.svg',
  '/images/refresh.svg',
  '/images/sleet.svg',
  '/images/snow.svg',
  '/images/thunderstorm.svg',
  '/images/tornado.svg',
  '/images/wind.svg',
];

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

کنترل کننده رویداد فعال را به روز کنید

برای اطمینان از اینکه رویداد activate به‌طور تصادفی داده‌های ما را حذف نمی‌کند، در رویداد activate service-worker.js ، if (key !== CACHE_NAME) { را با:

public/service-worker.js

if (key !== CACHE_NAME && key !== DATA_CACHE_NAME) {

کنترل کننده رویداد واکشی را به روز کنید

ما باید سرویس‌کار را تغییر دهیم تا درخواست‌های مربوط به API آب‌وهوا را رهگیری کند و پاسخ‌های آن‌ها را در حافظه پنهان ذخیره کند تا بتوانیم بعداً به راحتی به آنها دسترسی داشته باشیم. در استراتژی کهنه و اعتبار مجدد، ما انتظار داریم که پاسخ شبکه «منبع حقیقت» باشد و همیشه جدیدترین اطلاعات را در اختیار ما قرار دهد. If the network can't, it's OK to fail because we've already retrieved the latest cached data in our app.

Update the fetch event handler to handle requests to the data API separately from other requests.

public/service-worker.js

// CODELAB: Add fetch event handler here.
if (evt.request.url.includes('/forecast/')) {
  console.log('[Service Worker] Fetch (data)', evt.request.url);
  evt.respondWith(
      caches.open(DATA_CACHE_NAME).then((cache) => {
        return fetch(evt.request)
            .then((response) => {
              // If the response was good, clone it and store it in the cache.
              if (response.status === 200) {
                cache.put(evt.request.url, response.clone());
              }
              return response;
            }).catch((err) => {
              // Network request failed, try to get it from the cache.
              return cache.match(evt.request);
            });
      }));
  return;
}
evt.respondWith(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.match(evt.request)
          .then((response) => {
            return response || fetch(evt.request);
          });
    })
);

The code intercepts the request and checks if it is for a weather forecast. If it is, use fetch to make the request. Once the response is returned, open the cache, clone the response, store it in the cache, and return the response to the original requestor.

We need to remove the evt.request.mode !== 'navigate' check because we want our service worker to handle all requests (including images, scripts, CSS files, etc), not just navigations. If we left that check in, only the HTML would be served from the service worker cache. Everything else would be requested from the network.

Try it out

The app should be completely offline-functional now. Refresh the page to ensure that you've got the latest service worker installed. Then save a couple of cities and press the refresh button on the app to get fresh weather data.

Next, go to the Cache Storage pane on the Application panel of DevTools. Expand the section and you should see the name of your static cache and data cache listed on the left-hand side. Opening the data cache should show the data stored for each city.

Switch to the Service Workers pane, and check the Offline checkbox. Try reloading the page and then go offline and reload the page.

If you're on a fast network and want to see how weather forecast data is updated on a slow connection, set the FORECAST_DELAY property in server.js to 5000 . All requests to the forecast API will be delayed by 5000ms.

Verify changes with Lighthouse

It's also a good idea to run Lighthouse again.

SEO Audit

  • ✅ PASSED: Document has a meta description.

Progressive Web App Audit

  • ✅ PASSED: Current page responds with a 200 when offline.
  • ✅ PASSED: start_url responds with a 200 when offline.
  • ✅ PASSED: Registers a service worker that controls page and start_url.
  • ✅ PASSED: Web app manifest meets the installability requirements.
  • ✅ PASSED: Configured for a custom splash screen.
  • ✅ PASSED: Sets an address-bar theme color.

When a Progressive Web App is installed, it looks and behaves like all of the other installed apps. It launches from the same place that other apps launch. It runs in an app without an address bar or other browser UI. And like all other installed apps, it's a top level app in the task switcher.

In Chrome, a Progressive Web App can either be installed through the three-dot context menu, or you can provide a button or other UI component to the user that will prompt them to install your app.

Audit with Lighthouse

In order for a user to be able to install your Progressive Web App, the app needs to meet certain criteria . The easiest way to check is to use Lighthouse and make sure it meets the installable criteria.

If you've worked through this codelab, your PWA should already meet these criteria.

Add install.js to index.html

First, let's add the install.js to our index.html file.

public/index.html

<!-- CODELAB: Add the install script here -->
<script src="/scripts/install.js"></script>

Listen for beforeinstallprompt event

If the add to home screen criteria are met, Chrome will fire a beforeinstallprompt event that you can use to indicate your app can be 'installed' and then prompt the user to install it. Add the code below to listen for the beforeinstallprompt event:

public/scripts/install.js

// CODELAB: Add event listener for beforeinstallprompt event
window.addEventListener('beforeinstallprompt', saveBeforeInstallPromptEvent);

Save event and show install button

In our saveBeforeInstallPromptEvent function, we'll save a reference to the beforeinstallprompt event so that we can call prompt() on it later and update our UI to show the install button.

public/scripts/install.js

// CODELAB: Add code to save event & show the install button.
deferredInstallPrompt = evt;
installButton.removeAttribute('hidden');

Show the prompt and hide the button

When the user clicks the install button, we need to call .prompt() on the saved beforeinstallprompt event. We also need to hide the install button, because .prompt() can only be called once on each saved event.

public/scripts/install.js

// CODELAB: Add code show install prompt & hide the install button.
deferredInstallPrompt.prompt();
// Hide the install button, it can't be called twice.
evt.srcElement.setAttribute('hidden', true);

Calling .prompt() will show a modal dialog to the user, asking them to add your app to their home screen.

Log the results

You can check to see how the user responded to the install dialog by listening for the promise returned by the userChoice property of the saved beforeinstallprompt event. The promise returns an object with an outcome property after the prompt has shown and the user has responded to it.

public/scripts/install.js

// CODELAB: Log user response to prompt.
deferredInstallPrompt.userChoice
    .then((choice) => {
      if (choice.outcome === 'accepted') {
        console.log('User accepted the A2HS prompt', choice);
      } else {
        console.log('User dismissed the A2HS prompt', choice);
      }
      deferredInstallPrompt = null;
    });

One comment about userChoice : the spec defines it as a property , not a function as you might expect.

Log all install events

In addition to any UI that you add to install your app, users can also install your PWA through other methods, for example Chrome's three-dot menu. To track these events, listen for the appinstalled event.

public/scripts/install.js

// CODELAB: Add event listener for appinstalled event
window.addEventListener('appinstalled', logAppInstalled);

Then, we'll need to update the logAppInstalled function. For this codelab, we'll just use console.log , but in a production app you probably want to log this as an event with your analytics software.

public/scripts/install.js

// CODELAB: Add code to log the event
console.log('Weather App was installed.', evt);

Update the service worker

Don't forget to update the CACHE_NAME in your service-worker.js file since you've made changes to files that are already cached. Enabling the Bypass for network checkbox in the Service Workers pane of the Application panel in DevTools will work in development, but won't help in the real world.

Try it out

Let's see how our install step went. To be safe, use the Clear site data button in the Application panel of DevTools to clear everything away and make sure we're starting fresh. If you previously installed the app, be sure to uninstall it, otherwise the install icon won't show up again.

Verify the install button is visible

First, let's verify our install icon shows up properly. Be sure to try this on both desktop and mobile.

  1. Open the URL in a new Chrome tab.
  2. Open Chrome's three-dot menu (next to the address bar).
    ▢ Verify you see " Install Weather... " in the menu.
  3. Refresh the weather data using the refresh button in the upper right corner to ensure we meet the user engagement heuristics .
    ▢ Verify that the install icon is visible in the app header.

Verify the install button works

Next, let's make sure everything installs properly and our events are properly fired. You can do this either on desktop or mobile. If you want to test this on mobile, be sure you're using remote debugging so you can see what's logged to the console.

  1. Open Chrome, and in a new browser tab, navigate to your Weather PWA.
  2. Open DevTools and switch to the Console panel.
  3. Click the install button in the upper right corner.
    ▢ Verify the install button disappears
    ▢ Verify the install modal dialog is shown.
  4. Click Cancel.
    ▢ Verify " User dismissed the A2HS prompt " is shown in the console output.
    ▢ Verify the install button reappears.
  5. Click the install button again, then click the install button in the modal dialog.
    ▢ Verify " User accepted the A2HS prompt " is shown in the console output.
    ▢ Verify " Weather App was installed " is shown in the console output.
    ▢ Verify the Weather app is added to the place where you'd typically find apps.
  6. Launch the Weather PWA.
    ▢ Verify the app opens as a standalone app, either in an app window on desktop, or full screen on mobile.

.

Verify iOS installation works properly

Let's also check the behavior on iOS. If you have an iOS device, you can use that, or if you're on a Mac, try the iOS Simulator available with Xcode.

  1. Open Safari and in a new browser tab, navigate to your Weather PWA.
  2. Click the Share button.
  3. Scroll right and click on the Add to Home Screen button.
    ▢ Verify the title, URL, and icon are correct.
  4. Click Add.
    ▢ Verify the app icon is added to the home screen.
  5. Launch the Weather PWA from the home screen.
    ▢ Verify the app launches full screen.

Bonus: Detecting if your app is launched from the home screen

The display-mode media query makes it possible to apply styles depending on how the app was launched, or determine how it was launched with JavaScript.

@media all and (display-mode: standalone) {
  body {
    background-color: yellow;
  }
}

You can also check the display-mode media query in JavaScript to see if you're running in standalone .

Bonus: Uninstalling your PWA

Remember, the beforeinstallevent doesn't fire if the app is already installed, so during development you'll probably want to install and uninstall your app several times to make sure everything is working as expected.

Android

On Android, PWAs are uninstalled in the same way other installed apps are uninstalled.

  1. Open the app drawer.
  2. Scroll down to find the Weather icon.
  3. Drag the app icon to the top of the screen.
  4. Choose Uninstall.

ChromeOS

On ChromeOS, PWAs are easily uninstalled from the launcher search box.

  1. Open the launcher.
  2. Type " Weather " into the search box, your Weather PWA should appear in the results.
  3. Right click (alt-click) on the Weather PWA.
  4. Click Remove from Chrome...

macOS and Windows

On Mac and Windows, PWAs may be uninstalled through Chrome:

  1. In a new browser tab, open chrome://apps .
  2. Right click (alt-click) on the Weather PWA.
  3. Click Remove from Chrome...

You can also open the installed PWA, click the the three-dot context menu in the upper right corner, and choose " Uninstall Weather PWA... "

Congratulations, you've successfully built your first Progressive Web App!

You added a web app manifest to enable it to be installed and you added a service worker to ensure that your PWA is always fast and reliable. You learned how to use DevTools to audit an app and how it can help you improve your user experience.

You now know the key steps required to turn any web app into a Progressive Web App.

What's next?

Check out some of these codelabs...

Further reading

Reference docs