preface

For PWA, after being interrogated by the interviewer for many times 😭, I have a strong interest in him. I was too busy with the interview some time ago, so I was delayed to 😂. However, when it comes to learning new technologies, we always need to study them with a heart of awe. The rise of a technology always has its meaning and inevitably represents a trend. Having said all that, what exactly is a PWA?

What is the PWA

PWA (Progressive Web Apps) uses modern Web apis as well as traditional Progressive enhancement strategies to create cross-platform Web applications. These apps are ubiquitous and feature rich, giving them the same user experience benefits as native apps

The explanation on MDN is always very official, literally speaking, we can know that it is a gradual Web application, so what is gradual? In fact, it means that if the browser does not support this technology, then the original application will not have an impact, for the browser that supports this technology, it will add its features on the basis of the original, so that users get a better experience. The technology is currently integrated in Vue, React scaffolding, and once you have a Web app project, your PWA journey is already underway.

Why is it so popular? Which brings us to its three main features:

  • Can be added to the desktop
  • Offline access
  • The background to inform

For a Web site, how to retain users has to be considered, and for Web applications, a crude way to be remembered by users is to bookmark, but in terms of user experience level, this is not comparable to native applications. For a large project, the cost of developing a native app is enormous.

So how can we make a Web application as desktop as a native App adding directly accessible and having the transition effect of opening a website became an urgent development need, and PWA was born.

Detailed implementation of the three features

One important point to note in THE PWA is that it only supports HTTPS and localhost, which means your application must have this condition in order to be accessed.

Desktop add

The core of this feature is a file called manifest.json, which can be installed to the desktop once our application introduces this configuration.

The manifest configuration

 {
    "name": "HackerWeb".// Application name
    "short_name": "HackerWeb".// Short name for desktop display
    "start_url": ".".Url / / entry
    "display": "standalone".// The presentation mode of the application. This mode is generally the best for the experience
    "background_color": "#fff".// The theme color of the application will generally change the background color of your upper menu bar
    "description": "A simply readable Hacker News app.".// Application description
    "icons": [{// Display the app icon in different environments
    "src": "images/touch/homescreen48.png"."sizes": "144x144"."type": "image/png"}}]Copy the code

For details about the configuration, see Web App Manifest

Once configured, we just need to use the link tag to introduce

<link rel="manifest" href="manifest.json">
Copy the code

So your application already has the ability to be installed to the desktop, is not very simple 😏.

Offline access

The realization of this description function, the author began to prepare to enlarge the trick 🐤, one of its core concepts can be described in a picture:

In fact, the implementation of this technology needs to use our ServiceWorker and this Cache Storage to cooperate with the implementation.

The implementation idea is that ServiceWorker can intercept all requests, and can operate the Cache Storage for access operations, if the user is disconnected, we can choose to read the required data from the Cache, so that we can achieve offline caching 🤒.

ServiceWorker,

  • Service workers allow Web applications to be used in poorly networked or offline environments
  • Service workers can greatly improve the user experience of web apps
  • Service worker is an independent worker thread, independent of the current page process, is a special Web worker
  • Web workers are temporary, and the results of each action cannot be persisted. If the same complex operation is performed next time, it will take time to do it again
  • Once installed, it exists forever unless it is manually unregistered
  • It can wake up when it’s in use and automatically sleep when it’s not in use
  • Programmable interception of proxy requests and returns, cache files, cached files can be accessed by web processes (including network offline state)
  • Offline content developers can control
  • Must work in HTTPS environment
  • Asynchronous implementation, mostly implemented internally through promises

What is webWoker, this article will not repeat, detailed concept can refer to ruan Yifeng teacher’s blog, Web Worker use tutorial

Registered ServiceWorker

To use it, we usually sign up the first time a user visits the site. In order not to affect the normal page parsing and page resource download, we will choose to register ServiceWorker when the onLoad event is triggered. The registration of ServiceWorker is very simple, just need to call an Api:

window.onload = function() {
  if (navigator.serviceWorker) {
    navigator.serviceWorker
      .register("./sw.js")
      .then(registration= > {
        console.log(registration);
      })
      .catch(err= > {
        console.log(err); }); }};Copy the code

First, we will determine whether the browser supports ServiceWorker. If it supports ServiceWorker, we will register it. If not, we will skip it without affecting the page. The registration method returns a Promise object, which we can obtain in the THEN method. This object contains information about the successful registration. If the registration fails, we can catch it in the catch method.

Life cycle of serviceWorker

After registering our sw.js(filename customization) file, we can explore its three core lifecycle functions in the sw.js file.

  • Install – will be inservice workerTriggered when the registration is successful. It is used to cache resources
  • Activate – will be inservice workerWhen activated, it is used to delete old resources
  • Fetch – intercepts all requests for a page. When a request is intercepted, it is triggered (core). It is used to manipulate cache or read network resources

Install phase

At this stage, we mainly store some pages, resources, etc., which need to be cached offline, so that we can continue to access the website without the network.

self.addEventListener("install".async e => {
  cacheData(); // Call the cache method
  await self.skipWaiting(); // Skip the wait
  // e.waitUtil(self.skipWaiting()); // Another way to skip waiting
});
Copy the code

First I’ll call the appropriate cache resource method, and then self.skipWating is used to reactivate the install lifecycle function if your sw.js file is changed, but it doesn’t immediately trigger the Activite cycle. It will wait for the last sw.js to be destroyed before activating the next one. At this time, our newly registered sw.js will not be activated, so in order to make the newly registered sw.js take effect immediately, we can add this sentence to skip the wait.

Why are there two ways to write this place? SkipWating returns a Promise, which is asynchronous. We need to wait for it to complete before we move on to the next one. We can use async await to do this. You can also use a built-in utility method, waitUtil, to do this.

Let’s parse the cacheData method in our code:

// Cache method
const CHACH_NAME = "cache_v2";
async function cacheData() {
  const cache = await caches.open(CHACH_NAME); // Open a database
  const cacheList = [
    "/"."/index.html"."/images/logo.png"."/manifest.json"."/index.css"."/setting.js"
  ]; // The list that needs to be cached
  await cache.addAll(cacheList); // Cache it
}
Copy the code

In fact, we need to use the cache storage. In fact, it is a bit similar to a database, generally want to use a database, we need to open a database, each database has a name, meet these conditions, we can store data to the cache storage.

cache storage

  • The Caches API operates like a database:
    • caches.open(cacheName).then(function(cache) {}): turns on the cache and returns a promise matching the Cache object cacheName, similar to connecting to a database
    • caches.keys()Return a promise object containing all the cached keys (database name)
    • caches.delete(key)Delete the corresponding cache (database) based on key
  • Common cache object methods (single data operation)
    • cache.put(req, res)Use the request as a key and store the corresponding response
    • cache.add(url)Make a request based on the URL and store the response
    • cache.addAll(urls)Grab an array of urls and store the results
    • cache.match(req): Obtain the response corresponding to req

We need to make a list of the resources we need to cache, that is, the code in the cache ist, call the cache storage addAll method can be cached in the cache storage 😀.

Activate stage

In this phase, we will generally do nothing more than to delete the old resources or cache storage.

Since serviceWoker does not take effect immediately after it is installed and activated in the user’s browser, we will call an API during the activate phase to enable it to take effect on the first visit. The code is as follows:

const CHACH_NAME = "cache_v2";// The current database name is defined globally
self.addEventListener("activate".async e => {
  /** find all database names, clean up the old version of the database */
  const keys = await caches.keys();
  keys.forEach(key= > {
    // Delete the database name if it is not the name currently defined
    if (key !== CHACH_NAME) {
      caches.delete(key);
    }
  });
  
  /** is used to gain control of the page immediately, ensuring that the first time the user opens the browser it takes effect immediately */
  await self.clients.claim();
});
Copy the code

Since self.clients.claim() also returns a Promise object, we also need to wait for its execution to complete.

The fetch phase

Can say at this stage is to compare core lifecycle function, because the front two are mainly used for some initialization operation, and fetch it truly offline caching the hub, it intercepts all page request, because of this feature, we can in the case of no Internet users need to request the resource from the cache read out returned to the user.

In general, we will have a variety of strategies for processing user requests, the author will talk about two commonly used:

The network is preferred

As the name implies, it is to first go to the network request, if the request can not be obtained, then go to the cache to read, the specific code is as follows:

self.addEventListener("fetch".async e => {
  const req = e.request;// Get the request header
  await e.respondWith(networkFirst(req));// Return the requested resource to the browser
});

// Network priority
async function networkFirst(req) {
  /** Use try. Catch to catch an exception */
  try {
    const res = await fetch(req);
    return res;
  } catch (error) {
    const cache = await caches.open(CHACH_NAME); // Open a database
    return await cache.match(req);// Read the cache}}Copy the code

The Fetch is used to make a request to the corresponding network address. If the resource is not requested, an exception will be thrown, which can be caught by the try catch, and then cached in the catch.

The cache is preferred

Read cache data first, if no network request.

// Cache is preferred
async function cachekFirst(req) {
  const cache = await caches.open(CHACH_NAME); // Open a database
  let res = await cache.match(req);// Read the cache
  if (res) {
    return res;
  } else {
    res = await fetch(req);
    returnres; }}Copy the code

The specific code meaning will not be repeated, can see this step should be no problem for you 😜.

Once we have the resource, we just need to call the e.espondwith method to render the return value back to the browser. At this point, we have taken a big step. The last step is how to notify the system.

Notification

This processing part cannot be placed in the sw.js file because we need to use the Notification function in window.

// Obtain notification permission first
if (Notification.permission == "default") {
  Notification.requestPermission();
}
if(! navigator.onLine) {new Notification("Tip", { body: "You're disconnected. You're accessing cached content." });
}
Copy the code

With such system-level apis, the natural first step is to obtain user permissions before proceeding to the next step. I’ve only written a feature here that lets users know they’re offline.

The last

The author’s list of files:

  • images
    • logo.png
  • index.css
  • index.html
  • manifest.json
  • sw.js
  • setting.js
  • server.js

In the index.html file, you just need to introduce manifest.json and setting.js with the link tag. The contents of setting.js are as follows:

window.onload = function() {
  if (this.navigator.serviceWorker) {
    this.navigator.serviceWorker
      .register("./sw.js")
      .then(registration= > {
        console.log(registration);
      })
      .catch(err= > {
        console.log(err); }); }};/** * Determine whether the user is connected to the Internet, and give notification prompt */
// Obtain notification permission first
if (Notification.permission == "default") {
  Notification.requestPermission();
}
if(! navigator.onLine) {new Notification("Tip", { body: "You're disconnected. You're accessing cached content." });
}

Copy the code

In great detail also wrote more than 3k words, hope to be able to help you, but also welcome you to express the incorrect place to correct 🧐.