注意点
- HTTPS対応が必要
manifest.jsonの設定
各種プロパティ詳細
https://developer.mozilla.org/ja/docs/Web/Manifest
{
"name": "Test PWA App",
"short_name": "My PWA service", // アプリの名前
"icons": [ // 各アイコンを設定
{
"src": "/src/images/icons/app-icon-48x48.png",
"type": "image/png",
"sizes": "48x48"
},
{
"src": "/src/images/icons/app-icon-96x96.png",
"type": "image/png",
"sizes": "96x96"
},
{
"src": "/src/images/icons/app-icon-144x144.png",
"type": "image/png",
"sizes": "144x144"
},
{
"src": "/src/images/icons/app-icon-192x192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/src/images/icons/app-icon-256x256.png",
"type": "image/png",
"sizes": "256x256"
},
{
"src": "/src/images/icons/app-icon-384x384.png",
"type": "image/png",
"sizes": "384x384"
},
{
"src": "/src/images/icons/app-icon-512x512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/index.html",
"scope": ".",
"display": "standalone",
"orientation": "portrait-primary",
"background_color": "#fff", //スプラッシュスクリーンの背景
"theme_color": "#3f51b5", //androidのバーで表示
"description": "Plactice PWA APP",
"dir": "ltr",
"lang": "en-US"
}
<link rel="manifest" href="/manifest.json">
Service Workerの登録
register してから install もしくは activate
参考: https://developers.google.com/web/fundamentals/primers/service-workers/?hl=ja
register
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then(function() {
console.log('サービスワーカーが登録されました');
});
}
install
self.addEventListener('install', function(event) {
console.log('サービスワーカーのインストール', event);
});
self.addEventListener('activate', function(event) {
console.log('サービスワーカーの更新', event);
return self.clients.claim();
});
clients.claim
アクティベート後に Service Worker 内で clients.claim() を呼び出すことによって、制御されていないクライアントを制御できます。前述のデモのバリエーション(activate イベントで clients.claim() を呼び出す)を次に示します。 最初に猫が表示されるはずです。 「はずです」というのは、タイミングによって異なるからです。猫が表示されるのは、画像が読み込まれる前に Service Worker がアクティベートされ、clients.claim() が有効になった場合のみです。
Service Worker を使用して、ページをネットワーク経由で読み込む場合とは異なる方法で読み込んだ場合、clients.claim() は問題になることがあります。それは、Service Worker は最終的にはそれがない状態で読み込まれた一部のクライアントを制御するためです。
注: 多くのユーザーはボイラプレートとして clients.claim() を使用しているようですが、私自身はめったに使用しません。 本当に重要になるのは最初の読み込み時のみであり、段階的な機能拡張により、ページは通常は Service Worker がなくても問題なく動作します。
Cache
- 静的なファイルのキャッシュ
- 動的なファイルのキャッシュ
キャッシュネームによってブラウザのcacheストレージに貯めておくので、cacheのバージョンが変わったら必ず以前の名前のキャッシュを削除する
基本形
var CACHE_STATIC_NAME = 'static-v1'; // version指定
var CACHE_DYNAMIC_NAME = 'dynamic-v1';
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_STATIC_NAME)
.then(function(cache) {
cache.addAll([
// キャッシュしたいアイテム
'/',
'/index.html',
'/src/js/app.js',
]);
})
)
});
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys()
.then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {
console.log('古いキャッシュの削除', key);
return caches.delete(key);
}
}));
})
);
return self.clients.claim();
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response;
} else {
return fetch(event.request)
.then(function(res) {
return caches.open(CACHE_DYNAMIC_NAME)
.then(function(cache) {
cache.put(event.request.url, res.clone());
return res;
})
})
.catch(function(err) {
});
}
})
);
});
発展
var CACHE_STATIC_NAME = 'static-v15';
var CACHE_DYNAMIC_NAME = 'dynamic-v2';
var STATIC_FILES = [
'/',
'/index.html',
'/offline.html',
'/src/js/app.js',
'/src/js/feed.js',
'/src/js/promise.js',
'/src/js/fetch.js',
'/src/js/material.min.js',
'/src/css/app.css',
'/src/css/feed.css',
'/src/images/main-image.jpg',
'https://fonts.googleapis.com/css?family=Roboto:400,700',
'https://fonts.googleapis.com/icon?family=Material+Icons',
'https://cdnjs.cloudflare.com/ajax/libs/material-design-lite/1.3.0/material.indigo-pink.min.css'
];
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(CACHE_STATIC_NAME)
.then(function (cache) {
cache.addAll(STATIC_FILES);
})
)
});
self.addEventListener('activate', function (event) {
event.waitUntil(
caches.keys()
.then(function (keyList) {
return Promise.all(keyList.map(function (key) {
if (key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {
return caches.delete(key);
}
}));
})
);
return self.clients.claim();
});
// function trimCache(cacheName, maxItems) {
// caches.open(cacheName)
// .then(function (cache) {
// return cache.keys()
// .then(function (keys) {
// if (keys.length > maxItems) {
// cache.delete(keys[0])
// .then(trimCache(cacheName, maxItems));
// }
// });
// })
// }
function isInArray(string, array) {
var cachePath;
if (string.indexOf(self.origin) === 0) { // request targets domain where we serve the page from (i.e. NOT a CDN)
console.log('matched ', string);
cachePath = string.substring(self.origin.length); // take the part of the URL AFTER the domain (e.g. after localhost:8080)
} else {
cachePath = string; // store the full request (for CDNs)
}
return array.indexOf(cachePath) > -1;
}
self.addEventListener('fetch', function (event) {
var url = 'https://httpbin.org/get';
if (event.request.url.indexOf(url) > -1) {
event.respondWith(
caches.open(CACHE_DYNAMIC_NAME)
.then(function (cache) {
return fetch(event.request)
.then(function (res) {
// trimCache(CACHE_DYNAMIC_NAME, 3);
cache.put(event.request, res.clone());
return res;
});
})
);
} else if (isInArray(event.request.url, STATIC_FILES)) {
event.respondWith(
caches.match(event.request)
);
} else {
event.respondWith(
caches.match(event.request)
.then(function (response) {
if (response) {
return response;
} else {
return fetch(event.request)
.then(function (res) {
return caches.open(CACHE_DYNAMIC_NAME)
.then(function (cache) {
// trimCache(CACHE_DYNAMIC_NAME, 3);
cache.put(event.request.url, res.clone());
return res;
})
})
.catch(function (err) {
return caches.open(CACHE_STATIC_NAME)
.then(function (cache) {
if (event.request.headers.get('accept').includes('text/html')) {
return cache.match('/offline.html');
}
});
});
}
})
);
}
});