updated on 2019-03-19
環境 Ruby On Rails(5.2)
PWAを導入するには以下が必要になります。
色々な方法がありますが、CSSで
hogehoge.scss
@media all and (min-width: 769px){ // 769px 以上の画面用の CSS } @media all and (max-width: 768px){ // 768px 以下の画面用の CSS } @media all and (max-width: 640px){ // 640px 以下の画面用の CSS }
のようにしてサイズを指定して切り分けることができます。
config/environments/production.rb
Rails.application.configure do
# ...
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
# ...
end
そもそもServiceworkerってなんぞやというと「クライアント側でユーザーが見ている画面とは別にバックグラウンドで動かせるスクリプト」です。
ややこしい説明はここではしませんが、こいつのおかげでPWAの機能である、PUSH通知やオフラインでも画面を見せたりすることができます。
今回は gem serviceworker-rails という gem を使って実装しました。
まずはgemをインストールします。
gem 'serviceworker-rails'
$ bundle install
そして初期ファイルを作成します。
手で作ることもできますが、面倒くさいので今回はコマンドを叩いて作成します。
$ rails g serviceworker:install create app/assets/javascripts/manifest.json.erb create app/assets/javascripts/serviceworker.js.erb create app/assets/javascripts/serviceworker-companion.js create config/initializers/serviceworker.rb append app/assets/javascripts/application.js append config/initializers/assets.rb insert app/views/layouts/application.html.haml create public/offline.html
上記が初期ファイルになります。この初期ファイルの中でさまざまな設定が書かれています。
config/initializers/assets.rb
Rails.configuration.assets.precompile += %w[serviceworker.js manifest.json]
config/initializers/serviceworker.rb
Rails.application.configure do config.serviceworker.routes.draw do # map to assets implicitly match "/serviceworker.js" match "/manifest.json" # Examples # # map to a named asset explicitly # match "/proxied-serviceworker.js" => "nested/asset/serviceworker.js" # match "/nested/serviceworker.js" => "another/serviceworker.js" # # capture named path segments and interpolate to asset name # match "/captures/*segments/serviceworker.js" => "%{segments}/serviceworker.js" # # capture named parameter and interpolate to asset name # match "/parameter/:id/serviceworker.js" => "project/%{id}/serviceworker.js" # # insert custom headers # match "/header-serviceworker.js" => "another/serviceworker.js", # headers: { "X-Resource-Header" => "A resource" } # # anonymous glob exposes `paths` variable for interpolation # match "/*/serviceworker.js" => "%{paths}/serviceworker.js" end end
ここで serviceworker.js と manifest.json の2ファイルを読み込み、パスを指定しています。
app/assets/javascripts/serviceworker-companion.js
if (navigator.serviceWorker) { navigator.serviceWorker.register('/serviceworker.js', { scope: './' }) .then(function(reg) { console.log('[Companion]', 'Service worker registered!'); }); }
app/assets/javascripts/serviceworker.js.erb
var CACHE_VERSION = 'v1'; var CACHE_NAME = CACHE_VERSION + ':sw-cache-'; function onInstall(event) { console.log('[Serviceworker]', "Installing!", event); event.waitUntil( caches.open(CACHE_NAME).then(function prefill(cache) { return cache.addAll([ // make sure serviceworker.js is not required by application.js // if you want to reference application.js from here '<%#= asset_path "www_domain/application.js" %>', '<%= asset_path "www_domain/application.css" %>', '/offline.html', ]); }) ); } function onActivate(event) { console.log('[Serviceworker]', "Activating!", event); event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.filter(function(cacheName) { // Return true if you want to remove this cache, // but remember that caches are shared across // the whole origin return cacheName.indexOf(CACHE_VERSION) !== 0; }).map(function(cacheName) { return caches.delete(cacheName); }) ); }) ); } // Borrowed from https://github.com/TalAter/UpUp function onFetch(event) { event.respondWith( // try to return untouched request from network first fetch(event.request).catch(function() { // if it fails, try to return request from the cache return caches.match(event.request).then(function(response) { if (response) { return response; } // if not found in cache, return default offline content for navigate requests if (event.request.mode === 'navigate' || (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) { console.log('[Serviceworker]', "Fetching offline content", event); return caches.match('/offline.html'); } }) }) ); } self.addEventListener('install', onInstall); self.addEventListener('activate', onActivate); self.addEventListener('fetch', onFetch);
app/assets/javascripts/serviceworker-companion.js
と app/assets/javascripts/serviceworker.js.erb
では
のそれぞれの Serviceworker のイベントごとの挙動を設定しています。
続いて application.js に serviceworker-companion.js を読み込ませます。
app/assets/javascripts/application.js
//= require serviceworker-companion
最後に Serviceworker の目玉機能の1つでもある、オフラインページです。
ユーザーがオフライン時に表示される画面の設定です。Rails のデフォルトの 404ページや 500ページと同様、public 配下にデフォルトのファイルができているので、文言やデザインを変えたい方はこちらをいじると変えることができます。
また、404等と同様、public 配下ではなく、動的に作り直すこともできるようです。
ちなみにオフライン用のファイルとして public/offline.html
が使われるのは、app/assets/javascripts/serviceworker.js.erb
内の onInstall
関数で設定されているからなので、設定すれば、offline.html 以外のファイルもオフラインファイルとして設定できると思います。
デフォルトの offline.html は以下。
public/offline.html
<!DOCTYPE html> <html> <head> <title>You are not connected to the Internet</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> body { background-color: #EFEFEF; color: #2E2F30; text-align: center; font-family: arial, sans-serif; margin: 0; } div.dialog { width: 95%; max-width: 33em; margin: 4em auto 0; } div.dialog > div { border: 1px solid #CCC; border-right-color: #999; border-left-color: #999; border-bottom-color: #BBB; border-top: #B00100 solid 4px; border-top-left-radius: 9px; border-top-right-radius: 9px; background-color: white; padding: 7px 12% 0; box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); } h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } div.dialog > p { margin: 0 0 1em; padding: 1em; background-color: #F7F7F7; border: 1px solid #CCC; border-right-color: #999; border-left-color: #999; border-bottom-color: #999; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; border-top-color: #DADADA; color: #666; box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); } </style> </head> <body> <!-- This file lives in public/offline.html --> <div class="dialog"> <div> <h1>It looks like you've lost your Internet connection</h1> <p>You may need to reconnect to Wi-Fi.</p> </div> </div> </body> </html>
Serviceworker の導入は以上です。
rails s
で確認
最後に Manifest の導入をします。
Manifest は簡単にいうとホーム画面に追加したときの設定です。
設定は上で作られた app/assets/javascripts/manifest.json.erb
内で行います。
以下がデフォルトでできたファイルです。
app/assets/javascripts/manifest.json.erb
<% icon_sizes = Rails.configuration.serviceworker.icon_sizes %> { "name": "app name", "short_name": "app short name", "start_url": "/", "icons": [ <% icon_sizes.map { |s| "#{s}x#{s}" }.each.with_index do |dim, i| %> { "src": "<%= image_path "serviceworker-rails/heart-#{dim}.png" %>", "sizes": "<%= dim %>", "type": "image/png" }<%= i == (icon_sizes.length - 1) ? '' : ',' %> <% end %> ], "theme_color": "#000000", "background_color": "#FFFFFF", "display": "fullscreen", "orientation": "portrait" }
JSONでさまざまな設定がされているようですが、それぞれの意味と値はこんな感じです。
キー | 内容 |
---|---|
name | アイコンのラベルとして使われる名前 |
short_name | name が入り切らないときなどに表示される名前 |
start_url | ユーザーがアプリケーションを起動したときに最初にロードされるURL |
icons | Serviceworkerでさまざまな場所で使われるアイコン。 それぞれの用途の推奨サイズに一番近い画像が使われます。 ただし、iOSの場合、 app_touch_icon がアイコンに使われてしまうのでそちらをちゃんと設定しないといけない模様。・src(画像のパス) ・sizes(画像のサイズ。例:"128x128") ・type(例:"image/png") の3つを指定します。 デフォルトでは Rails.configuration.serviceworker.icon_sizes のサイズでループ処理で設定していますが、サイズの中身は 36 48 60 72 76 96 120 152 180 192 512 です。 |
theme_color | テーマカラー。アンドロイドのタスクスイッチャーではこの色で囲まれるらしい。 |
background_color | アプリの背景色。そもそもCSSで各々のサイトは背景色をつけていることも多いと思いますが、アプリの起動からコンテンツをロードするまでの間などにこの色が使われます。 |
display | アプリの表示の仕方を設定。 ・fullscreen(デバイスのメニューバー含めて非表示) ・standalone(ブラウザのUIを非表示。ネイティブアプリと同じ感じ。) ・browser(通常のブラウザ表示) |
orientation | ページの最初の向きを設定。 ・landscape にするとデフォルトで横向きになるので、横表示のみで表示するゲームなどには便利。他にも以下の値を指定できます。 any・natural・landscape-primary・landscape-secondary・portrait・portrait-primary・portrait-secondary |
先程と同様、検証ツール内の Application の中の Manifest
というところを開くと、
Manifest の設定が反映されているか確認することができます。
rails sをしたまま Xcode の Simulator を使ってすることもできます。
さて、アイコンはデフォルトだとハートアイコンになってると思います。後、IOSアイコンは個別に作成しなければならないので、手順メモを載せます
favicon.icoはブラウザで開くと上部でページタイトルの左に小さく表示されているアイコンのことです。
はじめに以下のiconフォルダ作成
/app/assets/images/favicons(このフォルダの中にiosアイコンを入れる)
*faviconsでなくても好きな名前でいい
/app/assets/images/serviceworker-rails(このフォルダの中にアンドロイドアイコンを入れる)
*serviceworker-railsでなくても好きな名前でいい
<Android用>
/app/assets/images/serviceworker-rails配下に
<ios用>
/app/assets/images/favicons配下に
<favicon.ico>
/app/assets/images配下に
favicon.ico を作成
読み込み
app/views/layouts/application.html.erb の<head>タグ内に以下を追記 <%= favicon_link_tag('favicon.ico') %> <link rel="apple-touch-icon" sizes="57x57" href="/assets/favicons/apple-touch-icon57.png"> <link rel="apple-touch-icon" sizes="60x60" href="/assets/favicons/apple-touch-icon60.png"> <link rel="apple-touch-icon" sizes="72x72" href="/assets/favicons/apple-touch-icon72.png"> <link rel="apple-touch-icon" sizes="76x76" href="/assets/favicons/apple-touch-icon76.png"> <link rel="apple-touch-icon" sizes="114x114" href="/assets/favicons/apple-touch-icon114.png"> <link rel="apple-touch-icon" sizes="120x120" href="/assets/favicons/apple-touch-icon120.png"> <link rel="apple-touch-icon" sizes="144x144" href="/assets/favicons/apple-touch-icon144.png"> <link rel="apple-touch-icon" sizes="152x152" href="/assets/favicons/apple-touch-icon152.png"> <link rel="apple-touch-icon" sizes="180x180" href="/assets/favicons/apple-touch-icon180.png"> これでfaviconおよびiosのアイコン設定完了
app/assets/javascripts/manifest.json.erbのパスをデフォルトから変更 <% icon_sizes = Rails.configuration.serviceworker.icon_sizes %> { "name": "My App Name", "short_name": "Short Name", "start_url": "/", "icons": [ <% icon_sizes.map { |s| "#{s}x#{s}" }.each.with_index do |dim, i| %> { "src": "<%= asset_path "serviceworker-rails/icon-#{dim}.png" %>", # image_pathからasset_pathに変更 "sizes": "<%= dim %>", "type": "image/png" }<%= i == (icon_sizes.length - 1) ? '' : ',' %> <% end %> ], "theme_color": "#F83E26", "background_color": "#000000", "display": "standalone", "orientation": "portrait" } これでAndroidアイコン設定も完了
次回起動アイコンやプッシュ通知も書きたい