Web技術でアプリ開発2016特集・第5弾は、プログレッシブウェブアプリ (Progressive Web Apps)をご紹介させていただきます。
はじめに
プログレッシブウェブアプリ(Progressive Web Apps)という言葉が初めて登場したのは2015年8月のAlex Russellによる記事Progressive Web Apps: Escaping Tabs Without Losing Our Soulです。当時オフラインやプッシュ通知など、モバイルウェブを飛躍的に進化させる画期的な機能が次々と追加されていた状況において、このムーブメントを呼称するための言葉が求められていました。Google社内でいくつもの候補が挙げられましたが、Service Workerの発案者としてAlex Russellが推したのが、この「プログレッシブウェブアプリ」でした。
また、当初Googleで始まったプログレッシブウェブアプリの動きではありましたが、先日のProgressive Web App Dev SummitではMicrosoft、Mozilla、Opera、Samsungの代表者も登壇するなど、各ブラウザベンダーとも足並みが揃いつつあります。これはもはやウェブ全体の動きと言うことができます。
若干古い情報になりますが、以降この (長い) 記事を読む以外に手早く情報を得るための方法として、下記を挙げておきます。
スマートフォン体験を一歩先へ – プログレッシブウェブアプリの作り方
プログレッシブウェブアプリとは
プログレッシブウェブアプリとは、最新の標準ウェブ技術を用いることで、オフラインやプッシュ通知といったこれまでよりも一次元上のユーザー体験をもたらすウェブアプリのことです。必ずしもデスクトップのウェブアプリや、ページ遷移を使ったサーバーサイドでレンダリングされるアプリを除外するものではありません。
理屈で説明するよりも、実際に体験した方が早いかと思います。いくつか具体例を挙げましょう。
※ 他にも様々なプログレッシブウェブアプリが既に存在していますが、pwa.rocksというサイトにいくつかまとまっていますので、ぜひご覧頂下さい。
実際に使ってみるとわかりますが、これらのアプリケーションにおいてこれまでにない特徴的な点がいくつかあります。
- ホーム画面に追加してワンタップで起動
- キャッシュを使ってオフライン、もしくは高速に動作
- プッシュ通知を送信
- 自動でログイン
プログレッシブとは何か
「プログレッシブ」という言葉自体は直訳すると「進歩的な」といった意味がありますが、ピンとこない方は多いと思います。
プログレッシブウェブアプリの「プログレッシブ」のひとつの意味は、ウェブページとして使い始めたサービスが、ホーム画面に追加し、オフラインで動作し、プッシュ通知を送り始めるといった流れの中で、「徐々にアプリへと変化していく」様を指しています。
もう一つ意味があります。
フロントエンドエンジニアであれば、プログレッシブ・エンハンスメントという言葉を聞いたことがあると思います。プログレッシブ・エンハンスメントとは、HTML5 時代に続々と新しい機能が登場する中で、最新の標準技術をまだ盛り込んでいないブラウザでも、情報を届けるといった目的をゴールに、必要最低限の機能をベースとして、可能であれば最新技術を使うことでよりリッチな体験を提供する、という設計コンセプトのことを言いました。(逆に、リッチな体験を提供できるブラウザをベースに、最新の技術が使えないブラウザでも最低限の目的は達成できるように設計する手法をグレースフル・デグラデーションと呼びました)
プログレッシブウェブアプリも同様に捉えることができます。つまり、必要最低限の機能を提供できる状態をベースとし、オフラインやプッシュ通知などをプログレッシブな (進歩的な) 機能として提供する、というものです。
これは言い方を変えれば、対応していないブラウザがあるからといって対応を待つべきではなく、それを前提としてウェブを前に進めていこう、というメッセージでもあるのです。実際、プログレッシブウェブアプリ対応への副作用として、複数の機能に未対応のブラウザでもコンバージョンレートが改善したという事例も出ています。
アプリとウェブのギャップ
Web2.0の時代を経てコンピューターの世界はネイティブアプリケーションからウェブアプリケーションの時代へと変化を遂げました。地図アプリケーションを思い出して下さい。その昔CD-ROMからせっせとインストールする必要のあった地図が、URLひとつでブラウザから見られるようになったのです。他にも例を挙げればキリがありません。
ウェブはその後もHTML5やその周辺技術の発展により大きく進化していきました。しかしその恩恵を満足の行く形で受けられたのはデスクトップでの話。モバイル・スマートフォンの時代となり、ユースケースが変化していく中で、スマートフォン上で快適に目的を達成するためには、ウェブブラウザは力不足と言われるようになってしまいました。その象徴的な出来事の一つがFacebookのCEOマーク・ザッカーバーグによるこの発言でした。
Disrupt:ザッカーバーグ、「HTML5を過大評価したのは、われわれ最大の失敗」と認める | TechCrunch Japan
これはひとえに標準化を必要とするウェブという環境の特性ゆえと言わざるを得ません。標準化を進めつつ実装し、浸透を待たなければならなかったウェブと異なり、ネイティブアプリはモバイルに最適化した仕様や機能を次々と取り込み、技術革新を起こしていきました。
しかしウェブには、ネイティブアプリでは実現できない特性も持ち合わせています。それがSLICEと呼ばれるものです。
- Secure (安全)
- Linkable (リンク可能)
- Indexable (インデックス可能)
- Composable (再構成可能)
- Ephemeral (一時的な利用)
ウェブもネイティブも、お互いの短所を埋める進化を遂げていく中で、ウェブが着実に前進していることの現れが、プログレッシブウェブアプリと言えるでしょう。一度はHTML5化を断念したFacebookがプッシュ通知を採用したのはその最たる例です。
Facebook、Googleと協力してモバイルウェブユーザーにプッシュ通知を送信
プログレッシブウェブアプリはWeb 2.0、HTML5と続いてきた大きな流れの、次の一歩と捉えて間違いありません。
プログレッシブウェブアプリを構成する機能
プログレッシブウェブアプリはそれ自体が機能を表しているわけではなく、様々な先進的機能をまとめたひとつのムーブメントです。具体的にどんな機能があるのか、ひとつひとつ見ていきましょう。
- HTTPS
- Web App Manifest
- Service Worker
- Cache API
- Web Push / Push Notifications
- Background Sync
- Web Budget API
- Credential Management API
- Payment Request API
HTTPS
プログレッシブウェブアプリに対応する上で大前提になるのがサイトの完全なHTTPS対応です。ログイン画面や商品購入画面だけではなく、すべてのページをHTTPS化する必要があります。
一般的に、HTTPSに対応することでウェブサイトは
- ウェブサイトの一意性を守るアイデンティティ(Identity)
- サーバー・ブラウザ間の通信を秘匿するコンフィデンシャリティ(Confidentiality)
- 送受信されるデータを改ざんさせないインテグリティ(Integrity)
の3つを手に入れることができます。
プログレッシブウェブアプリ対応において重要なのが、以下でご紹介する機能のほとんどがHTTPS上でのみ動作 (セキュアコンテキスト)するという点です。こういった機能はPowerful Features と呼ばれ、以前はHTTP上でも動作したgetUserMediaやgeolocationといったAPIもそこに加えられ、最近HTTPSのみで動作する変更が加えられました。
HTTPSに対応することなしにプログレッシブウェブアプリを実現することは不可能ですので、対応を検討する方はここからスタートして下さい。(ちなみにChromeでは、localhostとfile://もセキュアコンテキストに含まれますので、開発時は少し役に立つかもしれません)
Web App Manifest
ネイティブアプリとウェブサイトを比較した時に、最も大きく異なるのがアクセスのしやすさです。もちろん、アプリのようにインストールしなくても、リンクを踏むだけでアクセスできるという意味で、ウェブは手に届きやすい存在です。しかしスマートフォンの場合、デスクトップのようにキーボードで手軽に検索ができるわけではありません。ホーム画面からタップひとつで起動できるだけで、ユーザーにとっての身近さは大きく改善します。
そこで登場するのがWeb App Manifestです。Web App Manifestを使うことで、該当ページに関する様々な情報を定義することができます。
最近はlang
やscope
、screenshots
といった項目も新しく追加されていますので、その辺りも注目して下さい。
これらの情報を踏まえ、「ホーム画面に追加」をすると、ウェブアプリはホーム画面に鎮座することができるようになります。しかし、この機能がiOSでも古くからあるmeta
タグを使ったものに近いと思われた方もいるかもしれません。
Chromeの場合、このWeb App Manifestといくつかの条件が加わることで、ブラウザが自動的にホーム画面に追加を促してくれる、という点が大きく異なります。詳しくは下記の記事を参照して下さい。
Web App ManifestでWebアプリをインストール可能に – Qiita
Service Worker
Service Workerはプログレッシブウェブアプリを構成する機能の中でも、コンセプトを成立させた一番の立役者で、中核を成すものです。
従来のウェブにおいて、ページはサーバーから送られたHTMLとそれに付随するリソースで構成される機能がすべてであり、ページが閉じられると同時に忘れ去られる存在でした。Service Workerは、ページとは独立して動作するスクリプトを、ページを閉じた後でもブラウザ上で動かし続けることができる機能です。このService Workerを活用することで、様々な可能性が開かれます。
オフライン:Service Workerはページが送信するあらゆるリクエストを横取りすることができます。実質的なローカルプロキシーとなるため、強力なキャッシュを活用すれば、ネットワークに接続していない状態でもレスポンスを返すことができるようになります。詳しくはCache APIの項で説明します。
プッシュ通知:Service Workerはページが閉じた状態でも、また、ブラウザによっては起動してない状態であっても、イベントを検知することができます。そのため、外部サーバーが送信したプッシュリクエストをイベントとして受け取り、何かしらのアクションを起こすことができるのです。詳しくはWeb Push / Web Notificationsの項で説明します。
それでは、Service Workerの使い方を雰囲気だけ見てみましょう。
Service Workerは独立したJavaScriptとして動作するため、ファイルも分けて記述する必要があります。例えばページ上で動作するスクリプトapp.js
が、service-worker.js
というService Workerを使いたいとします。
1 |
navigator.serviceWorker.register('service-worker.js', {scope: './'}); |
これだけでService Workerが登録されます。簡単ですね。
service-worker.js
の中身はイベント・ドリブンのスクリプトになります。例えば下記のように記述しておけば、あらゆるリクエストをService Workerが拾い上げて、サーバーにリクエストを転送することなく”Hello World!”とレスポンスを返します。
1 2 3 |
self.addEventListener('fetch', function(event) { event.respondWith('Hello World!'); }); |
Service Workerは実際いつ動作を開始するのでしょう?影響範囲はどこまででしょう?こういった細かい話になるとセキュリティへの懸念も出てくるため、若干複雑な内容を理解する必要が出てきます。scopeとライフサイクルについては抑えておきましょう。
scope
Service Workerが登録されると、以後半永久的にスクリプトを動かし続けることになります。仮にService Workerがドメインの範囲内すべてに影響をおよぼすことができるとすると、ブログなどのユーザーの権限がパスで区切られたサービスでは、自分の預かり知らないスクリプトが埋め込まれてしまう可能性がでてきます。そこでService Workerでは、scopeという概念を使って影響範囲を制限します。詳しくはこちらの記事が参考になります。
ServiceWorkerのスコープとページコントロールについて – Qiita
ライフサイクル
Service Workerが登録されたあと、スクリプトを手動で更新したい場合もあります。新しいスクリプトはすぐに有効になるのでしょうか?その場合古いスクリプトはどうなってしまうのでしょうか?Service Workerは放っておいたら永久に更新されないのでしょうか?
Service Workerのライフサイクルを正しく理解することはとても重要です。下記の記事をぜひご覧ください。
- Service Workerの紹介: Service Workerの使い方 – HTML5 Rocks (一部古い内容が含まれます)
- 中級者向け Service Worker Tutorial | blog.jxck.io
- Service WorkerのRegistration
デバッグ
Chromeではプログレッシブウェブアプリのデバッグをしやすくするために、最近DevToolsにResourcesパネルの代わりに Applicationパネルが追加されました。Manifestや各種ストレージなどを、ひとつのパネルからまとめて確認することができます。中でもService Worker専用の項目では、現在起動中のスクリプトだけでなく
- Offline – オフラインのエミュレーションボタン
- Update on reload – リロードするだけでService Workerを更新
- Bypass for network – ネットワークリクエストの横取りを一時的に停止
といったオプションが利用できます。ここからService Workerの単独のDevToolsを開いてデバッグを行ってください。
少し未来の話
今後利用可能になるService Worker周辺の重要な機能についても少し触れておきましょう。
Link rel=serviceworker Chrome 54からOrigin Trial (特定のドメインに実験的にホワイトリストで機能を提供) ベースでService Workerの登録が linkタグやHTTPヘッダーで行えるようになりました。将来的にこれが利用できるようになれば、JavaScriptを記述することなく、linkタグやHTTPヘッダーのみでService Workerを登録できるようになります。
1 |
<link rel="serviceworker" href="/js/sw.js" scope="/"> |
https://www.chromestatus.com/feature/5682681044008960
Foreign fetch また、Web Fontsのような複数ドメインから共有されるリソースをCDN経由で提供するサーバーのリソースも、現在は利用する側のService Workerがハンドリングすることが可能ですが、これをCDN側のService Workerがハンドリングできれば効率が向上します。これを可能にする機能はForeign Fetchと呼ばれ、Chrome 54からOrigin Trialベースで提供が開始されています。
https://www.chromestatus.com/feature/5684130679357440
その他、Service Workerのさらに詳しい情報はHTML5 Conference 2016で行われたChromeエンジニアの保呂さんのスライドが参考になります。
Service Worker Deep Dive
Cache API
先程説明したように、Service Workerを使ってあらゆるリクエストを横取りすることで、オフラインの機能が実現可能になります。そこで強力なキャッシュ機能を提供するのがCache APIです。
Cache APIは取得されたリソースだけでなく、リクエストやレスポンスの内容も含めて保存されます。下記の例では、キャッシュが残っていればキャッシュされていたレスポンスを、なければサーバーにリクエストを送って、返ってきたレスポンスを返します (キャッシュに保存はしません)。
1 2 3 4 5 6 7 |
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request); }) ); }); |
Cache APIを使った実例について、詳しくはこちらの記事が参考になります。
ServiceWorkerとCache APIを使ってオフラインでも動くWebアプリを作る – Qiita
なお、Cache APIを扱う上で fetch()も避けては通れませんが、ググらビリティの低さから、まとまった資料が見つかりにくいという問題があります。こちらのページがよくまっていたのでリンクしておきます。
お疲れさまXMLHttpRequest、こんにちはfetch – Qiita
ここまで述べてきたように便利なService Worker + Cache APIですが、多くの人が似たようなコードを書くであろうことは容易に想像ができます。そこで便利なライブラリがいくつかあります。
sw-toolbox
リソースをキャッシュする際に必要になるのが、リソースの優先順位の判定です。例えば共通で利用されるJavaScriptファイルは、更新がない限りネットワークにリクエストを送るまでもなく常にキャッシュから読み込むことでスピードを優先したいと思うはずです。逆に例えば、天気アプリであれば、開く度に最新の天気予報を見たいですが、オフラインだった場合はオフラインだと言われるよりは、キャッシュからでもその後の天気予報を見たいと思うはずです。
このように、同じキャッシュを活用するでも、場面によってキャッシュを優先したいのか、ネットワークを優先したいのかが異なってきます。そこで便利なのがsw-toolboxです。
1 |
toolbox.router.get('/myapp/index.html', toolbox.networkFirst); |
sw-toolboxではネットワークを優先するnetworkFirst、キャッシュを優先するcacheFirstの他にもfastest、networkOnly、cacheOnlyといった様々なオプションが利用可能です。
https://googlechrome.github.io/sw-toolbox/docs/master/index.html
sw-precache
Service Workerを使ったキャッシュ機能以前にオフラインを実現する機能として登場したApplication Cacheは、ページを初めて開いた時点で、予め定義されたリソースをすべてキャッシュするものでした。Service Worker + Cache APIにおいても同様のニーズはあるでしょう。例えばアプリの外枠や必要最低限のCSSやJavaScriptファイルを予めロードしておくことで、コンテンツ部分のみをダイナミックにロードすればよいというApplication Shellというアーキテクチャを利用する場合です。
これを実現するのに便利なライブラリがsw-precacheです。Gulp やGruntといったタスクランナーを使うことで、リストされたリソースを最初のロード時点でキャッシュしてしまうJavaScript のコードを出力してくれます。あとはこれをService Workerとしてロードすればよいだけです。また、リソースの更新もハッシュを使って自動的に管理してくれるので、どのファイルを書き換えたからリストを変更しなければならない、といったようなマニュアル作業が不要になります。
1 2 3 4 5 6 7 8 9 10 |
gulp.task('generate-service-worker', function(callback) { var path = require('path'); var swPrecache = require('sw-precache'); var rootDir = 'app'; swPrecache.write(path.join(rootDir, 'service-worker.js'), { staticFileGlobs: [rootDir + '/**/*.{js,html,css,png,jpg,gif,svg,eot,ttf,woff}'], stripPrefix: rootDir }, callback); }); |
なお、最近追加されたruntimeCachingという機能を使うことで、sw-toolbox の便利な機能を宣言的に記述することもできます。
sw-appcache-behavior
もし既にApplication Cacheを使っていて、Service Workerに移行を検討している場合は、sw-helpersライブラリの一部であるsw-appcache-behaviorが便利です。既存の Manifestファイルをそのまま利用してService Workerによるオフラインを実現することができます。
sw-offline-google-analytics
オフラインのウェブサイトがうまく作れたとしても、ユーザーがどのアクセスページにアクセスし、どのように利用しているかが分からなければ、本当に役に立っているのかどうかを知ることはできません。これもsw-helpersの一部であるsw-offline-google-analyticsを使えば、オフライン中のアクセス解析をデータベース上に保存しておいて、オンラインになったタイミングでサーバーに送信してGoogle Analyticsでアクセス解析結果を閲覧することができるようになります。
Web Push / Web Notifications
プッシュ通知は、プログレッシブウェブアプリを語る際、これまでできなかったことの中で、技術者ではなくても効果が理解できるという意味では、最も分かりやすい機能です。実際に既に多くのサービスがこれを利用しています。例えば Facebook です。モバイルウェブ版のFacebookにアクセスしてみると、すぐにそのことに気付くでしょう。
1 2 3 4 5 6 7 |
navigator.serviceWorker.ready().then(function(sw) { sw.pushManager.subscribe({userVisibleOnly: true}) .then(function(sub) { // パーミッション取得後 sendSubToServer(sub); // Send subscription to the server }); }); |
1 2 3 4 5 6 7 8 |
self.addEventListener('push', function(event) { event.waitUntil( self.registration.showNotification(title, { body: body, icon: icon, tag: tag, actions: [{action: 'remindMe', title: 'REMIND ME'}] } }); }); |
具体的な実装方法は若干複雑ですので、サードパーティーのサービスを使うのも手かもしれません。自分で実装する場合は、こちらのドキュメントがよくまとまっています。
Web Pushでブラウザにプッシュ通知を送ってみる – Qiita
Firefox (Developer Edition)でW3C Push APIを使ってみる – Qiita
Chromeの場合、以前はGCM (Google Cloud Messaging、もしくはFirebase Cloud Messaging)が必須でしたが、現在は標準の Web Pushが利用できます。詳しくは下記をご覧下さい。
ChromeでW3C Push APIを使ってみた – Qiita
Web Pushについては、node.js 向けのライブラリも提供されています。
Background Sync
妙なタイミングでネットワークがオフラインになってしまったために、リクエストが送れなかったという経験は誰しもあると思います。そんな時にリクエストをキューしておいて、オンラインになったタイミングで送ってくれるとありがたいですよね。それを実現してくれるのが、Background Syncです。
ServiceWorkerのBackground Syncでオンライン復帰時にデータ送信 – Qiita
Web Budget API
現状Service Workerはプッシュを受信すると、通知を出さなければなりません。これはブラウザリソースの無駄遣いを防ぐためです。しかし、例えばバックグラウンドで購読しているフィードを更新するといったユースケースは容易に思いつきます。
そこで検討されているのがWeb Budget APIという機能です。これを使うことで、ページは与えられたバジェットを適切に配分してバックグラウンドでの処理を行うことが可能になりますので、リソースの無駄遣いを防ぎつつ必要な機能を実現できるようになるはずです。
https://www.chromestatus.com/features/5691190548627456
Credential Management API
Credential Management APIはユーザーのクレデンシャル (認証情報) を制御するためのAPIです。
これを使うことにより
- id / passwordをブラウザに覚えさせる
- ソーシャルログインの選択肢をブラウザに覚えさせる
- ログイン時にアカウント選択ダイアログを提供する
- ユーザーを自動的にログインさせる
といったことが可能になります。
典型的なユースケースとしては、ニュースサイトやコマースサイトといった、必ずしもログインしなくてもよい、しかし提供者側としてはログインしてもらいたいサービスにおいて役立ちます。実際に利用されている例としてはAliExpressの実装が非常によくできていますので、是非一度お試し下さい。
実装は基本的にフロントエンド側だけですので、比較的気軽に導入することができます。
Payment Request API
Payment Request APIは支払い時のチェックアウトに必要なフォームを置き換えるUIを提供します。Chromeではバージョン 53から利用できるようになる予定です。
Payment Request APIが提供するのは、ユーザーインターフェースです。ブラウザのAutofill機能を活用することで、クレジットカードの情報や住所などを、すばやく簡単に入力できるようになります。
支払い方法は現状クレジットカードのみですが、まもなくAndroid Payも利用可能 (残念ながら当初日本は対象外) になる予定です。将来的には、さらに様々な支払い方法をオープンかつ柔軟に組み込めるようになっていく予定です。
その他の機能
プログレッシブウェブアプリを語る上で数えるべき機能や考え方は他にもいくつかありますが、キリがありませんのでこの辺にしておきましょう。
- Web Bluetooth
- Web MIDI
- Web Animation
興味のある方は、この辺りもぜひ抑えておいていただければと思います。
Lighthouse
あるウェブアプリを指してそれがプログレッシブウェブアプリかどうかを議論するのはあまり意味のないことですが、それを示す指標を得ることはできます。それがLighthouseというChrome拡張機能です。ウェブサイトを開いた状態でこの拡張を起動すると、プログレッシブウェブアプリの対応度合いをスコアで示してくれます。
Chrome拡張機能としてだけではなく、node moduleとしても利用が可能です。
https://github.com/GoogleChrome/lighthouse
Lighthouseはまだアルファ版の段階のため、評価できない機能が多数ありますが、今後対応していく予定です。
リソース
プログレッシブウェブアプリに興味を持たれた方で、最新情報を追いかけたいという方は下記のページをご覧下さい。RSSフィードも配信しています。
https://developers.google.com/web/updates/
資料が英語で読めない?英語が分かる人はぜひ、翻訳にご協力下さい。
最後に
プログレッシブウェブアプリについて長々と書いてきましたが、重要なのは決してこれらの機能すべてに対応することではありません。自分が提供するサービスがユーザーに最大の価値を提供するために必要であれば、ネイティブアプリを提供するべき場合もあるでしょう。オフライン機能だけが必要な場合もあるかもしれません。これらの機能をうまく組み合わせて、ユーザーが本当に求めている価値を提供できるアプリを開発していただければと思います。