What is the PWA

Progressive Web App, or PWA, is a new way to improve the experience of Web apps by giving users the experience of native apps.

The above is a brief description given by Lavas. My personal impression of PWA is to transfer the native APP experience to the browser, including icon generation on the desktop, fast startup, offline use, and push messages. In a word, it needs to have all the features of native APP and go one step further.

Technology applied by PWA

  • Service Worker
  • cacheStorage
  • Push Notification (not involved in this application)

Demo application

The idea for this App came from Your First Progressive Web App. I saw this demo when I was learning PWA, but I couldn’t understand the code… So I used its UI design and ICON, and began to explore slowly by myself.

The source address

Online browsing address (because there is no ADAPTATION for PC style, please open it in mobile or Chrome mobile debug mode, Chrome click “Add to home screen” to add desktop ICON)

The project structure

  • Images (Store pictures)
  • Fontset.js (global font for different phones)
  • Index.html (home page)
  • Main.js (main program)
  • Manifest.json (Add control desktop launcher (icon))
  • Reset.css (Clear default styles)
  • Skeleton screen (for loading transition)
  • Style.css (main style)
  • Sw.js (Service worker process)

Cache App Shell

First, we need to register a service worker in the main process

// Register the service worker
window.addEventListener('DOMContentLoaded'.function() {
    SW.register();
})

const SW = {
    / / register
    register() {
        // Check whether serviceWorker is available
        if('serviceWorker' in navigator) {
            navigator.serviceWorker.register('./sw.js')
            .then(function() {
                console.log('Service Worker Registered');
            })
            .catch(function() {
                console.log('Service Worker failed'); })}}}Copy the code

If the registration is successful, the service worker officially starts working. The life cycle of the service worker can be simply described as

ServiceWorker (first install or change) -> Install -> Activite

The first step is “Install”. During the install process, you will cache the files you need offline, such as the page itself, the style of the page, and the main application. However, you should not cache sw.js as well, otherwise your application will never be updated

const CACHENAME = 'weather-' + 'v4';
const PATH = '/pwaTest';
const fileToCache = [
    PATH + '/',
    PATH + '/index.html',
    PATH + '/main.js',
    PATH + '/fontSet.js',
    PATH + '/skeleton.js',
    PATH + '/reset.css',
    PATH + '/style.css',
    PATH + '/images/icons/delete.svg',
    PATH + '/images/icons/plus.svg',
    PATH + '/images/partly-cloudy.png',
    PATH + '/images/wind.png',
    PATH + '/images/cloudy_s_sunny.png',
    PATH + '/images/cloudy.png',
    PATH + '/images/clear.png',
    PATH + '/images/rain.png',
    PATH + '/images/fog.png',
    PATH + '/images/icons/icon-32x32.png',
    PATH + '/images/icons/icon-128x128.png',
    PATH + '/images/icons/icon-144x144.png',
    PATH + '/images/icons/icon-152x152.png',
    PATH + '/images/icons/icon-192x192.png',
    PATH + '/images/icons/icon-256x256.png'
];

self.addEventListener('install', e => {
    console.log('Service Worker Install');
    e.waitUntil(
        caches.open(CACHENAME).then(function (cache) {
            self.skipWaiting();
            console.log('Service Worker Caching');
            returncache.addAll(fileToCache); }})))Copy the code

Note: e.waituntil () waits for a Promise object to complete execution.

When install is complete and the activate process is activated, we need to clean up the old cache. Otherwise, the browser will use the old cache again and it will take up space.

self.addEventListener('activate'.function (event) {
    event.waitUntil(
        // Iterate over all cached keys in caches
        caches.keys().then(function (cacheNames) {
            return Promise.all(
                cacheNames.map(function (NAME) {
                    if(NAME ! = CACHENAME) {// Delete cached files other than the current version
                        returncaches.delete(NAME); }})); })); });Copy the code

fetch

At the beginning, I didn’t know what fetch was for. I just clicked with the sample code and the program could run normally. I thought that service worker was just like EMIT and ON between Vue components, passing data through message.

But the service worker is not. In short, the service worker intercepts and processes all the requests from the server to the server’s local file (index.html, style.css, main.js) by listening to the fetch event. Also includes calls to external interfaces (GET, POST requests)

self.addEventListener("fetch".function(e) {
    // e is for all requests. Fetch will listen on all requests without invoking a single request
    e.respondWith(caches.match(e.request).then(function(response) {
          // Search for response in caches, return response if there is one, continue fetch if there is none
          return response || fetch(e.request);
    }));
});
Copy the code

At this point, the initial framework for the application is in place.

Offline functionality

We know that one of the great things about PWA is that it can be used offline, so we need to do some work on our code.

self.addEventListener('fetch', e => {
    e.respondWith(
        caches.match(e.request).then(function (res) {
            if (res) {
                if(e.request.url.indexOf(self.location.host) ! = =- 1) {
                    / / the same
                    return res;
                } else {
                    // Offline
                    if(! navigator.onLine) {return res;
                    } else {
                        return fetch(e.request).then((response) = > {
                            let responeClone = response.clone();
                            let responeClone_2 = response.clone();
                            responeClone_2.json().then(data= > {
                                caches.open(CACHENAME).then(cache= > {
                                    cache.put(e.request, responeClone);
                                })
                            }).catch(e= > {
                                console.log(e);
                            })
                            returnresponse; })}}}// Remote js file
            if (e.request.url.indexOf('https://pv.sohu.com/cityjson?ie=utf-8')! = =- 1) {
                return fetch(e.request);
            }
            return fetch(e.request).then((response) = > {
                let responeClone = response.clone();
                let responeClone_2 = response.clone();
                responeClone_2.json().then(data= > {
                    caches.open(CACHENAME).then(cache= > {
                        cache.put(e.request, responeClone);
                    })
                }).catch(e= >{})return response;
            }).catch(e= >{})})})Copy the code

The general idea is as follows:

  • Whether online or offline, parts of the App shell (i.e. homology) can always be retrieved offline, so simply return res
  • Online, in the case of the weather, direct call remote interface (do not use the local cache), the reason for doing this, because the weather need updated in real time, every time you visit should be to the latest weather conditions, if the call after a time to call directly the cached data, that the weather will stay forever in the first time
  • When offline, directly or already cached weather conditions can be used

Skeleton screen

When the user’s network condition is poor, the page information takes some time to load, but if the user is directly left with a white screen, the user does not know whether the application is still working, so it needs a transition to ease the user’s anxiety, then we need to use a skeleton screen

skeleton.js

const Skeleton = {
    Render(key, type, row) {
        let rows = (function() {
            let temp = ' ';
            for (let i = 0; i < row; i ++) {
                temp += '<p class="item"></p>'
            }
            returntemp; }) ();let model = (function() {
            let temp = ' ';
            switch (type) {
                case 'normal':
                    temp = `
                        <div class="card preload mg" id="${key}">
                            ${ rows }
                            <p class="item" style="width: 4rem"></p>
                        </div>
                    `
                    break;
                case 'title':
                    temp = `
                        <div class="card preload mg" id="${key}">
                            <p class="head"></p>
                            ${ rows }
                            <p class="item" style="width: 4rem"></p>
                        </div>
                    `
                    break;
                default:
                    break;
            }
            return document.createRange().createContextualFragment(temp); }) ();returnmodel; }}export default Skeleton
Copy the code

main.js

    import skeleton from './skeleton.js'// Create a new city weather instance buildNewCity(city) {if(navigator.online) {// Skeleton screen renders firstlet preModel = (function() {
                return skeleton.Render(city, 'title', 3); }) ();let container = document.getElementById('container'); container.appendChild(preModel); } this.getInfoNow(city); } // Skeleton screen is required only when onlineif (navigator.onLine) {
        let container = document.getElementById(this.name);
        setTimeout(() => {
            container.classList.remove('preload');
            container.innerHTML = ""; container.appendChild(model); // For the delete key binding event document.getelementById ('delete_' + this.name).addEventListener('click'.function() { WEATHERINFO.deleteCity(_this.name); })}, 200); }else {
        let card = document.createElement('div');
        card.classList = ['card ' + 'mg'];
        card.id = _this.name;
        card.appendChild(model);

        let container = document.getElementById('container'); container.appendChild(card); // For the delete key binding event document.getelementById ('delete_' + this.name).addEventListener('click'.function() { WEATHERINFO.deleteCity(_this.name); })}Copy the code

When the user adds a city, the skeleton screen is first put on the page, and then the fetch operation is carried out. When the fetch is complete, the data fetched will be put into the corresponding div.

Chrome Slow 3G under testing

So those are a couple of technical points of this little app, yesStab hereWatch the demo,The source code

If the article is useful to you, you can click “STAR”