Introduction to

Progressive Web Apps

&

Enonic PWA Starter

MOBILE APPS

WEB APPS

APPS SITES

NATIVE VS WEB

NATIVE VS WEB?

The full Safari engine is inside of iPhone. And so, you can write amazing Web apps that look and behave exactly like apps on the iPhone. You’ve got everything you need if you know how to write apps using the most modern web standards to write amazing apps for the iPhone today. So developers, we think we’ve got a very sweet story for you. You can begin building your iPhone apps today.

Steve Jobs, 2007

PWA

WTF  IS             ?

PROGRESSIVE WEB APPLICATIONS

Progressive Web Apps are:

  • Progressive
  • Responsive
  • Connectivity independent
  • App-like
  • Fresh
  • Safe
  • Discoverable
  • Re-engageable
  • Installable
  • Linkable

Progressive Web Apps

  • Offline Support with Service Worker
  • App Shell
  • Web App Manifest

SERVICE WORKER

SERVICE WORKER

in a nutshell

SERVICE WORKER

if ('serviceWorker' in navigator) {

  // Register a service worker hosted at the root of the
  // site using the default scope.

  navigator.serviceWorker.register('/sw.js').then(function(registration) {

    console.log('Service worker registration succeeded:', registration);

  }).catch(function(error) {

    console.log('Service worker registration failed:', error);
  });

} else {

  console.log('Service workers are not supported.');

}

registration

CACHING STRATEGIES

Network First

Cache First

Cache Only

Network Only

Cache, then Network

APP SHELL

APP SHELL

WEB MANIFEST

...is what turns your SITE into an APP

WEB MANIFEST

{
  "name": "PWA Starter for Enonic XP",
  "short_name": "PWA Starter",
  "theme_color": "#FFF",
  "background_color": "#FFF",
  "display": "standalone",
  "start_url": ".?source=web_app_manifest",
  "icons": [
    {
      "src": "precache/icons/icon.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>My First PWA</title>

    <link rel="manifest" href="manifest.json">
</head>

WEB MANIFEST

SERVICE WORKER

?

WEB MANIFEST

+

PWA

=

SERVICE WORKER

if (containsDataUrl(e.request.url) || responseUpdate) {
    let url = responseUpdate ? e.request.url.replace("?update=true", "") : e.request.url;
    
    e.respondWith(
      caches.open(responseUpdate ? cacheName : dataCacheName).then(function(cache) {
          consoleLog("Fetching data url " + url);
          return fetch(e.request)
              .then(function (response) {
                  cache.put(url, response.clone());
                  return response;
              })
              .catch(function (ex) {
                  consoleLog('Network is down. Trying to serve from cache...');
                  return cache.match(e.request, {
                      ignoreVary: true
                  })
                  .then(function (response) {
                      consoleLog((response ? 'Serving from cache' : 'No cached response found') + ': ' + e.request.url);
    
                      return response || getFallbackPage(e.request.url);
                  });
              });
      })
    );
  }
  else {
      e.respondWith(
          caches.match(e.request, {
              ignoreVary: true
          })
          .then(function (response) {
              consoleLog((response ? 'Serving from cache' : 'Requesting from the server') + ': ' + e.request.url);

              return response || fetch(e.request);
          })
      );
  }

...IS COMPLICATED

REMEMBER TO CACHE YOUR ASSETS

const filesToCache = [
    offlineUrl,
    offlineCompactUrl,
    '{{siteUrl}}',
    '{{siteUrl}}/',
    '{{assetUrl}}/js/main.js',
    '{{assetUrl}}/js/image.js',
    '{{assetUrl}}/js/material.js',
    '{{assetUrl}}/js/dialog-polyfill.js',
    '{{assetUrl}}/css/main.css',
    '{{assetUrl}}/css/image.css',
    '{{assetUrl}}/css/material.css',
    '{{assetUrl}}/css/dialog-polyfill.css',
    '{{assetUrl}}/img/cancel.svg',
    '{{assetUrl}}/img/download_image.svg',
    '{{assetUrl}}/img/info.svg',
    '{{assetUrl}}/img/pencil.svg',
    '{{assetUrl}}/img/spinner.svg',
    '{{assetUrl}}/img/placeholder.png',
    '{{assetUrl}}/img/noisy-texture.png',
    '//fonts.googleapis.com/icon?family=Material+Icons',
    '//fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&amp;lang=en'
];


self.addEventListener('install', function(e) {
  consoleLog('Install');

  e.waitUntil(self.skipWaiting());

  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      consoleLog('Caching app shell');
      return cache.addAll(filesToCache);
    }).catch(function(err) {
        console.log(err);
    })
  );
});

ENONIC PWA STARTER

E    NONIC PWA STARTER

DOES ALL THE DIRTY JOB FOR YOU!

E    NONIC PWA STARTER

  • Generates and registers Service Worker

  • Implements Web Manifest

  • Bundles JS files and CSS stylesheets

  • Precaches all assets

  • Implements fallback page

  • Routes requests

  • Complies with PWA audits

E    NONIC PWA STARTER

  • Integration with NPM, Gulp and Webpack
  • Automatic generation of Web Manifest
  • Automatic generation of Service Worker
  • Routing with configurable caching strategy
  • Run-time caching
  • Background synchronisation
  • ...and much more...

Easy Precaching

importScripts('/node_modules/workbox-sw/build/workbox-sw.vX.X.X.prod.js');

const workboxSW = new WorkboxSW();
workboxSW.precache([]);

Template for Service Worker:

{
    globDirectory: 'resources/assets',
    globPatterns: ['precache/**\/*'],
    globIgnores: ['*.svg'],
    swSrc: 'resources/js/sw-template.js',
    swDest: 'build/sw.js'
}

Config:

Easy Precaching

importScripts('/node_modules/workbox-sw/build/workbox-sw.vX.X.X.prod.js');

const workboxSW = new WorkboxSW();
workboxSW.precache([
  {
    url: '/precache/index.html',
    revision: 'bb121c',
  }, {
    url: '/precache/styles/main.css',
    revision: 'acd123',
  }, {
    url: '/precache/scripts/main.js',
    revision: 'a32caa',
  }
]);

Built Service Worker:

Runtime Caching

importScripts('/node_modules/workbox-sw/build/workbox-sw.vX.X.X.prod.js');

const workboxSW = new WorkboxSW();

workboxSW.router.registerRoute('/schedule', workboxSW.strategies.networkFirst());

workboxSW.router.registerRoute('/news', workboxSW.strategies.cacheFirst());

workboxSW.router.registerRoute('/about', workboxSW.strategies.cacheOnly());

Simple routing with caching strategies:

E    NONIC PWA STARTER

E    NONIC PWA STARTER

Demo Time

Thank you!

Stay progressive :)

PWA Presentation for Evry

By Alan Semenov

PWA Presentation for Evry

  • 2,095