エンジニアHubPowered by エン転職

若手Webエンジニアのための情報メディア

PWAの作り方をサクッと学ぶ - 「ホーム画面に追加」「キャッシュ操作」「プッシュ通知」の実装

PWAをテーマにしたコミュニティ「PWA Night」を運営する菅家大地さんが、既存のWebアプリをPWA化する簡単な実装方法を解説します。

image_PWA

はじめまして、菅家(@kan_dai)といいます。普段は株式会社TAMという会社でフロントエンドをメインに、クライアントのWebサイト制作やWebサービスの開発をしています。PWA(Progressive Web Apps)をテーマにしたコミュニティ「PWA Night」の運営もしています。

さて、2018年ごろからPWAという言葉を聞く機会が多くなってきました。2019年現在、毎月コンスタントにPWAに関する仕事の相談を受けるようになっており、PWAへの関心の高まりを感じます。日本経済新聞やスマートフォン版Yahoo! Japanといった有名サービスでの導入事例も確実に増えつつあります。

現時点ではPWAの導入に際して、プッシュ通知やインストールを促すミニバナーの表示など、一部機能がiOSでは使えず、iPhoneのシェアが高い日本では“様子見”といった印象もあります。しかし、iOSの対応が進むことによって、PWAの事例がどんどん増えていく*1のではないかと予想しており、今後、PWAとそれに付随する知識やスキルが重要になっていくはずです。

これらを知るためには、実際に作ってみるのが一番良いと思います。本稿では、PWAでサンプルアプリを開発するための簡単な実装方法と、今後の動向予測をお伝えしたいと思います。

サンプル開発を通して学ぶPWA

「何をもってPWAといえるのか」という定義は個人的には難しい点だと考えています。サンプルアプリを作る前に、Googleが提供しているPWAのチェックリストを見てみましょう。PWAに必要なベースラインの項目と、さらに一歩前進するための項目がたくさん並んでいます。

Progressive Web App Checklist  |  Google Developers

長大なリストですが、全てやらないといけないわけではありません。Progressive(プログレッシブ)という言葉の通り、要素技術を段階的に導入できるのがPWAの良いところだと考えています。

既存のWebアプリをPWA化してみよう

上記の「段階的に導入できる」という特長を生かし、PWAを学ぶには、既存のWebアプリをPWA化するのが一番簡単な方法だと考えています。

今回は、筆者が以前作った「10秒間で何回タップできるか」で遊ぶゲーム「TAP10」を題材に、PWA化していく過程を紹介します。本記事では、既存のWebアプリを「インストール可能にする」「Service Workerを使ってキャッシュする」「Webプッシュの導入」までをターゲットにします。

image

コードの詳しい説明は割愛させていただきますが、このWebアプリは以下のような3ファイルで構成された簡単なものになっています。

index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TAP10</title>
<link rel="stylesheet" type="text/css" href="app.css">
</head>
<body>

<section class="container">
  <h1 class="title">10秒で何回タップできるか</h1>
  <button id="js-tapBtn" class="btn-tap" disabled>
    <span id="js-count"></span><br>タップ!!
  </button>
  <div class="time">残り時間 <span id="js-time"></span> 秒</div>
  <button id="js-startBtn" class="btn">START</button>
</section>

<script src="app.js"></script>
</body>
</html>
app.css
@charset 'UTF-8';

html {
  font-family: sans-serif;
  touch-action: manipulation;
}
.container {
  padding: 15px;
  max-width: 400px;
  margin: 0 auto;
  text-align: center;
  font-size: 16px;
}
.title {
  font-size: 20px;
  margin: 10px 0 20px;
}
.btn-tap {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  color: #fff;
  background-color: #a0ce00;
  border: none;
  outline: none;
  font-size: 20px;
  margin-bottom: 30px;
  cursor: pointer;
}
.btn-tap:disabled {
  background-color: #ddd;
  color: #aaa;
}
.btn-tap span {
  font-size: 60px;
}
.btn {
  display: inline-block;
  margin: 30px 0 0;
  padding: 15px 60px;
  border: none;
  border-radius: 10px;
  background-color: #54bbc1;
  font-size: 18px;
  color: #fff;
  font-weight: bold;
  cursor: pointer;
}
.time span {
  display: inline-block;
  width: 50px;
  font-size: 20px;
  font-weight: bold;
}
app.js
// 変数定義
let isPlaying = false
let tapCount, time = 0
const tapBtn    = document.getElementById('js-tapBtn')
const startBtn  = document.getElementById('js-startBtn')
const countText = document.getElementById('js-count')
const timeText  = document.getElementById('js-time')

// ゲームの初期値設定
const setGame = () => {
  tapCount = 0
  time = 10000
  countText.innerText = tapCount
  timeText.innerHTML = time / 1000
}
setGame()

// タップした時にカウントを増やす
tapBtn.addEventListener('click', () => {
  if (!isPlaying) return false
  tapCount++
  countText.innerText = tapCount
})

// STARTボタンを押してゲームをスタートさせる
startBtn.addEventListener('click', () => {
  setGame()
  isPlaying = true
  tapBtn.disabled = false
  startBtn.style.display = 'none'

  const timer = setInterval( () => {
    time -= 10
    timeText.innerHTML = (time / 1000).toFixed(2)

    if (time === 0) {
      clearInterval(timer)
      isPlaying = false
      startBtn.style.display = 'inline-block'
      startBtn.innerText = 'もう一回'
    }
  }, 10)
})

PWA化の第一歩! インストール可能にする

スマートフォンの「ホーム画面に追加」(インストール)できるようになる機能をAdd to Home Screen、通称A2HSといいます。個人的にはこの機能こそが一番PWAらしい機能であり、PWA化への第一歩かと思っています。

「ホーム画面に追加」をすると以下のことができるようになります。

  • アドレスバーなどを表示しないネイティブアプリのようなUIを実現できる(以下画像左)
  • アプリの切り替え画面でアプリとして認識される(以下画像中央)
  • アプリのドロワーやアプリの管理に追加される(以下画像右)

image

実装方法はiOSとAndroidで違いがあるので、それぞれ説明します。ただし、iOSの場合、「ホーム画面に追加」ができるのはSafariだけなのでご注意ください。

iOSの場合はHTMLのhead内に以下のような記述を追記します。これだけです。

<!-- アドレスバー等のブラウザのUIを非表示 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- default(Safariと同じ) / black(黒) / black-translucent(ステータスバーをコンテンツに含める) -->
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- ホーム画面に表示されるアプリ名 -->
<meta name="apple-mobile-web-app-title" content="TAP10">
<!-- ホーム画面に表示されるアプリアイコン -->
<link rel="apple-touch-icon" href="images/icons/icon-152x152.png">

Androidの場合は、ウェブアプリマニフェストと呼ばれるjsonファイルを読み込み、Service Workerを登録することで実装できます。Service Workerについてはこの後に詳しく説明します。

また、Service WorkerはHTTPSの環境でしか動かないため(例外的にlocalhostでは稼働します)、「HTTPS環境」でのサイト配信が必要になります。

HTMLファイルに、ウェブアプリマニフェストの読み込みとService Workerを登録する記述を記載します。

<!-- ウェブアプリマニフェストの読み込み -->
<link rel="manifest" href="manifest.json">
<!-- ServiceWorkerの登録 -->
<script>
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('sw.js')
      .then((reg) => {
        console.log('Service worker registered.', reg);
      });
}
</script>

manifest.jsonの中身は以下のような記述になっています。この中でアプリの名前やホーム画面用のアイコン、スプラッシュスクリーンの背景色などを設定しています。

{
  "name":"TAP10",
  "short_name":"TAP10",
  "icons": [{
      "src": "/icons/icon-192x192.png",
      "sizes":"192x192",
      "type": "image/png"
    }, {
      "src": "/icons/icon-512x512.png",
      "sizes":"512x512",
      "type": "image/png"
    }],
  "start_url": "/index.html?utm_source=homescreen",
  "display": "standalone",
  "background_color": "#FFFFFF",
  "theme_color": "#FFFFFF"
}

アイコンにはいろいろなサイズの画像を設定できますが、少なくとも192x192pxのアイコンと512x512pxのアイコンを用意する必要があります。192pxのアイコンがあれば、最も大きなAndroid端末でもアイコンが適切に使用されます。

Add to Home Screen  |  Web Fundamentals  |  Google Developers

image

この中の display の項目で standalone を設定することで、ホーム画面から起動した時に、ブラウザのURLバーなどが表示されないネイティブアプリのようなUIで起動できます。

詳しい設定や他の設定項目は、MDN(Mozilla Developer Network)に詳しく記載されています。実装時の参考にしていただければと思います。

プログレッシブウェブアプリ | MDN

sw.js というファイルにService Workerの処理を書いていきますが、インストール可能にするだけであれば記述は空でも大丈夫です。

ただ、AndroidでMini-infobarと呼ばれるインストールを促すバナーを表示するためには fetch のイベントを登録する必要があります。最低限、以下のような記述があればOKです。

self.addEventListener('fetch', function(e) {
  // ここは空でもOK
})

ここまで実装すると、Mini-infobarが表示されてインストールできるようになります(メニューからもインストール可能です)。iOSの場合はこの機能が提供されていないため、ユーザー自身がメニューを表示して、ホーム画面に追加を行う必要があります。

image
左:Android / 右:iOS

しかし、突然バナーが表示されてもインストールしないというユーザーが大半でしょう。2019年8月開催の「PWA Night vol.7」で紹介していただいた「いこレポ」(アクトインディ運営)の例では、beforeInstallprompt というイベントを使うことで、インストールを促すポップアップを表示するというUIを実現していました。この実装についてはブログでも詳しく紹介されています。

workbox を導入してServiceWorkerによるキャッシュを実装した話 - アクトインディ開発者ブログ

また、将来的にGoogle Chromeでは、ホーム画面に追加可能なPWAの場合、オムニボックスと呼ばれるURLバーの部分にアイコンを表示する仕様に変わるという予測もあります。PC版のChromeでは既にそうなっています。(下図参照)

image
いこレポのポップアップ(参照:https://speakerdeck.com/hero/ikorehotefalseworkboxdao-ru-shi-li-at-pwa-night-number-7

Service Workerを使ったキャッシュで処理速度アップ

さて、PWAの話で必ずといっていいほど出てくる名であるService Workerですが、これはブラウザがWebページとは別にバックグラウンドで実行するスクリプトのことです。よく話題に出るPWAによるプッシュ通知やキャッシュ操作をする機能も、Service Workerで実現されており、PWAのコアの技術要素といってもいいでしょう。

今回のサンプルでは、キャッシュを活用したパフォーマンスの向上やオフライン対応を実装してみようと思います。キャッシュのコントロールはService Workerのスクリプト内に手動で書いていくこともできますが、少々複雑な作業になります。そのため、実際の案件では簡単な記述で最適なService Workerのコードを生成してくれるGoogle製のライブラリ「Workbox」を使うことが多いです。前項で説明した「いこレポ」の機能実装にもWorkboxが活用されています。

Workbox  |  Google Developers

Workboxを使ったコードで解説していきます。例えば、今回使用している「index.html」「app.css」「app.js」のファイルをキャッシュする場合は以下のように記述します。

// ファイブラリのインポート
importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js')

// ファイルのキャッシュ
workbox.precaching.precacheAndRoute([
  {
    url: '/index.html',
    revision: '12345'
  },
  {
    url: '/app.css',
    revision: '12345'
  },
  { 
 url: '/app.js',
    revision: '12345'
  },
])

ファイル名とリビジョンを指定しています。リビジョンを指定することで、キャッシュのバージョン管理ができるようになります。

ファイルの内容が変わった時には、リビジョンの値を変更すれば、従前のキャッシュを破棄して新しいファイルを取得・キャッシュしてくれます。

また、リビジョンの更新は手動ではなく、Workboxのページの中でも紹介されているリビジョン付きでファイルを出力する任意のツールを使用することができます。

image
キャッシュされたファイル

image
キャッシュされたファイルをService Workerを通して取得

ファイルがService Workerを通してキャッシュから取得されているのが分かるかと思います。ネットワークへの接続が低減し、表示のスピードアップなどパフォーマンスの向上につながります。

プリキャッシュとランタイムキャッシュを使い分ける

上記のキャッシュは「プリキャッシュ」と呼ばれ、Service Workerのインストール時にキャッシュされます。App Shellと呼ばれるアプリケーションの主要なリソースや、長期間キャッシュできることが分かっているアセットに最適で、リビジョンで管理できます。

プリキャッシュとは別に「ランタイムキャッシュ」と呼ばれるキャッシュがあります。これは指定したURLにリクエストがあった時のみキャッシュが実行されるものです。ランタイムキャッシュは、キャッシュの有効期限やキャッシュ戦略をリソースに合わせて設定することができます。

キャッシュ戦略とは、ファイルを「キャッシュから取得するのか」「ネットワークから取得するのか」といったファイルの取得パターンのことです。例えば、「頻繁に更新されるコンテンツだから常に最新の状態を取得したい」という情報であれば、キャッシュからの取得には向きません。この場合、「ネットワーク優先」で設定したりします。

キャッシュしたいコンテンツの特性に合わせて、プリキャッシュとランタイムキャッシュを使い分けるようにしましょう。

キャッシュを使ってオフラインでも動かせる

PWAといえば、「オフラインでもアプリケーションを動かすことができる」という点もよく語られます。

そもそもなぜオフラインでも動くのかというと、必要なリソースを端末にキャッシュすることでネットワークにアクセスする必要がなくなるためです。例えば、前項のService Workerを実装することで、このアプリに必要なリソースはユーザーのデバイスにキャッシュされ、オフラインでもアプリが動きます。

image
必要なファイルはキャッシュされているのでオフラインモードでも動作している

キャッシュは用法・用量を守って正しく使う

「パフォーマンスも向上し、オフラインでも動くのであれば、全てキャッシュすればいい」と思うかもしれません。しかし、実は“使いどころ”を考慮することが重要です。

まず、キャッシュはブラウザによって最大値が決められているため(Safariは50MB未満など)、何も考えずにキャッシュしているとすぐに容量がいっぱいになってしまいます。

また、更新されるはずだったファイルが更新されずに不具合が起きてしまう可能性もあり、キャッシュの使い方を間違えるとリスクがあるのも事実です。ユーザーの端末に溜まるものなので、開発者側から削除できないことも認識しておくべき要素です。

キャッシュは、メリット・デメリット・リスクなどを考えながら上手に活用していきましょう。

Webプッシュ通知を実装する

PWAを導入したいという方の中でも、プッシュ通知を使いたいという声は非常に多いです。注目の高い技術なので、サンプルに実装してみましょう。

※2019年10月現在、iOSでのWebプッシュはサポートされていません

Webプッシュを実現するには、「JavaScriptのAPIを使って独自で実装する」「Webプッシュを提供しているサービスを使う」の2つの方法があります。今回は、後者の無料で簡単に始めることができるサービス「OneSignal」を使った方法を紹介します。独自で実装する場合は、Google Code Labsの記事「ウェブアプリへのプッシュ通知の追加」などを参考に試してみてください。

まずは、OneSignalのサイトで会員登録を行い、「NEW APP/WEBSITE」タブから設定していきます。

image

次に、プッシュの種類で「Web Push」を選んで、設置方法で「Custom Code」を選びます。その後にドメインなどの設定をしていきます。

image

すると、SDKファイルがダウンロードできるようになります。後は、ファイルをダウンロードしてサーバーのルートディレクトリに配置し、生成された以下のコードをHTMLの <head> 内に挿入するだけです。

<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async=""></script>
<script>
  var OneSignal = window.OneSignal || [];
  OneSignal.push(function() {
    OneSignal.init({
      appId: "YOUR-APP-ID",
      notifyButton: {
        enable: true,
      },
    });
  });
</script>

ここまで進めた状態でサイトを見てみると右下にアイコンが追加されています。タップすると通知の許可を求められるので、許可するとユーザーとして追加されます。

image

管理画面のNew Messageからプッシュ通知を送信できます。

image

送信すると、許可した端末にプッシュ通知が届くことを確認できました。

サンプル開発はここまでです。

PWAに対して難しい印象を持っている人も多いと思いますが、意外と簡単に実装できるということが伝わっていればうれしいです。

PWA開発のためのチュートリアルとしては、Googleが提供する「はじめてのプログレッシブウェブアプリ」にとても詳しく説明されています。この記事を読んでさらに詳しく知りたいと思った方は、ぜひ目を通してみてください。

最先端の事例に学ぶPWAの可能性

ここまで、簡単なサンプル作成で基本的なことを学んできましたが、「Webでこんなことができるのか」とワクワクするようなPWAの事例もたくさんありますので、いくつか紹介させていただきます。

Instagram

InstagramもPWA対応をしています。端末からの写真の読み取り、カメラを起動しての撮影といった基本機能はもちろん、写真の編集やフィルター加工もCanvas(図形を描画するHTML要素)を使って実現しています。Web技術でネイティブアプリのような写真加工ができるのは驚きです。

image

こえのブログ

サイバーエージェント社の「こえのブログ」もブラウザの新しい機能をふんだんに取り入れ、素晴らしいユーザー体験を提供してくれるPWAです。例えば、ブラウザで音声の録音・圧縮を行ったり、録音時に振動でフィードバックを与えたりと、Webアプリでここまでできるのかという驚きがありました。以前「PWA Night vol.4」(2019年5月開催)で登壇していただいた時の資料にも詳しくまとまっていますのでぜひご覧ください。

こえのブログでのPWA ~ 開発現場編 ~ / Koe-No-Blog PWA - Speaker Deck

image(参照:https://speakerdeck.com/herablog/koe-no-blog-pwa

その他

Appscope」というサイトにさまざまなPWAが紹介されています。Webの未来・可能性を感じることができると思いますので、ぜひご覧ください。

image

最近のPWAの大きな動きとこれから

最後に、今後のPWAの広がりに影響を与えるであろうトピックをいくつかご紹介しましょう。

Google Play StoreでPWAのリリースが可能に?

2019年、年明け早々飛び込んできた大きなトピックです。きっかけとなった記事がこちらです。

Google Play Store now open for Progressive Web Apps 😱

実際はPWAをそのままGoogle Play Storeに配信できる、ということではなく、Chromeに新しく搭載された「Trusted Web Activity(TWA)という機能を利用し、ネイティブアプリ内で Web アプリを起動する仕組みを使うという話です。ただ、従前のWebViewでは実現できなかった「ネイティブアプリとPWAの間でクッキーやキャッシュの共有」ができるとあります。これにより、ログイン状態を共有することでWebとアプリがシームレスに連携することなどが考えられ、非常に楽しみな技術です。

デスクトップPWAの可能性

Web技術でのデスクトップアプリの開発といえばGitHubの「Electron」が有名ですが、筆者が注目しているのが、2019年3月にリリースされたGoogle Chrome 73から正式にサポートされているデスクトップPWAです。インストール可能なPWAアプリを作ってインストールすれば、そのままデスクトップアプリのように使えます。

Webアプリをデスクトップアプリ化したいというニーズはあると思いますし、マルチプラットフォームとしてPWAの活用できる場面がどんどん増えていることを感じています。「PWA Night vol.6」でも登壇してくれたScrapboxなど、デスクトップPWAでどんどん使っていきたいサービスだと思っています。

Webでできることがどんどん増えている

Google I/O 2019」でも、Webでもうすぐ使えるようになるAPIなどの発表がありました。多くの発表がありましたが、個人的にはこれらのAPIが気になっています。

  • Badging API:アプリアイコンにバッジを付与することが可能に
  • Shape Detection API:顔検出、バーコード検出、およびテキスト検出などが可能
  • Native File System API:ローカルデバイス上のファイルとやり取りすることが可能に

Webでできることがどんどん増えてきており、それに比例するようにPWAでできることもどんどん増えていきます。他にもいろいろなAPIが紹介されているので、気になる方はGoogle Developersの記事「Unlocking new capabilities for the web」などをぜひ読んでみてください。

image(参照:https://developers.google.com/web/updates/capabilities

最後に

今後、PWAはますます盛り上がっていくはずです。

PWA Nightはコミュニティの立ち上げから1年を迎えます。そこで、通常の勉強会をさらにスケールアップさせたカンファレンス「PWA Night CONFERENCE 2020」を、2020年2月1日(土)、渋谷・宇田川町の「Abema Towers」で開催する予定です。

PWA Night CONFERENCE 2020では、Web体験を進化させるPWAについて、みんなでカジュアルかつフラットに情報交換したいと思っています。Webサービス開発に携わるエンジニアやディレクター、事業開発担当の人が今の仕事においてPWAという新しい選択肢を持てるような、未来につながるカンファレンスにしたいと考えています。詳しくは、公式サイトをご覧ください。

PWA Night CONFERENCE 2020

今後もPWAの情報が集まる場所として盛り上げていければと思いますので、PWAに興味を持った方はぜひ遊びに来てください!

みんなでWebの未来を作っていきましょう!

著者プロフィール

菅家大地 (かんけ・だいち)@kan_dai

Webコンサルティング会社のデザイナーを経て株式会社TAMのフロントエンドエンジニアに。大手企業のWebサイトやアプリのフロントエンドの開発業務に従事。
2019年3月に宮城県に移住。PWA Nightや仙台のフロントエンドコミュニティの運営に携わっている。

関連記事

*1:実際、iOS12.1以前ではホーム画面に追加したアプリについて「アプリを切り替えて戻った場合に最初の画面に戻ってしまう(マルチタスク時に状態を保持できない)」「左スワイプで戻れない」などの問題がありましたが、iOS12.2でそれらが解消され、使いやすさが大幅に改善されました。こうしたiOSの対応に比例して、以前よりもPWA導入に前向きな話を聞くことが増えたように感じます。