Rails6 gem serviceworker-rails で PWA化

# はじめに
WebアプリケーションをネイティブなアプリのようにできるPWA化に憧れて、実装してみました！
紆余曲折しましたがなんとか完成！
忘れないうちにまとめておこうと思います！

おおまかな流れは、こちらのREADMEを参考にしています↓

https://github.com/rossta/serviceworker-rails

# 前提
Ruby 3.1.2
Rails 6.1.7.7

Rails6以降の記事が少なくて…！苦戦しました💦

# Serviceworkerの導入

#### gem導入

```Gemfile.
gem 'serviceworker-rails'
```

```
$ bundle install
```

ファイルはコマンドを実行すれば、自動作成してもらえます

```
$ rails g serviceworker:install
Running via Spring preloader in process 31804
      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
The file /home/ec2-user/environment/go_out_planning/app/assets/javascripts/application.js does not appear to exist
```

:::note info
application.jsはRails 5.x以前だとassets/javascript/に格納されていましたが、現在Rails6のため下記が出力されます
```
The file /home/ec2-user/environment/go_out_planning/app/assets/javascripts/application.js does not appear to exist
```
:::

#### serviceworker-companion.jsの読み込み

app/assets/javascripts/serviceworker-companion.js
のjsファイルをapplication.jsのあるapp/javascript/packsに移動します

![スクリーンショット 2024-09-17 13.39.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/069ee3dc-1c26-4374-2597-27131cd74ed6.png)

次に、application.jsでserviceworker-companionを読み込みます

```application.js
import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"

import "jquery";
import "popper.js";
import "bootstrap";
import "../stylesheets/application";
// この行を追加
import '../packs/serviceworker-companion';

Rails.start()
Turbolinks.start()
ActiveStorage.start()
```

#### 設定
`Rails.configuration.assets.precompile += %w[serviceworker.js manifest.json]`を最終行に追加

```config/initializers/assets.rb
# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'

# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths << Rails.root.join('node_modules')

# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
Rails.application.config.assets.precompile += %w( admin.js admin.css )

# この行を追加
Rails.configuration.assets.precompile += %w[serviceworker.js manifest.json]
```

こちらは特に触る必要はありませんでした

```config/initializers/seviceworker.rb
# frozen_string_literal: true

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
この状態で一旦サーバーを立ち上げてみます

```
$ rails s
[Webpacker] Compiling...
Started GET "/service_worker.js" for 180.0.182.32 at 2024-09-17 05:07:08 +0000
Cannot render console from 180.0.182.32! Allowed networks: 127.0.0.0/127.255.255.255, ::1
  
ActionController::RoutingError (No route matches [GET] "/service_worker.js"):
```

/service_worker.jsに接続できないよーと言われてますね
ということでservice_worker.jsをpublicの中に作成しましょう

:::note info
なぜpublicの中なのか？
publicの中に配置することにより、Rails のルーティングを介さずに静的ファイルとして提供でき、url/service_worker.jsで接続することが可能になります！
:::

```public/service_worker.js
self.addEventListener('install', function(event) {
  console.log('[Service Worker] Installing Service Worker...');
});

self.addEventListener('activate', function(event) {
  console.log('[Service Worker] Activating Service Worker...');
});

self.addEventListener('fetch', function(event) {
  console.log('[Service Worker] Fetching something....', event.request.url);
});
```

アドレスバーに~.com/service_worker.jsを打ち込むと、
以下のような画面になっていればOK!

![スクリーンショット 2024-09-17 14.15.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/1d2ed89e-901a-5f6b-5132-f5c0068a53cc.png)

#### 検証ツールで確認

Application>Service workersを開いてStatusを確認
ちゃんとactivated is a runningになってますね！

![スクリーンショット 2024-09-17 14.17.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/42d7ae1d-890a-81e6-af01-5455fe4810b8.png)

#### manifest.json.erb

/assets/javascript/manifest.json.erb
が現在では読み込まれていないいないので、application.html.erbの`<head>`部分に`<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">`を追記していきます！

```application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>GoOutPlanning</title>
    <link rel="manifest" href="/manifest.json" crossorigin="use-credentials">

  </head>

  <body>
  :
  </body>
</html>
```

#### majifest.json.erbを編集

デフォルトの記述がこちら↓

```app/assets/javascripts/manifest.json.erb
<% icon_sizes = Rails.configuration.serviceworker.icon_sizes %>
{
  "name": "My Progressive Rails App",
  "short_name": "Progressive",
  "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"
}
```

今回は下記のように編集↓
iconはサイズ別に複数用意しておく(適当な変換サイトでOK)
assets/imagesに入れておこう！

```manifest.json.erb
{
  "name": "おでかけプランニング-GoOutPlanning-",
  "short_name": "GoOutPlanning",
  "start_url": "/",
  "icons": [
    {
      "src": "<%= image_path "go_out_planning_logo_36.png" %>",
      "sizes": "36x36",
      "type": "image/png"
    },
    {
      "src": "<%= image_path "go_out_planning_logo_48.png" %>",
      "sizes": "48x48",
      "type": "image/png"
    },
    {
      "src": "<%= image_path "go_out_planning_logo_72.png" %>",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "<%= image_path "go_out_planning_logo_96.png" %>",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "<%= image_path "go_out_planning_logo_144.png" %>",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "<%= image_path "go_out_planning_logo_192.png" %>",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "<%= image_path "go_out_planning_logo_512.png" %>",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "theme_color": "#3B4674",
  "background_color": "#FFFFFF",
  "display": "standalone",
  "orientation": "portrait",
  "screenshots": [
    {
      "src": "<%= image_path "screenshot-wide_1472x720.png" %>",
      "sizes": "1472x720",
      "type": "image/png",
      "form_factor": "wide"
    },
    {
      "src": "<%= image_path "screenshot-mobile_640x1280.png" %>",
      "sizes": "640x1280",
      "type": "image/png",
      "form_factor": "narrow"
    }
  ]
}
```

#### 検証ツールで確認

Application>Manifestを開いて内容が反映されていればOK！

![スクリーンショット 2024-09-17 14.49.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/9ede3e98-a807-d6ec-011b-aac7defb0106.png)

![スクリーンショット 2024-09-17 14.50.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/c4078773-88a9-4719-6dcd-050cc2320a14.png)

#### アプリをインストールしてみる

![スクリーンショット 2024-09-17 14.54.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/3093aebd-9522-87d9-6724-97f808e7815f.png)

 アドレスバーにインストールのマークが出ているのでクリックし、インストールする

 ![スクリーンショット 2024-09-17 14.54.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/135d2ebb-8a5a-c380-19ef-e3dd3bfbc251.png)

![スクリーンショット 2024-09-17 14.56.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/8bcd5501-7eac-e785-26a1-2efdde930814.png)

これで開発環境のアプリインストールができました！
あとはデプロイして本番環境でも問題なく動作するか確認しましょう！

#### 本番環境での動作確認
最初に本番環境で確認したら反映されていなかったので、もし同じ方がいらっしゃったら`assets:precompile`を実行してみるといいかもです！

また、一応UIが非表示かされてはいるのですが、本番環境で/service_worker.jsにアクセスできなくなっていたので修正しておきます

/service_worker.jsに接続できないようですね
ちなみに、アドレスバーにgo-out-planning.net/service_worker.jsを打ち込むと、以下のような画面が出てきます！

![スクリーンショット 2024-09-17 12.44.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/1c9c7daf-8dd0-60ff-2168-7605595c737c.png)

publicにservice_worker.jsを格納しているはずなのに、出てこない！
というのはこれで一発で解消できます！

```config/environments/production.rb
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
```

を↓に修正するだけ！

```config/environments/production.rb
config.public_file_server.enabled = true
```
:::note info
config.public_file_server.enabled の設定を true に変更すると、Rails アプリケーションが静的ファイルを自動的に提供するようになります。具体的には、public ディレクトリにある静的ファイル（例: JavaScript、CSS、画像ファイルなど）が、Rails アプリケーションのサーバーを通じて提供されるようになります。
:::

ただ、今回は小規模なアプリケーション開発のため上記をtrueにしていますが、基本的には非推奨のようです…
もっといい方法あれば是非教えてください🙇

# 実際のアプリ

https://go-out-planning.net/

# さいごに
長くなってしまいましたが、以上がRailsアプリをPWA化するために私が行ったことです！

# 参照サイト
ありがとうございました！

https://github.com/rossta/serviceworker-rails

https://qiita.com/hirotakasasaki/items/e3fb638b4f75a92bce8b

https://qiita.com/chikara_funabashi/items/7e456100e1ceb1bf3f20