Sprockets管理のRails4環境でWebサイトをPWAに対応する方法のまとめ(準備〜デプロイ編)


ちょっと前に作られたWebサービスをPWA化(ServiceWorkerの導入とmanifest.jsonの導入)をしたので、そちらのまとめです。同じような環境でPWAの導入を検討されている方の参考になればと思います。

(PWAとかService Workerが何者かという説明は省いております。)

対象のサービスの状態

  • サーバサイド: Rails 4.1.4
  • フロントエンド: jQuery
  • ビルド: railsについてくるsprockets

4〜5年前ぐらいの環境だと思ってもらえれば大丈夫。

流れ

  • ライブラリのインストール
  • Sprocketsの読み込み設定
  • デプロイ設定

ライブラリのインストール

インストールするもの

rails側

  • serviceworker-rails

javascript側

  • workbox-sw
  • pwacompat
  • babel

serviceworker-rails

railsにService Workerを導入するために必要なgem

gem 'serviceworker-rails'

javascriptファイルをsprockets管理に置いてない場合はgemでインストールしちゃうともしかしたら面倒かも...

sprockets管理のjavascriptだと /assets/hogehoge.js という用に /assets ディレクトリの下にファイルがくることになるかと思います。

ところがService Workerのファイルやmanifest.jsonはそれだと少々困ってしまいます。
そこで、/serviceworker.js/manifest.json でアクセスした時にちゃんと /app/assets/javascripts/ 以下を見るようにルーティングを調整してくれます。

URL: /serviceworker.js
   ↓
実ファイル: /assets/javascripts/servicewoker.js

workbox-sw

GoogleがつくっているService Workerのキャッシュ管理用ライブラリ。
今からService Workerを導入するなら黙って入れておきましょう。

npmでinstallできます。

npm install --save workbox-sw

え、npmでjsライブラリを管理していない?

そしたらこれを気に導入してしまいましょう。
下記を config/initialize/assets.rb に追加すれば npm install したライブラリがsprocketsで利用できるようになります。

config/initialize/assets.rb

Rails.application.config.assets.paths << Rails.root.join('node_modules')

workbox-swについて
https://developers.google.com/web/tools/workbox/

pwacompat

最近出てきた、こちらもGoogle製のライブラリ。

iOS safariはmanifest.jsonにまだ対応してないのですが(2018年7月現在)
このライブラリを使うと、iPhoneでmanifest.jsonの内容を元にスプラッシュ画面を出せるようになったり、iconを表示できるようになったり、縦横が選べるようになったり、ということができるようになります。

先ほどのworkboxとは違い、こちらはそこまで重要ではないですが入れておくと良いでしょう。

pwacompatはService Worker側でなくフロント側で読み込まれるようにする必要があります。

npm insatll --save pwacompat

pwacompat
https://github.com/GoogleChromeLabs/pwacompat

CORS対応

スプラッシュ画面に表示するアイコンの画像URLを外部サイトから読み込もうとするとCORSのエラーになってしまっていたので修正プルリク出したのですが、無事取り込まれました

babel

rails4のsprocketsにworkbox-swとか導入すると、precompile時にシンタックスエラーで無事死にます

そこでbabelでトランスパイルして、トランスパイルしたファイルをsprockets管理するようにしました。

npm install --save bable

※sprocketsのアップデートとかwebpackの導入とかも選択肢にありましたが、ハードル高そうなので一番シンプルにできるbabelにしてます。

sprocketsの読み込み設定

Service Worker側のjavascriptはgemをinstallすることで、ある程度よしなにやってくれますが、npm installして、トランスパイルをしたライブラリは少々手を加える必要があります。

workbox-sw

  1. app/assets/javascripts/serviceworker-lib.js を新規で作成
serviceworker-lib.js
//= require workbox-sw/build/workbox-sw-es2015

※workbox-sw-es2015はトランスパイル後のファイル名

  1. config/initializers/assets.rb に追加
assets.rb
Rails.configuration.assets.precompile += %w[serviceworker.js manifest.json serviceworker-lib.js]

※serviceworker.jsとmanifest.jsonは自動的に追加されてます。

  1. Service Worker側で読み込む

app/assets/javascripts/serviceworker.js.erb

serviceworker.js.erb
importScripts('<%= asset_path "serviceworker-lib.js" %>') ← 追加

pwacompat

application.js

application.js
//= require hogehoge
//= require fugafuga
//= require piyopiyo
//= require pwacompat/pwacompat-es2015 ← 追加

テストについて
今回babelでトランスパイルをしたファイルを読み込むように設定をしたので、テスト実行前にbabelでトランスパイルをする必要があります。
(workboxはService Worker側で必要なファイルなのでテスト実行前のトランスパイル設定は必要ないです。)

CircleCIの場合

circleci/config.yml
     - run: bundle --path vendor/bundle
     ↓↓↓↓ この辺に追加 ↓↓↓↓
     - run: node_modules/.bin/babel node_modules/pwacompat/pwacompat.js -o node_modules/pwacompat/pwacompat-es2015.js
     - run:
          name: Setup database
          command: bundle exec rake db:create db:schema:load

Androidシミュレータ

開発環境でAndroidでPWAが正しく動作をしているか確認するようにAndroidシミュレータをインストールします。
今回はGenymotionというシミュレータを使用。

他サイトのリンクですが、こちらを参考に設定しました
https://hep.eiz.jp/genymotion-google-play/

デプロイ

railsでおなじみの capistrano に、今回babelを使ってトランスパイルするので、それを追加しておきます。

追加する場所が少々厄介でした。
実行したいタスクの順番は下記の通り

タスクの順番
1. npm install
2. babel
3. precompile

npm install の後で、precompile の前に実行をしたいのですが、
capistorano-npm を使うとこの間にタスクを追加することができませんでした

なので仕方なく自前で npm install のタスクを追加して babelコマンドを実行します。

deploy.rb
task :npm_install do
    on roles( %w(<precompile 実行対象サーバーのrole>) ) do
      within release_path do
        with rails_env: fetch(:rails_env) do
          execute "npm install --prefix #{release_path}"
        end
      end
    end
  end

  desc 'execute babel'
  task :babel do
    on roles( %w(<precompile 実行対象サーバーのrole>) ) do
      within release_path do
        with rails_env: fetch(:rails_env) do
          execute "#{release_path}/node_modules/.bin/babel #{release_path}/node_modules/workbox-sw/build/workbox-sw.js -o #{release_path}/node_modules/workbox-sw/build/workbox-sw-es2015.js"
          execute "#{release_path}/node_modules/.bin/babel #{release_path}/node_modules/pwacompat/pwacompat.js -o #{release_path}/node_modules/pwacompat/pwacompat-es2015.js"
        end
      end
    end
  end
  before :compile_assets, :npm_install
  after :npm_install, :babel

task :babel がbabelを実行しているタスクです。

within release_path do 〜 end の中で 実行している execute の後がbableコマンドです。

execute "#{release_path}/node_modules/.bin/babel #{release_path}/node_modules/workbox-sw/build/workbox-sw.js -o #{release_path}/node_modules/workbox-sw/build/workbox-sw-es2015.js"
execute "#{release_path}/node_modules/.bin/babel #{release_path}/node_modules/pwacompat/pwacompat.js -o #{release_path}/node_modules/pwacompat/pwacompat-es2015.js"

release_pathを指定しているのがおかしな感じですが、なぜかパスを指定しないとうまく動いてくれなかったのでそうしてます。
なくても動くかも。