atmos24
Создайте комфортную атмосферу в своей квартире с atmos24.ru! Установка кондиционера по выгодной цене в Москве. Закажите сейчас!
Maxim Salnikov
Angular GDE
How to build an offline-ready Angular app
Developer Engagement Lead at Microsoft
Angular
Service Worker
# Installing the Workbox Node module
$ npm install workbox-build --save-dev
On every app build
import {
precacheAndRoute,
createHandlerBoundToURL
} from "workbox-precaching";
import { NavigationRoute, registerRoute } from "workbox-routing";
// Precaches and routes resources from __WB_MANIFEST array
precacheAndRoute(self.__WB_MANIFEST);
// Setting up navigation for SPA
const navHandler = createHandlerBoundToURL("/index.html");
const navigationRoute = new NavigationRoute(navHandler);
registerRoute(navigationRoute);
const { injectManifest } = require("workbox-build");
let workboxConfig = {
swSrc: "src/service-worker.js",
swDest: "dist/prog-web-news/sw.js",
// + more on the next slide
};
injectManifest(workboxConfig).then(() => {
console.log(`Generated ${workboxConfig.swDest}`);
});
globDirectory: "dist/prog-web-news",
globPatterns: ["index.html", "*.css", "*.js", "assets/**/*"],
globIgnores: [
"**/*-es5.*.js", // Skip ES5 bundles for Angular
],
// Angular takes care of cache busting for JS and CSS (in prod mode)
dontCacheBustURLsMatching: new RegExp(".+.[a-f0-9]{20}.(?:js|css)"),
// By default, Workbox will not cache files larger than 2Mb
// (might be an issue for dev builds)
maximumFileSizeToCacheInBytes: 4 * 1024 * 1024, // 4Mb
import { precacheAndRoute } from "workbox-precaching";
import { NavigationRoute, registerRoute } from "workbox-routing";
...
precacheAndRoute([
{ revision: "866bcc582589b8920dbc5bccb73933b1", url: "index.html" },
{ revision: null, url: "styles.c2761edff7776e1e48a3.css" },
{ revision: null, url: "main.3469613435532733abd9.js" },
{ revision: null, url: "polyfills.25b2e0ae5a439ecc1193.js" },
{ revision: null, url: "runtime.359d5ee4682f20e936e9.js" },
{
revision: "33c3a22c05e810d2bb622d7edb27908a",
url: "assets/img/pwa-logo.png",
},
]);
import resolve from 'rollup-plugin-node-resolve'
import replace from 'rollup-plugin-replace'
import { terser } from 'rollup-plugin-terser'
export default {
input: 'dist/prog-web-news/sw.js',
output: {
file: 'dist/prog-web-news/sw.js',
format: 'iife'
},
plugins: [ /* Next slide */ ]
}
plugins: [
resolve(),
replace({
'process.env.NODE_ENV': JSON.stringify('production')
}),
terser()
]
"build-pwa":
"ng build --prod &&
node workbox-inject.js &&
npx rollup -c"
import { Workbox, messageSW } from 'workbox-window';
...
ngOnInit(): void {
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
// Reload-to-Update flow Using messageSW
// See demo repo aka.ms/angular-workbox
}
}
import { registerRoute } from "workbox-routing";
import {
CacheFirst,
NetworkFirst,
StaleWhileRevalidate,
} from "workbox-strategies";
// Gravatars can live in cache
registerRoute(
new RegExp("https://www.gravatar.com/avatar/.*"),
new CacheFirst()
);
// Keeping lists always fresh
registerRoute(
new RegExp("https://progwebnews-app.azurewebsites.net.*posts.*"),
new NetworkFirst()
);
// Load details immediately and check and inform about update right after
import { BroadcastUpdatePlugin } from 'workbox-broadcast-update';
registerRoute(
new RegExp("https://progwebnews-app.azurewebsites.net.*posts/slug.*"),
new StaleWhileRevalidate({
plugins: [
new BroadcastUpdatePlugin(),
],
})
);
import {
googleFontsCache,
imageCache
} from "workbox-recipes";
// GOOGLE FONTS
googleFontsCache({ cachePrefix: "wb6-gfonts" });
// CONTENT
imageCache({ maxEntries: 10 });
// Adding you own event handlers
self.addEventListener("periodicsync", function (event) {
// Your code
});
// Using existing Workbox plugins
import { BackgroundSyncPlugin } from 'workbox-background-sync';
const bgSyncPlugin = new BackgroundSyncPlugin('myQueue', {
maxRetentionTime: 24 * 60 // Retry for max of 24 Hours
});
// Writing your own Workbox plugins
import { MyBackgroundFetchPlugin } from 'my-background-fetch';
Maxim Salnikov
@webmaxru
self.addEventListener('install', event => {
// Putting resources into the Cache Storage
})
self.addEventListener('activate', event => {
// Managing versions
})
self.addEventListener('fetch', event => {
// Exctracting from the cache and serving
})
const PRECACHE = 'precache-v1';
const RUNTIME = 'runtime';
const PRECACHE_URLS = [
'index.html',
'./',
'styles.css',
'../../styles/main.css',
'demo.js'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(PRECACHE)
.then(cache => cache.addAll(PRECACHE_URLS))
.then(self.skipWaiting())
);
});
self.addEventListener('activate', event => {
const currentCaches = [PRECACHE, RUNTIME];
event.waitUntil(
caches.keys().then(cacheNames => {
return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));
}).then(cachesToDelete => {
return Promise.all(cachesToDelete.map(cacheToDelete => {
return caches.delete(cacheToDelete);
}));
}).then(() => self.clients.claim())
);
});
self.addEventListener('fetch', event => {
if (event.request.url.startsWith(self.location.origin)) {
if (event.request.url.indexOf('api/') != -1) {
event.respondWith(
caches.match(event.request.clone()).then((response) => {
return response || fetch(event.request.clone()).then((r2) => {
return caches.open(RUNTIME).then((cache) => {
cache.put(event.request.url, r2.clone());
return r2.clone();
});
});
})
);
} else {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
if (cachedResponse) {
return cachedResponse;
}
return caches.open(RUNTIME).then(cache => {
return fetch(event.request).then(response => {
return cache.put(event.request, response.clone()).then(() => {
return response;
});
});
});
})
);
}
}
});
import { offlineFallback } from 'workbox-recipes'
import { precacheAndRoute } from 'workbox-precaching'
// Include offline.html, offline.png in the WB manifest
precacheAndRoute(self.__WB_MANIFEST)
// Serves a precached web page, or image
// if there's neither connection nor cache hit
offlineFallback({
pageFallback: "offline.html",
imageFallback: "offline.png"
});
Maxim Salnikov
@webmaxru
By atmos24
There is no need to advocate for progressive web apps anymore. The idea of connection-independent applications has proven its viability and we see more and more large and small projects following that path, making the offline-ready behavior a best practice, good manner of the web. In my session, based on the deep exploration of Service Worker API possibilities and gathered UX gotchas, we go through the history of the offline web, the importance of treating the network as an enhancement, current challenges (and their solutions) and proper tooling. We architect our offline-ready Angular app applying the best tech and UX practices adding the features one-by-one: app shell, caching resources and data, sync when online. All in name of our users who demand the new level of the resilient web experience.
Создайте комфортную атмосферу в своей квартире с atmos24.ru! Установка кондиционера по выгодной цене в Москве. Закажите сейчас!