PWA

In the past few days, I have come into contact with the new concept of PWA when learning webpack, so here is a brief summary of the basic concepts, uses and how to use PWA in combination with webpack and vue.

What is PWA?

Progressive Web Apps, or PWAs for short, are a new way to enhance the experience of web apps, giving users the experience of native apps.

PWA can achieve the experience of native applications is not based on a specific technology, but through the application of some new technologies to improve, in the security, performance and experience of three aspects have been greatly improved, PWA is essentially Web App, with some The new technology also has some of the characteristics of Native App, both Web App and Native App advantages.

The main features of PWA include the following three points:

  • Reliable - Even in unstable network environments, it can load and display instantly
  • Experience - Responds quickly and has smooth animations that respond to user actions
    Sticky Like native apps on the device, with an immersive User Experience that users can add to the desktop

PWA itself emphasizes progressive and does not require all requirements for security, performance, and experience to be met at once. Developers can PWA Checklist Look at the existing features.

Specific details can be referred to.PWA文档

Service

What is Service

The W3C organization proposed an HTML5 API such as Service Worker as early as May 2014, mainly for persistent offline caching.

Of course, this API is not out of thin air, as for the origin of which we can simply sort out:

JavaScript in browsers runs on a single main thread and can only do one thing at a time. With the continuous complexity of Web business, we gradually add a lot of resource-consuming and time-consuming complex computing processes in js, and the performance problems caused by these processes are more prominent in the complexity of WebApp.

W3C organization early insight into the impact of these problems may be caused, this time there is a called Web Worker API was created, the only purpose of this API is to liberate the main thread, Web Worker is separated from the main thread, some complex time-consuming work to it, after completion through the postMessage method to tell the main thread, and the main thread through the onMessage method to get Web Worker feedback results.

All problems seem to be solved, but the Web Worker is temporary, and the results of each task cannot be persisted. If there is the same complex operation next time, it will take time to do it all over again. Can we have a Worker that persists all the time and is ready to accept commands from the main thread? Based on this requirement, the initial version of Service Worker was launched. Service Worker added persistent offline caching capabilities to the Web Worker. Of course, before the Service Worker, there was also an API for offline caching on HTML5 called AppCache, but there are many AppCache 不能忍受的缺点

The W3C decided that AppCache should remain in the HTML 5.0 Recommendation and be removed in subsequent versions of HTML.

Service Workers have the following functions and features:

  • An independent worker thread, independent of the current webpage process, with its own independent worker context.
    Once installed, it exists forever unless manually unregistered
  • It can wake up directly when used, and automatically sleep when not in use
  • Programmable intercept proxy requests and returns, cache files, cached files can be retrieved by web page processes (including network offline status)
  • Controllable for offline content developers
  • Ability to push messages to Clients
  • DOM cannot be manipulated directly
  • Must work in HTTPS environment
  • Asynchronous implementation, internally mostly implemented through Promises

So we basically know the great mission of the Service Worker, which is to make the cache elegant and extreme, to make the shortcomings of the Web App more weak compared to the Native App, and to provide developers with unlimited reverie on performance and experience.

How to Use Services

Prerequisite

Service Workers have certain prerequisites when using them for security and implementation principles.

  • Due to the HTTPS environment required by the Service Worker, we can usually resort to github page Learning debugging. Of course, the general browser allows debugging Service Worker when the host is localhost or 127.0.0.1 is also ok.
  • Service Worker caching mechanism is dependent Cache API Achieved
  • Depends HTML5 fetch API
  • Depends Promise Achieve

Process

Register

To install the Service Worker, we need to start the installation by registering the Service Worker in the js main thread (js in the regular page), this process will notify the browser where the javaScript files of our Service Worker thread are located.

Let’s first feel a piece of code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
123456789101112131415if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js', {scope: '/'})
.then(function (registration) {

//Registration successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(function (err) {

//Registration failed: (
console.log('ServiceWorker registration failed: ', err);
});
});
}
  • This code is first to determine the availability of the Service Worker API. If it is supported, we will continue to talk about implementation, otherwise we will not talk about it.

  • If supported, register a Service Worker at/sw.js when the page is onloaded.

  • Each time the page loads successfully, the’register () 'method will be called, and the browser will determine whether the Service Worker thread has been registered and act accordingly.

  • The scope parameter of the register method is optional and specifies the subdirectory of the content you want the service worker to control. The service worker file in this demo is located in the root domain, which means the scope of the service worker will be the entire source.

    Regarding the scope parameter of the’register ‘method, it needs to be explained: The Service Worker thread will receive a fetch event for all items on the scope specified domain directory. If our Service Worker’s javaScript file is in’/a/b/sw.js’, without passing the scope value, the scope value is’/a/b '.

    The meaning of the value of scope is that if the value of scope is’/a/b ‘, then the Service Worker thread can only capture (’/a/b/page1 ‘,’/a/b/page2 ',…) page fetch events. Through the meaning of scope, we can also see that the Service Worker does not serve a single page, so global variables need to be used with caution in the js logic of the Service Worker.

  • 'Then () ’ function chain calls our promise, and when the promise resolves, the code inside will be executed.

  • At the end we chain a’catch () 'function, which will only be executed when the promise is rejected.

After the code execution is completed, we have registered a service worker, which works in the worker context, so there is no access to the DOM. Run the service worker code outside the normal page to control their loading.

Check if the registration is successful

If you are confused, did my Service Worker register successfully? What does it look like to register successfully?

You can open our good partner chrome browser on your PC and enter’chrome://inspect/#service-workers’

chorme inspect-Service Worker

We can also view service worker details through’chrome://serviceworker-internals’. This is still useful if you just want to understand the lifecycle of service workers, but it is likely to be completely replaced by’chrome://inspect/#service-workers’ in the future.

Of course, it can also be used for testing隐身窗口You can close the Service Worker thread and reopen it, as the previous Service Worker thread will not affect the new window. Any registrations and caches created from the incognito window will be cleared after the window is closed.

Reasons for registration failure

Why does it cause Service Worker registration to fail? The reasons are basically the following situations:

Not an HTTPS environment, not localhost or 127.0.0.1.

  • The address of the Service Worker file is not written correctly and needs to be relative to the origin.
  • Service Worker files with a different origin than your App are not allowed.

Installation

After your Service Worker registration is successful, our browser already has a worker context that belongs to your own web App. At this time, the browser will non-stop trying to install and activate the pages on your site. It, and the cache of static resources can be done here.

The install event will be bound to the Service Worker file. After the Service Worker is installed successfully, the install event will be triggered.

The install event is typically used to populate your browser’s offline cache capabilities. To achieve this, we use Service Worker’s new iconic storage cache API - a global object on Service Worker that allows us to store resources sent by network responses and generate keys based on their requests. This API works very similar to standard browser caching, but only for your site’s domain. It persists until you tell it it is no longer stored and you have full control.

** The usage of localStorage is very similar to the usage of Service Worker cache, but since localStorage is synchronous usage, it is not allowed to be used in Service Workers. **** IndexedDB can also be used for data storage within Service Workers. **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1234567891011121314151617//listen to the install event of the service worker
this.addEventListener('install', function (event) {
//If the service worker has been installed successfully, the event.waitUntil callback function will be called
event.waitUntil(
//After the installation is successful, operate the CacheStorage cache. Before using it, you need to open the corresponding cache space through caches.open ().
caches.open('my-test-cache-v1').then(function (cache) {
//Add the precache cache via the addAll method of the cache cache object
return cache.addAll([
'/',
'/index.html',
'/main.css',
'/main.js',
'/image.jpg'
]);
})
);
});
  • Here, we add an install event listener, followed by an’ExtendableEvent.waitUntil () 'method on the event - this will ensure that the Service Worker does not install before the code in waitUntil () has finished executing.
  • In waitUntil (), we used the caches.open () method to create a new cache called v1, which will be the first version of our site resource cache. It returns a promise to create a cache. When it is resolved, we will then call a method’addAll () 'on the created cache instance (Cache API). The parameter of this method is a set of URLs relative to origin. These URLs are the list of resources you want to cache.
  • If the promise is rejected, the installation will fail and the worker will not do anything. This is also possible because you can fix your code and try again the next time the registration occurs.
  • When the installation completes successfully, the Service Worker will be activated. The first time your Service Worker is registered/activated, it will be no different. But when the Service Worker is updated, it will be different.

Custom request response

At this point, in fact, now that you can cache your site resources, you need to tell the service worker to do something with these cached contents. With the fetch event, this is very easy to do.

Every time any resource controlled by the Service Worker is requested, the fetch event will be triggered. These resources include the html doc within the specified scope, and any other resources referenced within these html docs (such as’index.html 'initiates a cross-domain request to embed an image, which will also pass through the Service Worker), and the image of the Service Worker proxy server begins to slowly emerge, and the hook of the proxy server can use the two powerful tools of scope and fetch events. Site request management Well organized.

You can add a fetch event listener to the service worker, then call the respondWith () method on the event to hijack our HTTP responses, and then you can update them with your own magic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1234567891011121314151617181920212223242526272829303132this.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request).then(function (response) {
//Come on, the proxy can handle some proxy things

//If the service worker has its own return, return directly and reduce the http request once
if (response) {
return response;
}

If the service worker does not return, it must directly request the real remote service
Var request = event.request.clone (); // copy the original request
return fetch(request).then(function (httpRes) {

The return of the http request has been caught and can be disposed of.

//The request failed, just return the failed result directly...
if (!httpRes || httpRes.status ! 200) {
return httpRes;
}

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

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

We can perform static resource caching when’install ‘, or proxy page requests through’fetch’ event handling callbacks to achieve dynamic resource caching.

The two ways can be compared:

  • The advantage of on install is that it can be offline on the second visit, but the disadvantage is that the URL that needs to be cached needs to be inserted into the script during compile, increasing the amount of code and reducing maintainability;
  • The advantage of on fetch is that there is no need to change the compile process and no additional traffic is generated. The disadvantage is that it requires one more access to be available offline.

In addition to static pages and files, true offline availability can be achieved if Ajax data is properly cached. Achieving this step may require some refactoring of existing web apps to separate data and templates.

Service

‘/sw.js’ controls the caching of page resources and requests, so what if the caching policy needs to be updated? That is, what if ‘/sw.js’ is updated? ‘/sw.js’ itself how to update?

If the content of ‘/sw.js’ is updated, the browser retrieves a new file when visiting a website page, and a byte-by-byte comparison of the ‘/sw.js’ file is found to be different, it will think that an update is launched 更新算法, so the new file will be installed and the install event will be triggered. However, the old Service Worker, which is already active at this time, is still running, and the new Service Worker will enter the waiting state after the installation is completed. The new Service Worker will not take effect in the next reopened page until all opened pages are closed and the old Service Worker is automatically stopped.

Automatically update all pages

What if you want all pages to be automatically updated in time when a new version is available? You can execute the’self.skipWaiting () ‘method in the install event to skip the waiting state and go directly to the activate phase. Then when the’activate’ event occurs, update the Service Workers on all Clients by executing the’self.clients.claim () 'method.

Take a look at specific examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
12345678910111213141516171819202122232425//Skip the wait during installation and go directly to active
self.addEventListener('install', function (event) {
event.waitUntil(self.skipWaiting());
});

self.addEventListener('activate', function (event) {
event.waitUntil(
Promise.all([

//Update Client
self.clients.claim(),

//clean up old version
caches.keys().then(function (cacheList) {
return Promise.all(
cacheList.map(function (cacheName) {
if (cacheName ! 'my-test-cache-v1') {
return caches.delete(cacheName);
}
})
);
})
])
);
});

Another thing to note is that the ‘/sw.js’ file may be due to browser caching issues. When the file changes, it is still an old file in the browser. This will cause the update to not respond. If you encounter this problem, you can try to do this: add a filter rule for the file on the Web Server without caching or setting a shorter valid period.

Manual update

In fact, you can also manually update the page with the help of’Registration.update () '.

Refer to the following example:

1
2
3
4
5
6
7
8
9
123456789var version = '1.0.1';

navigator.serviceWorker.register('/sw.js').then(function (reg) {
if (localStorage.getItem('sw_version') ! version) {
reg.update().then(function () {
localStorage.setItem('sw_version', version)
});
}
});
debug

Service Worker debug 技巧 中也会提到, Service Worker 被载入后立即激活可以保证每次 /sw.js 为最新的。

The code is as follows:

1
2
3
123self.addEventListener('install', function () {
self.skipWaiting();
});

Service

We already know that the working principle of Service Worker is based on the registration, installation, activation and other steps in the browser js main thread to share the cache task independently, so how do we do some of the things we want workers to do in a series of operations in these APIs themselves?

Here we need to understand the concept of Service Worker lifecycle, which helps us learn to carry out purposeful callbacks at each stage of the lifecycle, so that our custom work can be carried out correctly and effectively in the Service Worker. MDN gives a detailed Service Worker lifecycle diagram:

Service Worker 生命周期

We can see that the lifecycle is divided into several states: ‘Installing’, ‘After Installation’, ‘Activating’, ‘After Activation’, ‘Obsolete’.

Installing: This state occurs after the service worker is registered, indicating that the installation has started, triggering the install event callback to specify some static resources for offline caching.

There are two methods in the’install 'event callback:

  • 'event.waitUntil () ': Pass a Promise as an argument and wait until the Promise is resolved.
  • 'Self.skipWaiting () ': ‘self’ is a global variable for the current context. Executing this method means forcing the service worker currently in the waiting state to enter the activate state.
    Installed: The service worker has completed installation and is waiting for other service worker threads to be closed.
  • ** Activating **: Clients in this state that are not controlled by other service workers, allow the current worker to complete the installation, and clear the old cache resources of other workers and associated caches, waiting for the new service worker thread to be activated.

There are two methods in the’activate 'callback:

  • 'event.waitUntil () ': Pass a Promise as an argument and wait until the Promise is resolved.
  • 'Self.clients.claim () ': Executing this method in the activate event callback means taking control of the page, so that the updated version cache is used when the page is opened later. The old Service Worker script no longer controls the page and will be stopped later.
  • ** Activated **: In this state, the’activate 'event callback is processed (providing an opportunity to update the cache policy). Functional events’fetch (request) ', 'sync (background synchronization) ', 'push (push) ’ can be handled.
    Redundant: This state indicates the end of a service worker’s life cycle.

Here, in particular, the reasons for entering the redundant state may be these:

  • installation failed
  • Activating failed
  • A new version of Service Worker replaced it and became active

Use in Vue

  • npm install workbox-webpack-plugin
  • After introducing the plugin in webpack.config.js, introduce the plugin in the plugins
  • Run webpack packaging, will generate service-worker.js and manifest in the dist directory
  • Register and install the service-worker.js we packaged and generated in index.js through the code in the example in the previous section

For specific details, please refer toworkbox-webpack-plugin文档

The plug-in is actually based on the vue project and packaging results to generate a service-worker related files, and ultimately we need to add our own service-worker in the project to register the relevant installation code.