PWA: Offline site access

What is offline access

Accessing a website requires a network, and when it is disconnected, it jumps to the small dinosaur page in Google Browser. (Small dinosaurs are surprising when clicked with the mouse)

Service Worker is a feature of PWA that allows access to our website when it is disconnected.

Offline Access Principle

Normally, when you visit a Web site, the browser sends a request to the server, which returns the resource after processing.

After adding Service Worker to the site.

The first time you visit a website, Service Worker intercepts http requests; First go to the Cache to find out if there is this resource, and then send a request to the server. When the server returns the resource, it adds a copy to the cache and returns it to the browser.

The second time you visit the website, Service Worker intercepts http requests; Go to the Cache first to find out if there is this resource, find the previously added resource, and return directly to the browser; One http request was omitted and, to some extent, the site was accessed more quickly.

From the above description, you know that offline access still requires that you have visited the site first, and Service Worker successfully cached the resources on the browser side so that you can visit the site offline.

Modify the site to support offline access

Dead work

PWA requires one: the web address must be https protocol, or the domain name must be localhost.

Download a nginx locally, deploy the Web page on the Web server, and access it through localhost. Understanding Web server references: Graphical Web Server

Write a web page index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Service Worker Offline Cache</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>Service Worker Offline Cache Demo</h1>
</body>
</html>

Write style file style.css, set h1 font to red

h1 {
    color: red;
}

Start the nginx server and visit the website through localhost, you can see the following image. (The port I use here is 3400, your port number may not be the same as mine)

Register Service Worker

Modify index.html to register Service Worker

// index.html
<body>
    <h1>Service Worker Offline Cache Demo</h1>
    <script>
        // Determine if the browser supports Service Worker
        if ('serviceWorker' in navigator) {
            window.addEventListener('load', function () {
                // Register Service Worker
                navigator.serviceWorker.register('/sw.js')
                    .then(function (registration) {
                        console.log('sw.js login was successful', registration.scope);
                    })
                    .catch(function (err) {
                        console.log('sw.js login has failed ', err);
                    });
            });
        }
    </script>
</body>
  • 'serviceWorker'in navigator first determines if the browser supports Service Worker;
  • navigator.serviceWorker.register('/sw.js') This line of code is to register the Service Worker. The parameter sw.js is a JS file with arbitrary names. The specific logic of the service worker is written here, for example, to intercept http requests and operate on caches.

Write Cache Logic

Create sw.js file.

  • Listen for fetch events and intercept requests;
  • The cache is processed, requests are returned directly from the cache, requests the server to make a backup copy of the responding resources in the cache if not in the cache.
// Once Service Worker is registered successfully, every request accessed by the browser is blocked by the fetch event
// Service Worker registration failed, request will not enter fetch event
self.addEventListener('fetch', function (event) {
    
    // Print Request Resource URL
    console.log('url is', event.request.url)

    event.respondWith(
        // caches is a global variable, it's a cached object
        // This step is to determine if the resource is in the cache
        caches.match(event.request).then(function (cacheRes) {
            // cacheRes is not empty, there is this resource in the cache, return directly to the browser
            // Omit an http request
            if (cacheRes) {
                return cacheRes;
            }

            // cacheRes is empty, indicating that the request is not in the cache
            // Copy the original request
            var request = event.request.clone(); 

            // fetch is the browser's own request library to send requests to the server
            return fetch(request).then(function (httpRes) {
                // The request failed, just return the failed result.
                if (!httpRes || httpRes.status !== 200) {
                    return httpRes;
                }

                // Add the response to the cache if the request succeeds
                // Next time the same resource is requested, the resource is taken directly from the cache and no further requests are made
                var responseClone = httpRes.clone();
                caches.open('offline-cache-v1').then(function (cache) {
                    cache.put(event.request, responseClone);
                });

                return httpRes;
            });
        })
    );
})

test

If you visit the web page again, you can still see the previous page. It looks like nothing has changed ~~

How to simulate offline state?

Normally, access the index.html web page, which contains the external style style.css inside. The color of the page font is controlled by the external style. Delete the style.css file. The font of the page should be changed to black before it is accessed again.

Refresh the page and you can see that the font of the page is still red, indicating that the Service Worker cache was successful.Instead of going to the Web server to request style.css at all, the browser gets style.css directly from the browser-side cache (Cache).

Cache is not a mystery. We can see and feel it.

DevTool > Application > Cache, you can see that there is indeed a cached sytle.css file, right-click in the red letter 3, delete the sytle.css file, and refresh the page to see that the page font has turned black.

If you're looking good enough, you've already seen an option for Service Workers above.

After registering once, Service Worker will always be saved on the browser side, even if you close the web page, it will always exist.

  • The right_arrow Unregister button cancels the registered Service Worker, but it will not take effect immediately. When you close the current Tab page, you will find it canceled the next time you visit it.
  • Status #4283 denotes the version number and running status of the Service Worker.

Think about it: What if the style.css style on the server side is modified now, and the browser side won't go to the server to request the latest version of the style.css file at all, because the style.css resource already exists in the Service Worker's cache?

Update Service Worker

Once and for all

In actual development, you typically only put files that don't change much into Service Worker, such as jquery.min.css and vue.js, while developer-maintained utils.js and style.css are not included in Service Worker.

This part of the work can be done in sw.js by judging the whitelist, and the pseudocode is as follows:

// sw.js

// Whitelist
const whiteUrls = [
    'jquery.js',
    'vue.js',
]

self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request).then(function (cacheRes) {
            // .......

            return fetch(request).then(function (httpRes) {
                if (!httpRes || httpRes.status !== 200) {
                    return httpRes;
                }

                // Place whitelist in cache, other normal requests
                if (whiteUrls.findIndex(event.request.url) !== -1) {
                    var responseClone = httpRes.clone();
                    caches.open('offline-cache-v1').then(function (cache) {
                        cache.put(event.request, responseClone);
                    });
                }
                
                return httpRes;
            });
        })
    );
})

The advantage is that you don't have to do anything else when utils.js and style.css you maintain changes;

The disadvantage is that the class libraries juqery.min.js and vue.js cannot be updated. For personal projects, the general class libraries used will not be updated for 800 years. This is a simple way to do this. For company projects, it is still necessary to refresh the cache every time a deployment comes online.The guest officer then looked down_

Change Cache Version Number

Whenever sw.js changes, even if there is only one punctuation modification, the browser side can recognize that Service Worker has an update.

After a developer modifies utils.js or style.css, he or she will manually modify the version number, the version variable value in the sample code, until the next deployment comes online.

const version = 'offline-cache-v2'

self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request).then(function (cacheRes) {
            // .......

            return fetch(request).then(function (httpRes) {
                if (!httpRes || httpRes.status !== 200) {
                    return httpRes;
                }

                // Cached version number
                var responseClone = httpRes.clone();
                caches.open(version).then(function (cache) {
                    cache.put(event.request, responseClone);
                });
                
                return httpRes;
            });
        })
    );
})

The goal is to tell the browser Service Worker that there are updates.The image below shows that the version number of Service Worker did generate 4294, but it will not take effect immediately. The current page needs to be closed before the next login takes effect.And the cache is v1, not v2.

  • Resolve the above-mentioned cache unchanged issue: add activate listener function, enter the event whenever sw.js changes, delete old cache objects internally, create new cache objects;
  • Fix the above issues that require closing the page to reopen: add install listener function, add code to skipWaiting status and take effect immediately.

The final code for sw.js is as follows.

const version = 'offline-cache-v2'

// This event is triggered after the Serverice Worker installation succeeds
self.addEventListener('install', function (event) {
    // sw.js has been updated to take effect immediately
    event.waitUntil(self.skipWaiting());
});

// Trigger this event when sw.js has an update
self.addEventListener('activate', function (event) {
    event.waitUntil(
        Promise.all([
            // Update Client
            self.clients.claim(),

            // Delete older versions of cached objects
            caches.keys().then(function (cacheList) {
                return Promise.all(
                    cacheList.map(function (cacheName) {
                        if (cacheName !== version) {
                            return caches.delete(cacheName);
                        }
                    })
                );
            })
        ])
    );
});

// Page Send Request Triggers This Event
self.addEventListener('fetch', function (event) {
    
    console.log('url is', event.request.url)

    event.respondWith(
        caches.match(event.request).then(function (response) {
            // If Service Worker has its own return, it returns directly, reducing one http request
            if (response) {
                return response;
            }

            // If the service worker does not return, you must request the real remote service directly
            var request = event.request.clone(); // Copy the original request
            return fetch(request).then(function (httpRes) {
                // The request failed, just return the failed result.
                if (!httpRes || httpRes.status !== 200) {
                    return httpRes;
                }

                // If the request succeeds, cache the request.
                var responseClone = httpRes.clone();
                caches.open(version).then(function (cache) {
                    cache.put(event.request, responseClone);
                });

                return httpRes;
            });
        })
    );
})

Keywords: Front-end Web Server Vue Nginx JQuery

Added by drisate on Thu, 29 Aug 2019 05:59:48 +0300