Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Make your Polymer web app Progressive

Make your Polymer web app Progressive

In this presentation, I describe all PWA features and how to implement them using Polymer

Oleh Zasadnyy

October 22, 2016
Tweet

More Decks by Oleh Zasadnyy

Other Decks in Programming

Transcript

  1. Mobile web Apps Monthly unique visitors (MM) 8.9 3.3 Source

    (June 2015): https://goo.gl/nArG5B 2.5x
  2. 53% of visits are abandoned if a mobile site takes

    more than 3 seconds to load Source (September 2016): https://goo.gl/a3g4J5 1 out of 2 people expect a page to load in less than 2 seconds
  3. 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9

    1 Waiting in line at retail store Watching a melodramatic TV show Standing at the edge of a virtual cliff Watching a horror movie Experiencing mobile delays Solving a math problem Source (2015): https://goo.gl/8v7bRG The level of stress caused by mobile delays was comparable to watching a horror movie Cognitive load associated with stressful situations
  4. 77% of mobile sites take longer than 10 seconds to

    load on 3G networks Source (September 2016): https://goo.gl/a3g4J5 19 seconds is the average load time for mobile sites on 3G networks
  5. 1.2 1.5 1.8 2.1 2.4 2.7 3.0 3.3 3.6 4.2

    4.5 4.8 5.1 5.4 5.7 6.0 6.3 6.6 6.9 7.2 7.5 7.8 8.1 8.4 8.7 9.0 9.3 9.6 9.9 0 20,000 40,000 60,000 80,000 100,000 120,000 140,000 160,000 180,000 3.9 Load time (seconds) Sessions Sessions Conversation rate Source (September 2015): https://goo.gl/qEcjDu 1.9% 1.5% 0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 1.7 Conversion rate (%) 0.98% 1.9x Conversion rate
  6. Source (September 2015): https://goo.gl/qEcjDu 1.2 1.5 1.8 2.1 2.4 2.7

    3.0 3.3 3.6 4.2 4.5 4.8 5.1 5.4 5.7 6.0 6.3 6.6 6.9 7.2 7.5 7.8 8.1 8.4 8.7 9.0 9.3 9.6 9.9 0 20,000 40,000 60,000 80,000 100,000 120,000 140,000 160,000 180,000 3.9 Load time (seconds) Sessions Sessions Bounce rate 56% 12.8% 0 6 13 19 26 32 39 45 51 58 Bounce rate (%) 20% Bounce rate
  7. Push critical resources for the initial URL route Render initial

    route Pre-cache remaining routes Lazy-load and create remaining routes on demand
  8. <app-drawer-layout fullbleed>
 <app-drawer>
 <app-toolbar>Menu</app-toolbar>
 <iron-selector selected="[[page]]">
 <a name="view1" href="/view1">View One</a>


    ...
 </iron-selector>
 </app-drawer>
 
 <app-header-layout>
 ...
 </app-header-layout>
 </app-drawer-layout>
  9. Polymer({
 is: ‘my-app', 
 properties: {
 page: {
 type: String,


    observer: '_pageChanged'
 }
 }, 
 _pageChanged: function (page) {
 var resolvedPageUrl = this.resolveUrl('my-' + page + '.html');
 this.importHref(resolvedPageUrl, null, this._showPage404, true);
 }, 
 _showPage404: function () {
 this.page = 'view404';
 }
 });
  10. Amsterdam, NL - Go Daddy - Chrome - 3G First

    View (3.7s) Repeat View (0.6s)
  11. if ('serviceWorker' in navigator) {
 navigator.serviceWorker.register('/service-worker.js')
 .then(function (reg) {
 console.log('Service

    Worker Registered', reg);
 })
 .catch(function (err) {
 console.log('Error registering Service Worker', err);
 });
 } Register Service Worker
  12. var cacheName = 'app-shell-cache-v1';
 var filesToCache = ['/', '/index.html',...];
 


    self.addEventListener('install', function (event) {
 event.waitUntil(caches.open(cacheName)
 .then(function (cache) {
 return cache.addAll(filesToCache);
 }).then(function () {
 return self.skipWaiting();
 }));
 }); Cache Files
  13. self.addEventListener('activate', function (e) {
 e.waitUntil(
 caches.keys().then(function (keyList) {
 return Promise.all(keyList.map(function

    (key) {
 if (key !== cacheName) {
 return caches.delete(key);
 }
 }));
 }));
 return self.clients.claim();
 }); Remove Outdated Resources
  14. {
 "entrypoint": "index.html",
 "shell": "src/my-app.html",
 "fragments": [
 "src/my-view1.html",
 "src/my-view2.html",
 "src/my-view3.html",


    "src/my-view404.html"
 ],
 "sourceGlobs": [
 "src/**/*",
 "images/**/*",
 "bower.json"
 ],
 "includeDependencies": [
 "manifest.json",
 "bower_components/webcomponentsjs/webcomponents-lite.min.js"
 ]
 }
  15. Chrome Desktop and Mobile (version 40+) Firefox Desktop and Mobile

    (version 44+) Opera on Mobile (version 27+)
  16. Browser quota limits Chrome - 6% of free disk space

    per origin Firefox - 10% of free disk space (shared across eTLD+1) Safari - at least 10% of free disk space Edge - <8GB: 10MB 8GB-32GB: 50MB 32GB-128GB: 250MB >128GB: 500MB
  17. {
 "name": “GDG DevFest Ukraine 2016",
 "short_name": “GDG DevFest",
 "icons":

    [{
 "src": "images/icons/splash-icon-128.png",
 "sizes": "128x128",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-192.png",
 "sizes": "192x192",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-384.png",
 "sizes": "384x384",
 "type": "image/png"
 }],
 "start_url": "/",
 "display": "standalone",
 "background_color": "#607d8b",
 "theme_color": "#607d8b"
 }
  18. {
 "name": “GDG DevFest Ukraine 2016",
 "short_name": “GDG DevFest",
 "icons":

    [{
 "src": "images/icons/splash-icon-128.png",
 "sizes": "128x128",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-192.png",
 "sizes": "192x192",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-384.png",
 "sizes": "384x384",
 "type": "image/png"
 }],
 "start_url": "/",
 "display": "standalone",
 "background_color": "#607d8b",
 "theme_color": "#607d8b"
 }
  19. {
 "name": “GDG DevFest Ukraine 2016",
 "short_name": “GDG DevFest",
 "icons":

    [{
 "src": "images/icons/splash-icon-128.png",
 "sizes": "128x128",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-192.png",
 "sizes": "192x192",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-384.png",
 "sizes": "384x384",
 "type": "image/png"
 }],
 "start_url": "/",
 "display": "standalone",
 "background_color": "#607d8b",
 "theme_color": "#607d8b"
 }
  20. {
 "name": “GDG DevFest Ukraine 2016",
 "short_name": “GDG DevFest",
 "icons":

    [{
 "src": "images/icons/splash-icon-128.png",
 "sizes": "128x128",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-192.png",
 "sizes": "192x192",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-384.png",
 "sizes": "384x384",
 "type": "image/png"
 }],
 "start_url": "/",
 "display": "standalone",
 "background_color": "#607d8b",
 "theme_color": "#607d8b"
 }
  21. {
 "name": “GDG DevFest Ukraine 2016",
 "short_name": “GDG DevFest",
 "icons":

    [{
 "src": "images/icons/splash-icon-128.png",
 "sizes": "128x128",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-192.png",
 "sizes": "192x192",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-384.png",
 "sizes": "384x384",
 "type": "image/png"
 }],
 "start_url": "/",
 "display": "standalone",
 "background_color": "#607d8b",
 "theme_color": "#607d8b"
 }
  22. {
 "name": “GDG DevFest Ukraine 2016",
 "short_name": “GDG DevFest",
 "icons":

    [{
 "src": "images/icons/splash-icon-128.png",
 "sizes": "128x128",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-192.png",
 "sizes": "192x192",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-384.png",
 "sizes": "384x384",
 "type": "image/png"
 }],
 "start_url": "/",
 "display": "standalone",
 "background_color": "#607d8b",
 "theme_color": "#607d8b"
 }
  23. {
 "name": “GDG DevFest Ukraine 2016",
 "short_name": “GDG DevFest",
 "icons":

    [{
 "src": "images/icons/splash-icon-128.png",
 "sizes": "128x128",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-192.png",
 "sizes": "192x192",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-384.png",
 "sizes": "384x384",
 "type": "image/png"
 }],
 "start_url": "/",
 "display": "browser",
 "background_color": "#607d8b",
 "theme_color": "#607d8b"
 }
  24. {
 "name": “GDG DevFest Ukraine 2016",
 "short_name": “GDG DevFest",
 "icons":

    [{
 "src": "images/icons/splash-icon-128.png",
 "sizes": "128x128",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-192.png",
 "sizes": "192x192",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-384.png",
 "sizes": "384x384",
 "type": "image/png"
 }],
 "start_url": "/",
 "display": "browser",
 "background_color": "#607d8b",
 "theme_color": "#607d8b"
 }
  25. {
 "name": “GDG DevFest Ukraine 2016",
 "short_name": “GDG DevFest",
 "icons":

    [{
 "src": "images/icons/splash-icon-128.png",
 "sizes": "128x128",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-192.png",
 "sizes": "192x192",
 "type": "image/png"
 },{
 "src": "images/icons/splash-icon-384.png",
 "sizes": "384x384",
 "type": "image/png"
 }],
 "start_url": "/",
 "display": "browser",
 "background_color": "#607d8b",
 "theme_color": "#607d8b"
 }
  26. {
 "body": "Did you make a $1,000,000 purchase at Dr.

    Evil...",
 "icon": "images/ccard.png",
 "vibrate": [200, 100, 200, 100, 200, 100, 400],
 "tag": "request",
 "actions": [
 { "action": "yes", "title": "Yes", "icon": "images/yes.png" },
 { "action": "no", "title": "No", "icon": "images/no.png" }
 ]
 }
  27. Check if User is Subscribed Ask User To Subscribe User

    Subscribes Send Subscription Save Subscription Browser Server Subscribing Users
  28. Sending Messages Generate Message Send to End Point Send to

    Browser Received by Browser Server Browser End Point
  29. if ('serviceWorker' in navigator) {
 navigator.serviceWorker.register('/service-worker.js')
 .then(function (reg) {
 reg.pushManager.getSubscription()


    .then(function (sub) {
 console.log('Subscription Info', sub);
 });
 });
 } else {
 console.log('Subscription Info', 'Not Supported');
 } Check for Subscriptions
  30. var opts = {userVisibleOnly: true, applicationServerKey: pubKey};
 navigator.serviceWorker.getRegistration().then(function (reg) {


    reg.pushManager.subscribe(opts)
 .then(function (sub) {
 console.log('Update Server with sub obj', sub);
 updateServerWithSubscription(sub);
 })
 .catch(function (error) {
 console.log('Unable to subscribe user', error);
 });
 }); Subscribe the User
  31. navigator.serviceWorker.getRegistration().then(function (reg) {
 reg.pushManager.getSubscription().then(function (sub) {
 if (sub) {
 sub.unsubscribe();


    console.log('Update our server to remove subscription');
 }
 });
 }).catch(function (error) {
 console.log('Error while trying to unsubscribe', error);
 }); Unsubscribe the User
  32. self.addEventListener('push', function (event) {
 var data;
 if (event.data) {
 data

    = event.data.json();
 } else { 
 // fetch data from server 
 }
 self.registration.showNotification(data.title, {
 body: data.body, icon: data.icon, tag: data.tag
 });
 }); Listen For Messages
  33. self.registration.showNotification(data.title, {
 body: data.body,
 icon: data.icons,
 tag: data.tag,
 actions: [


    {action: 'like', title: 'Like', icon: ic_li},
 {action: 'reshare', title: 'Reshare', icon: ic_re}
 ]
 }); Add Action Buttons
  34. Respond To The User self.addEventListener('notificationclick', function (event) {
 if (event.action

    === 'like') {
 event.waitUntil(fetch('/like-from-notification'));
 } else if (event.action === 'reshare') {
 event.waitUntil(fetch('/reshare-from-notification'));
 } else {
 clients.openWindow(event.srcElement.location.origin);
 }
 });
  35. <platinum-push-messaging id="messaging"
 title="Application updated"
 message="The application was updated in the

    background"
 icon-url="icon.png"
 click-url="notification.html"
 ></platinum-push-messaging>
  36. requestPermission: function () {
 this.$.messaging.enable()
 .then(function () {
 // permission

    was granted
 }, function (err) {
 // permission was denied
 });
 } Subscribe the User
  37. requestPermission: function () {
 this.$.messaging.requestPermission()
 .then(function () {
 // permission

    was granted
 }, function (err) {
 // permission was denied
 });
 } Subscribe the User
  38. Chrome Desktop and Mobile (version 50+) Firefox Desktop and Mobile

    (version 44+) Opera on Mobile (version 37+)
  39. Stick to transform and opacity changes for your animations Promote

    moving elements with will-change or translateZ Avoid overusing promotion rules; layers require memory and management
  40. Stick to transform and opacity changes for your animations Promote

    moving elements with will-change or translateZ Avoid overusing promotion rules; layers require memory and management
  41. Stick to transform and opacity changes for your animations Promote

    moving elements with will-change or translateZ Avoid overusing promotion rules; layers require memory and management
  42. Avoid setTimeout or setInterval for visual updates; always use requestAnimationFrame

    instead Move long-running JavaScript off the main thread to Web Workers Use micro-tasks to make DOM changes over several frames Use Chrome DevTools’ Timeline and JavaScript Profiler to assess the impact of JavaScript
  43. Avoid setTimeout or setInterval for visual updates; always use requestAnimationFrame

    instead Move long-running JavaScript off the main thread to Web Workers Use micro-tasks to make DOM changes over several frames Use Chrome DevTools’ Timeline and JavaScript Profiler to assess the impact of JavaScript
  44. var dataSortWorker = new Worker("sort-worker.js");
 dataSortWorker.postMesssage(dataToSort);
 
 // The main

    thread is now free to continue working on other things...
 
 dataSortWorker.addEventListener('message', function (evt) {
 var sortedData = evt.data;
 // Update data on screen...
 });
  45. Avoid setTimeout or setInterval for visual updates; always use requestAnimationFrame

    instead Move long-running JavaScript off the main thread to Web Workers Use micro-tasks to make DOM changes over several frames Use Chrome DevTools’ Timeline and JavaScript Profiler to assess the impact of JavaScript
  46. var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
 requestAnimationFrame(processTaskList);
 
 function processTaskList(taskStartTime) {
 var

    taskFinishTime;
 do {
 var nextTask = taskList.pop(); // Assume the next task is pushed onto a stack.
 
 processTask(nextTask); // Process nextTask.
 
 // Go again if there’s enough time to do the next task.
 taskFinishTime = window.performance.now();
 } while (taskFinishTime - taskStartTime < 3);
 
 if (taskList.length > 0)
 requestAnimationFrame(processTaskList);
 }
  47. Avoid setTimeout or setInterval for visual updates; always use requestAnimationFrame

    instead Move long-running JavaScript off the main thread to Web Workers Use micro-tasks to make DOM changes over several frames Use Chrome DevTools’ Timeline and JavaScript Profiler to assess the impact of JavaScript
  48. <style is="custom-style">
 :root {
 --layout: {
 display: flex;
 };
 


    --layout-inline: {
 display: inline-flex;
 };
 
 --layout-horizontal: {
 @apply(--layout);
 flex-direction: row;
 }; ...
  49. 3.5 minutes vs 1.1 minutes 40% higher re-engagement rate 70%

    greater conversion rate among those arriving via Add to Home Screen 3x lower data usage Live: https://www.flipkart.com
  50. 104% for new users across all browsers 82% increase in

    iOS conversion rate 2X more pages visited per session 74% increase in time spent per session across all browsers Live: https://aliexpress.com
  51. 23% increase in mobile search users who return within 7

    days 88% improvement in load time for AMP content versus traditional mobile web 1000+ articles The Washington Post publishes in AMP HTML daily Live: https://www.washingtonpost.com