preface

Recently, the iteration of the company’s projects has been gradually slowed down, and the time to leave work has gradually become earlier. Therefore, in line with the concept of gradual increase, THE company’s projects have been transformed into PWA after work

Why PWA

  1. User requirements. Many of our users have small computers, don’t want to remember web addresses, and don’t know how to bookmark their browsers. Before the use of similar software have desktop version, there is a feel desktop version than the web page version of reliable, simple illusion, has repeatedly reflected in nail nail after-sales group, how to save the web page to the desktop, convenient for him to open directly on the desktop next time
  2. PWA is progressive. If the user’s browser does not support the API required by ServiceWorker and so on to build PWA, there will be no impact on the use of PWA. Moreover, according to the buried platform, Chrome browser accounts for about 80% of our users
  3. Offline cache, installable, fetch intercepting and other functions are attractive to me, and I hope to learn to use them

Began to transform

In order to quickly transform into PWA, I chose to use WorkBox, a PWA tool library launched by Google, and create serviceWorker files with Webpack

Install dependencies

npm install --save-dev workbox-webpack-plugin
npm install --save workbox-core workbox-routing workbox-strategies workbox-precaching workbox-expiration workbox-cacheable-response
Copy the code

Workbox-webpack-plugin provides two plugins, GenerateSW and InjectManifest.

GenerateSW

The GenerateSW plug-in can be configured to directly compile and generate corresponding serviceWorker files, without us directly writing serviceWorker files. The usage mode is roughly as follows:

import { InjectManifest } from 'workbox-webpack-plugin';

new GenerateSW({
    skipWaiting: true.clientsClaim: true.mode: 'development'.runtimeCaching: [{urlPattern: /^https? : \ \ / \ /. +? \.alicdn.com\/.+$/,
        handler: 'StaleWhileRevalidate'},]});Copy the code

Generating the serviceWorker file by GenerateSW compilation is simple but not flexible enough, so I actually use another InjectManifestPlugin

InjectManifest

InjectManifest does two main things

  1. Compile webpack to generate a list of resource files to variablesself.__WB_MANIFESTThe form is injected into what we provideserviceWorkerIn the template file
  2. Compile the template file we provide to generate the targetserviceWorkerfile

The usage mode is roughly as follows:

const { InjectManifest } = require('workbox-webpack-plugin');
new InjectManifest({
    swSrc: path.resolve('src/sw.js'),
    swDest: path.resolve(BUILD_DEST, 'sw.js'),}),Copy the code

Write the serviceWorker template file

Precache static resources

After serviceWork is activated, it immediately requests and caches all the files in the pre-cached list. Then, when the same resource is downloaded, the pre-cached resources are preferentially used

workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
Copy the code

Routing request cache

  1. Use NavigationRoute to cache HTML files
registerRoute(
  new NavigationRoute(
    new NetworkFirst({
      cacheName: 'navigation-cache'.plugins: [
        new CacheableResponsePlugin({
          statuses: [200]}),new ExpirationPlugin({
          maxEntries: 300.maxAgeSeconds: 7 * 24 * 60 * 60,}),],}),,);Copy the code
  1. Cache local static resource files
registerRoute(
  /\.(css|js|png|jpg|jpeg|svg|webp)$/.new CacheFirst({
    cacheName: 'static-cache'.plugins: [
      new CacheableResponsePlugin({
        statuses: [200]}),new ExpirationPlugin({
        maxEntries: 300.maxAgeSeconds: 7 * 24 * 60 * 60,}),],}),);Copy the code
  1. Cache static resource files in the CDN
registerRoute( /^https? : \ \ / \ /. *? \.alicdn.com\/.+?\.(css|js|png|jpg|jpeg|svg|gif|webp)$/, new CacheFirst({ cacheName: 'alicdn-cache', plugins: [ new CacheableResponsePlugin({ statuses: [200] }), new ExpirationPlugin({ maxEntries: 300, maxAgeSeconds: 7 * 24 * 60 * 60,}),],);Copy the code

There is a point that needs to be noted here, alicDN static resources and our website domain name are not the same domain name, there is cross-domain, when the request for static resources, opaque response will be returned. When we use cache-first to Cache opaque responses,workbox tells us not to Cache opaque responses with this policy because opaque responses are a black box to JavaScript, Can’t get the correct status code, headers, and body, so the resources in our cache are not reliable; In addition, when we cache opaque responses, the cache occupies much more space than the actual resource size, which is likely to cause DOMException: Quota exceeded. So you need to deal with caching of opaque responses

The opaque response becomes transparent

Since opaque responses can cause problems, simply make opaque responses transparent and you should be fine. After checking, I found that the alicDN response header will return access-Control-Allow-Origin: *, the backend supports CORS cross-domain resource sharing. In this case, as long as we request static resources, we should be able to let the request go cORS. So I tried to enable CORS in one of the IMG tags

<img crossorigin="anonymous" />
Copy the code

The opaque response succeeded in becoming transparent. But adding crossorigin to all tags would be too much work. Is there a unified processing method? There is. This can be done by intercepting fetch requests, or in the case of workBox, by setting fetchOptions in the cache policy class

registerRoute( /^https? : \ \ / \ /. *? \.alicdn.com\/.+?\.(css|js|png|jpg|jpeg|svg|gif|webp)$/, new CacheFirst({ cacheName: 'alicdn-cache', plugins: [ new CacheableResponsePlugin({ statuses: [200] }), new ExpirationPlugin({ maxEntries: 300, maxAgeSeconds: 8 * 24 * 60 * 60,}),], // add fetchOptions: {mode: 'cors', credentials: ' ',},}),);Copy the code

Create the manifest.json file

From the MANIFEST configuration file, you can specify the PWA application icon, initial page, background color, theme color, display mode, and more

// manifest.json
{   
    "name": "xxx"."short_name": "xxx"."icons": [{"src": "/static/images/[email protected]"."sizes": "144x144"."type": "image/png"}]."start_url": "/index.html"."display": "standalone"."background_color": "# 000"."theme_color": "# 000"
}
Copy the code
<link rel="manifest" href="/manifest.json">
Copy the code

conclusion

Finally, our PWA application transformation is complete. PWA technology is a collection of a series of technologies. Here, I only used serviceWorker, MANIFEST, push/ Notification and other technologies but did not cover them. If necessary, corresponding functions can be added in the future

extending

What is an opaque response?

To put it simply, an opaque response is the response we get when we use fetch and set no-CORS to request a cross-domain resource

fetch('https://www.baidu.com/img/flexible/logo/pc/[email protected]', {
  mode: 'no-cors'
}).then(response= > {
  return console.log(response)
}).catch(error= > {
  return console.log(error)
});
Copy the code

The printed result is

Response {
  body: null
  bodyUsed: false
  headers: {},
  ok: false
  redirected: false
  status: 0
  statusText: ""
  type: "opaque"
  url: ""
}
Copy the code

In Response, we can see the opaque Response

  1. The HTTP status code is 0 instead of 200
  2. StatusText is empty
  3. Headers are empty
  4. The body also is empty

Anyway, we (JavaScript) can’t get the content in this Response