Remember before

Half a year ago, I was working on pWA at an internal front end seminar. The feature I was in charge of was implementing resource caching with service-Worker. So we have tried to introduce PWA in the local futuo information page before, and we are also preparing to introduce pWA in the official environment, but there is no good opportunity.

Last week, I received user feedback that the seed page could not be entered and was stuck all the time. I checked the nginx log on the server and found that the resource response was too slow and timed out (this site did not use CDN resources), js and other resources were not loaded. Again, the page is rendered front-end, so the user keeps the screen blank.

Fortunately, the technical construction of the group has been preparing to introduce the PWA, this time it is just right to experiment with the page whose influence is not too wide and updated too frequently, in case of problems, the influence is not too big.

So it started to dry up

First I asked my colleagues at the company and found that no one had introduced SW in a formal environment. Looks like I’m the first to eat crab, thrill.

I. Introduction to Service worker


The origin of service workers

A service worker is an advanced feature of a browser. It is essentially a Web worker, a script that runs independently of a web page. The Web Worker API was created to free up the main thread. Because JavaScript in browsers runs on a single thread, as Web services become more and more complex, time-consuming and resource-consuming operations in JS can cause various performance problems. However, since web workers are independent from the main thread, they can entrust some complex logic to the main thread, and then tell the main thread through postMessage after completion. Service worker is an upgraded version of Web worker. Compared with the latter, the former has the ability of persistent offline cache.

Characteristics of service workers

Sw has the following characteristics:

  • A script that runs in the background, independent of the main thread
  • It exists forever after being installed, unless manually uninstalled
  • Programmable interception of requests and returns, cache files. The FETCH API is used by sw to intercept and process network requests, and in conjunction with cacheStorage to manage caching of Web pages and communicate with front-end PostMessages.
  • You can’t manipulate the DOM directly: Because sw is a script that runs independently of the web page, you can’t access Windows or the DOM in its environment.
  • The protocol must be HTTPS. However, for local debugging, it also works at http://localhost and http://127.0.0.1.
  • Asynchronous implementation, sw makes heavy use of Promise.

The life cycle of the service worker

The service worker goes through the following stages from code writing to running in the browser: installing -> installed -> activating -> activated -> Redundant;

Installing: This happens after the service worker is registered and the installation begins. During this process, the Install event callback is triggered to specify some static resources for offline caching.

Installed: the sw is installed and waiting for the other Service worker to be shut down. (in install’s event callback, you can skipWaiting by calling the skipWaiting method)

Activating: a client in this state that is not controlled by another Service Worker, allowing the current Worker to complete the installation and clearing the old cache resources associated with the other workers and waiting for the new Service Worker thread to be activated.

Activated: This state handles the activate event callback and provides processing for functional events: FETCH, sync, and push. (In acitive’s event callback, call self.clients.claim())

Redundant: Redundant state. This state indicates that the mission cycle of a SW is over

Service worker code implementation

// Listen for the onload event in the page code and register a service worker using the sw configuration file
 if ('serviceWorker' in navigator) {
        window.addEventListener('load'.function () {
            navigator.serviceWorker.register('serviceWorker.js')
                .then(function (registration) {
                    // Registration succeeded
                    console.log('ServiceWorker registration successful with scope: ', registration.scope);
                })
                .catch(function (err) {
                    // Failed to register
                    console.log('ServiceWorker registration failed: ', err);
                });
        });
    }
Copy the code
//serviceWorker.js
var CACHE_NAME = 'my-first-sw';
var urlsToCache = [
    '/'.'/styles/main.css'.'/script/main.js'
];

self.addEventListener('install'.function(event) {
    // Some resources can be pre-cached during the install phase
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(function(cache) {
                console.log('Opened cache');
                returncache.addAll(urlsToCache); })); });// The fetch event can intercept the network request for some processing
self.addEventListener('fetch'.function (event) {
    event.respondWith(
        caches.match(event.request).then(function (response) {
            // If it matches a resource in the cache, it returns it directly
            if (response) {
                return response;
            }
          
            // The request continues if the match fails
            var request = event.request.clone(); // Copy the original request

            // By default, obtaining resources from third-party urls that do not support CORS will fail.
            // You can overcome this problem by adding the no-cors option to the request, although this may result in an "opaque" response, meaning you cannot tell whether the response was successful or not.
            if(request.mode ! = ='navigate' && request.url.indexOf(request.referrer) === - 1) 						{
                request = new Request(request, { mode: 'no-cors'})}return fetch(request).then(function (httpRes) {
								// Get the data returned from the HTTP request and do some operations
              
              	// If the request fails, it will be returned directly. For POST requests, it will be returned directly. Sw cannot cache POST requests
                if(! httpRes || ( httpRes.status ! = =200&& httpRes.status ! = =304&& httpRes.type ! = ='opaque') || request.method === 'POST') {
                    return httpRes;
                }

                // If the request is successful, the request is cached.
                var responseClone = httpRes.clone();
                caches.open('my-first-sw').then(function (cache) {
                    cache.put(event.request, responseClone);
                });

                returnhttpRes; }); })); });Copy the code

2. Introduction of service worker into seed

The code I wrote six months ago while researching pWA offline caching was shown above, but this time, when I really wanted to use it in a formal environment, I decided to use a webpack plugin: workbox-webpack-plugin. Workbox is Google’s official PWA framework. Workbox-webpack-plugin is one of the tools produced by It. It has two built-in plug-ins: GenerateSW and InjectManifest

  • GenerateSW: This plugin will generate a service worker configuration file for you, but this plugin is weak, mainly handling file caching and install, activate
  • InjectManifest: This plugin allows you to customize more configurations such as fecth, push, and sync events

Since this is for resource caching, only the GenerateSW part is used.

		// In the webpack configuration file
		var WorkboxPlugin = require('workbox-webpack-plugin');
		
		new WorkboxPlugin.GenerateSW({
            cacheId: 'seed-cache'.importWorkboxFrom: 'disabled'./ / can fill ` CDN `, ` local `, ` disabled `,
            importScripts: '/scripts-build/commseed/workboxswMain.js'.skipWaiting: true.// Skip waiting state
            clientsClaim: true.Notifications let the new SW immediately take control of the page
            cleanupOutdatedCaches: true.// Delete outdated and old versions of the cache
            
            // The final generated service worker address, which is related to the output address of webpack
            swDest: '.. /workboxServiceWorker.js'.include: [].// Cache rules, which can be used to match the request for caching
            // We can cache js, CSS, and image resources separately.
            // Since the seed farm site is not updated for a long time, the cache time can be slightly longer
            runtimeCaching: [
                {
                    urlPattern: /.*\.js.*/i.handler: 'CacheFirst'.options: {
                        cacheName: 'seed-js'.expiration: {
                            maxEntries: 20.If the number exceeds 20, delete it according to LRU principle
                            maxAgeSeconds: 30 * 24 * 60 * 60.// 30 days}},}, {urlPattern: /.*css.*/.handler: 'CacheFirst'.options: {
                        cacheName: 'seed-css'.expiration: {
                            maxEntries: 30.If the number exceeds 30, delete it according to LRU principle
                            maxAgeSeconds: 30 * 24 * 60 * 60.// 30 days}},}, {urlPattern: /.*(png|svga).*/.handler: 'CacheFirst'.options: {
                        cacheName: 'seed-image'.expiration: {
                            maxEntries: 30.If the number exceeds 30, delete it according to LRU principle
                            maxAgeSeconds: 30 * 24 * 60 * 60.// 30 days},},}]})Copy the code
  1. ImportWorkboxForm and importScripts:

ImportWorkboxFrom: specifies the address of the workbox frame file. The value can be CDN, local, or disabled

  • CDN: the introduction of Google’s official CDN, of course, will be strong in China. pass
  • Local: workboxPlugin generates the workbox code locally. You can upload and deploy these configuration files together, so that the generated code is deployed each time.
  • Disabled: The generated workbox code is imported using importScript to specify the JS file.

I chose the third option because it allows me to specify where to import workbox.js from, for example, if the site has a CDN in the future, I can put the workbox.js on the CDN. For now, the generated files are placed in script folder.

  1. The strategy of workbox
    • Stale-while-revalidate: Returns a response using the cache as quickly as possible, or a network request if the cache is invalid
    • Cache-first: indicates the Cache priority
    • Network-first: indicates the Network priority
    • Network-only: uses Only the resources requested by the Network
    • Cache-only: Only Cache is used

The CSS and JS of the general site are all in the CDN, and the SW has no way to judge whether the resources requested from the CDN are correct (HTTP 200). If the cache fails, it will be bad. In this case, the stale-while-revalidate strategy is used to ensure the page speed. Even if the page fails, the user will update it after refreshing.

Since the js and CSS resources of the seed project are under the site, the cache-first policy is used directly.

Once configured in webpack, perform webpack packaging to see the service worker configuration file generated by workbox-webpack-plugin in the specified directory.

Once connected, open the site and you can see the cached resources in the Chrome debugger on your PC

Access process considerations

  1. As mentioned above, service workers exist forever once installed; If one day you want to remove the service worker thread running behind the browser, manually uninstall it. Therefore, I need to know how to uninstall the service worker before accessing it.

    if ('serviceWorker' in navigator) {
           navigator.serviceWorker.getRegistrations()
               .then(function(registrations) {
    				for(let registration of registrations) {
                         // There is more than one service worker installed on the page, find our one and delete it
                        if(registration && registration.scope === 'https://seed.futunn.com/'){ registration.unregister(); }}}); }Copy the code
  2. If resources are cached using the service worker, will new resources be pulled next time they are republished? This is also possible, as long as the resource address is different and the hash value is changed, then the resource will be pulled again and cached. As shown in the following figure, different versions of the same JS are cached.

  1. There is also the issue of thinking about the development process. If it goes live later, the SW thing is installed, and every time it opens, it directly reads the cached resourcesLocal debuggingWhat should I do? Chrome’s “disabled cache” doesn’t work either, so you can’t hash resources locally (currently the project only hashes them when it’s released). After thinking about this for a long time, I finally found that Chrome already has this setting. In devtool, you can set the service worker to skip.bypass for network

  1. The caching capabilities of Service Woker give us much more control over caching than the default caching capabilities of browsers.

  2. One of the disadvantages of this product is that not many browsers support service worker. Apple system only supports service worker from 11.3, so up to now, the WebView of Ios version of Futu Niuniu and wechat ios version do not support service worker feature. Android support is a little more extensive, so this time around, android customers can feel the benefits of seed optimization better.

Afterword.

It has been almost two weeks since seed Farm joined the service worker, and there is no problem so far. Everything seems to be going well.

Since the initial contact at the seminar, I have been thinking about preparing it for use, but I have always been afraid of its uncertainty. With more and more familiar with it, this time finally put it up, miss things for a long time can be a account.