プログレッシブウェブアプリについて~デモアプリで効果を検証~

ニュース技術の斉藤です

07/13(金)に社内の数名でGoogleforMobileに参加してきました
https://events.withgoogle.com/google-for-mobile/program/


多くのセッションがあり、全体としては先日のGoogleI/Oで発表されたFireBaseについての内容が多く、GoogleのFirebaseに対する強い意思を感じられました

この記事ではGoogleforMobileでも2セッション発表がありました、プログレッシブウェブアプリの紹介をします

プログレッシブウェブアプリについて

プログレッシブウェブアプリとはwebアプリとネイティブアプリ両方の良い部分を兼ね備えた、webアプリです
(教科書通りに実装すれば)特徴は下記の通りです


ホーム画面からの起動

ホーム画面に追加することで、あたかもネイティブアプリかのように起動できます


Push

ネイティブアプリのプッシュ通知をwebアプリに対して実現できます


ネットワーク接続に依存しない

ServiceWorkerのキャッシュを活用することにより、オフラインやネット環境が良くない場所でも高速で動作します


レスポンシブでアプリ感覚

AppShellモデルに基いて実装されるため、アプリ感覚で操作することができます


リンクで共有

あくまでもWebアプリなのでURLを使って簡単に共有でき、インストールの必要がありません


わざわざ教科書通りに実装すればと書いたのは、必要な部分だけを実装することが可能で、例えばホーム画面に追加したときの動作をリッチにしたいのでmanifest.jsonだけ登録するということができます
この必要な部分だけを実装できるのもプログレッシブウェブアプリのいいところだと個人的には思います


実装はmanifest.jsonとServiceWorkerを活用します


ServiceWorkerについて

バックグラウンドで実行するJavaScriptです
普段のJavaScriptとは違い、DOMに直接アクセスすることはできません
ただキャッシュやpushメッセージなど様々な機能を持ち合わせています
逆にさまざまなことができてしまうので、セキュリティ面からHTTPSでしか動作しません
ですのでプログレッシブウェブアプリもHTTPSで通信することが前提となります

今回はHome画面への追加とキャッシュについて、コードも交えながら、ご紹介します


Home画面からの起動をリッチにする

manifest.jsonを記述し、読み込んであげるだけで実現できます

コンテンツからの呼び出し
link rel="manifest" href="manifest.json

manifest.json
{
"name":"ExciteProgressiveWebApp",
"short_name":"Excite",
"icons":[{
"src":"https://s.eximg.jp/exnews/a/img/apple-touch-icon.png",
"sizes":"150x150",
"type":"image/png"
}],
"start_url":"/index.html"
}
nameとshort_nameの使い分けは
Home画面に追加した際のアイコン下に表示されるのがshort_name
スプラッシュ画面等に表示されるのがnameです

上記に加えて、下記を設定しておくと、よりネイティブアプリっぽくなります
"display":"standalone",
"background_color":"#111111",
"theme_color":"#111111",
displayは[fullscreen|standalone|minimal-ui|browser]の4つのモードがあり、
standaloneを設定しておくと、chromeのタブではなく独立して起動してくれるので、ネイティブアプリと差がなくなり、オススメです

manifest.jsonの詳しい仕様はこちら
https://www.w3.org/TR/appmanifest/

キャッシュを活用し、ネットワーク接続に依存しないサービスにする

実装については下記Google公式のページを参考にしております
https://codelabs.developers.google.com/codelabs/your-first-pwapp-ja/index.html

ServiceWorkerの登録

キャッシュの登録、呼び出しにはServiceWorkerを活用します
まずはブラウザにServiceWorkerを登録します

app.js(通常のコンテンツ側のjs)
if('serviceWorker' in navigator){
navigator.serviceWorker.register('/service-worker.js').then(function (registration){
console.log('ServiceWorker registration successful with scope:', registration.scope);
}).catch(function (err){
console.log('ServiceWorker registration failed:',err);
});
}
ポイントはservice-worker.jsの場所です
ここで読み込んだ位置の配下がServiceWorkerの有効スコープになるため,よく検討する必要があります

ServiceWorkerは4つのイベントを受け取ることができます
それぞれのイベントに対して、適したキャッシュの処理を実装していきます

table1.ServiceWorkerのイベント一覧
イベント名発火タイミング
installserviceWorker.registerが成功した直後
activateinstall後またはServiceWorkerが更新され、ページに対してコントール可能になった際
fetchページ内でネットワークリクエストが発生した際
messageメッセージを受け取った際

キャッシュの書き込み

ServiceWorkerがインストールされたタイミングで静的コンテンツをキャッシュしておきます

service-worker.js
var files = [
'/styles.css',
'/images/lep.png',
'/images/E1468374024477_1.jpg',
'/images/E1468542105874_1.jpg',
'/images/E1468631912161_1.jpg',
'/images/E1466132241582_1.jpg',
'/images/E1466132241582_2.jpg'
];

self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open_(cacheVersion).then(function(cache) {
console.log('[ServiceWorker] Caching app shell');
return cache.addAll(files);
}));
});
caches.openでキャッシュストレージを開き,addALLにキャッシュしたいファイルを一覧を渡して、完了です
注意としては1つでもキャッシュ生成に失敗すると、全ての生成が失敗します

キャッシュの更新(削除)

activateになったタイミングで前回のキャッシュversionと比べて、
変更があれば古いキャッシュを削除します

service-worker.js
self.addEventListener('activate',function(e) {
console.log('[ServiceWorker] Activate');
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
console.log('[ServiceWorker] Removing old cache',key);
if(key !== cacheVersion){
return caches.delete(key);
}
}));
})
);
});
caches.keysは現存する全てのkeyを配列で返却します
戦略的にkeyを分けている場合の削除処理はご注意ください

キャッシュからの読み込み


service-worker.js
self.addEventListener('fetch',function(e){
console.log('[ServiceWorker]Fetch',e.request.url);
e.respondWith(
caches.match(e.request).then(function(response){
return response | | fetch(e.request);
})
);
});
caches.matchでcache内を探し、あればそれを返却し、なければfetchメソッドで探しにいきます
厳格に実装するのであればfetchメソッドの結果も確認しておくべきでしょう

これでキャッシュの追加、読み込み、更新ができるようになりました

キャッシュの効果を確認する

数値でみる

f0364156_20253358.png
図1.ServiceWorkerでのキャッシュなし

f0364156_20253359.png
図2.ServiceWorkerでのキャッシュあり

300KB程度の画像ですが、通信が発生しないので処理時間は1/30程度に減少しているのが、確認できます!

オフラインでの動作


実装は簡単ですが、威力は絶大です!

※注意
参考にしているGoogle公式チュートリアルにもありますが、
このまま実際のサービスに登録するのは危険です
キャッシュ戦略を考え、適したものに書き換えてください


まとめ

webアプリとネイティブアプリの差はどんどん小さくなっていくと思います

プログレッシブウェブアプリは今後も様々なサービスで対応がすすみ、webアプリはネイティブアプリに近づいていくでしょう

ネイティブアプリはインストールや共有のしづらさがwebアプリに劣っていましたが、Instant Appsの登場でインストール、共有がURL1つで行えるようになりました
(Instant Appsをネイティブアプリとして捉えるかどうかは議論の余地があります)
これからは各サービスに適した方法を選択し、なにをユーザーに届けるかが、より大事になっていくのでないかと思います

参考

https://codelabs.developers.google.com/codelabs/your-first-pwapp-ja/index.html
https://www.w3.org/TR/appmanifest/
https://www.w3.org/TR/service-workers/


エンジニア募集

エキサイトではエンジニアとして一緒に働いてくださる方を新卒採用と中途採用で募集しています。
詳しくは、こちらの採用情報ページをご覧ください。
一緒にプログレッシブウェブアプリのサービス導入を目指しましょう!

[PR]
by ex-engineer | 2016-08-03 18:00