概要
- Ruby on Rails Advent Calendar 2022の17日目の記事です。
- Ruby on Rails 7.0からアセット管理にimport mapsが使われるようになりました。Rails6のときに使われていたwebpackerとは大きく仕組みが異なります。
- import mapsの学習のためにrails newから始めて、1つサービスを構築して公開してみました。
- 個人的には日本や世界のDXをに役立ったり、生活を便利にするサービスを作ることを片手間におここなっています。今回もその一環です。
今回作ったサイト
「元号くん」という名称で、西暦から和暦を表示したり、和暦から西暦を表示できます。また、各年ごとの祝日の情報を表示しています。
ただ表示するだけではRailsを使う意味があまりないのでREST APIでも同様の変換を提供できるようにしました。
サイトのURLは以下になります。
トップページのスクリーンショットは以下です。
importmapとは
すでに解説記事が多数あるのでそちらを参照するのが良いです。こちらの記事がとても理解しやすかったです。
JSのmoduleをimportするときに、今まではブラウザ上では相対パスでしか指定できなかったけど、importするmoduleとしてnode_moduleやURLなど色々指定できるようにする機能というかんじです。なるほど。
importmapの対応ブラウザ
2022年12月14日時点でグローバルで72.9%です。古めのsafariだと対応してい無さそうです。
サービス構築
要件定義
- importmapを使う。
- REST APIからも利用できるようにする。
- すでに動いている自宅サーバで公開できるようにすることでサーバ費用はほぼ0円で運用する。
外部設計
大したサービスではないのでページ階層を書いておきます。
- トップページ
- 西暦→和暦 一覧
- 各西暦のページ
- 和暦→西暦 一覧
- 和暦のページ
- 年ごとの休日一覧
- 年ごとのページ
- 西暦→和暦 一覧
内部設計
- 西暦と和暦を変換するgemであるwarekiを使えばかんたんに実装できそうです。
- 休日のマスタはholiday_jpを使えばかんたんに実装できそうです。
- RDBなどのストレージは使わないで運用できそうなのでストレージの設計は無いです。
実装
まずはじめに開発サーバを作ります。dockerによるコンテナ上で開発をするので開発環境を準備します。
以下のファイルを準備します。
上記ファイルを準備した上で以下のコマンドを実行して、railsの実行にgemをインストールします。
% docker compose run --rm app bundle install
次に、railsの新規プロジェクトを作成します。なんら特殊なオプションを指定しないでrailsの推奨するテンプレで作っていきます。
% docker compose run --rm app bundle exec rails new . -C sqlite3
生成されたGemfileを覗いてみます。アセットに関連するgemは以下のものが指定されていました。
gem "sprockets-rails" gem "importmap-rails" gem "turbo-rails" gem "stimulus-rails"
ここで、config/importmap.rb の初期設定を見てみます。ここに、ロードする可能性のあるJSの場所を一元管理しておくこととなります。
pin "application", preload: true pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true pin_all_from "app/javascript/controllers", under: "controllers"
1行目で書かれているapplication.jsにはstimulusの初期化が書かれています。
> cat ./app/javascript/controllers/application.js import { Application } from "@hotwired/stimulus" const application = Application.start() // Configure Stimulus development experience application.debug = false window.Stimulus = application export { application }
2〜4行目にあるhotwiredの部分では、turbo, stimulusのモジュール定義が入っています。
最後の行は、pin_all_fromと書かれており、controllers以下のファイルを自動的に展開してキーとして指定できるような定義が書かれています。
controllers以下のJSの中身は以下のようになっています。stimulusで書かれたJSのテンプレが入っています。stimulusを使わなければごそっと消しても良いかと思います。
> head -n 1000 ./app/javascript/controllers/* ==> ./app/javascript/controllers/application.js <== import { Application } from "@hotwired/stimulus" const application = Application.start() // Configure Stimulus development experience application.debug = false window.Stimulus = application export { application } ==> ./app/javascript/controllers/hello_controller.js <== import { Controller } from "@hotwired/stimulus" export default class extends Controller { connect() { this.element.textContent = "Hello World!" } } ==> ./app/javascript/controllers/index.js <== // Import and register all your controllers from the importmap under controllers/* import { application } from "controllers/application" // Eager load all controllers defined in the import map under controllers/**/*_controller import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" eagerLoadControllersFrom("controllers", application) // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" // lazyLoadControllersFrom("controllers", application)
BootstrapのJSとCSSの追加
デザインはTwitter bootstrapを使うので追加していきます。
まずは、今回のメインであるimportmapを使ったJSのロード部分です。まずは、キーを追加します。今回デフォルトで指定されているファイルをそのまま使います。minifyされたファイルのURLが指定されています。
また、bootstrapが依存しているpopperjsも自動的に追加されました。
% docker compose run -- app bin/importmap pin bootstrap Pinning "bootstrap" to https://ga.jspm.io/npm:[email protected]/dist/js/bootstrap.esm.js Pinning "@popperjs/core" to https://ga.jspm.io/npm:@popperjs/[email protected]/lib/index.js
実行後のconfig/importmap.rbがこちら。
pin "application", preload: true pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true pin_all_from "app/javascript/controllers", under: "controllers" pin "bootstrap", to: "https://ga.jspm.io/npm:[email protected]/dist/js/bootstrap.esm.js" pin "@popperjs/core", to: "https://ga.jspm.io/npm:@popperjs/[email protected]/lib/index.js"
次に、上記の定義をもとに app/javascript/application.js で読み込みます。最後の2行を追加します。
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "@hotwired/turbo-rails" import "controllers" import * as Popper from "@popperjs/core" import * as Bootstrap from "bootstrap"
次に、stylesheetです。railsの推奨はdartsassなので、dartsass-railsをインストールします。
% docker compose run --rm app bundle add dartsass-rails Fetching gem metadata from https://rubygems.org/.......... Resolving dependencies... Fetching gem metadata from https://rubygems.org/.......... Resolving dependencies... <snip> Fetching dartsass-rails 0.4.0 <snip> Installing dartsass-rails 0.4.0
そしてインストールを実行
% docker compose run --rm app bundle exec rails dartsass:install Build into app/assets/builds create app/assets/builds create app/assets/builds/.keep append app/assets/config/manifest.js Stop linking stylesheets automatically gsub app/assets/config/manifest.js append .gitignore Add default app/assets/stylesheets/application.scss create app/assets/stylesheets/application.scss Add default Procfile.dev create Procfile.dev Ensure foreman is installed run gem install foreman from "." Fetching foreman-0.87.2.gem Successfully installed foreman-0.87.2 1 gem installed Add bin/dev to start foreman create bin/dev Compile initial Dart Sass build run rails dartsass:build from "." + /usr/local/bundle/gems/dartsass-rails-0.4.0/exe/aarch64-linux/sass --style\=compressed --no-source-map --load-path /app/app/assets/stylesheets --load-path /app/app/assets/builds --load-path /app/app/assets/config --load-path /app/app/assets/images --load-path /app/app/assets/stylesheets --load-path /usr/local/bundle/gems/stimulus-rails-1.2.1/app/assets/javascripts --load-path /usr/local/bundle/gems/turbo-rails-1.3.2/app/assets/javascripts --load-path /usr/local/bundle/gems/importmap-rails-1.1.5/app/assets/javascripts --load-path /usr/local/bundle/gems/actiontext-7.0.4/app/assets/javascripts --load-path /usr/local/bundle/gems/actiontext-7.0.4/app/assets/stylesheets --load-path /usr/local/bundle/gems/activestorage-7.0.4/app/assets/javascripts --load-path /usr/local/bundle/gems/actionview-7.0.4/lib/assets/compiled --load-path /app/app/javascript --load-path /app/vendor/javascript /app/app/assets/stylesheets/application.scss:/app/app/assets/builds/application.css
次に、CSSのファイルを管理するためにnpm経由でcssを取得します。今までなら、npmによってJSとCSSを同時に取得していましたが、管理が別々になっちゃいます。
dartsass自体はnodeJS無しで実行できますが、sassファイルの管理自体はnpm経由が私は楽だと思うので結局はnodeJSを使ってしまっています。
npm-check-updatesによってパッケージの更新がわかるので楽です。
package.json
{ "dependencies": { "bootstrap": "^5.2.3", "bootstrap-icons": "^1.10.2", "bootswatch": "^5.2.2" }, "devDependencies": { "npm-check-updates": "^16.4.3" }, "license": "UNLICENSED" }
app/assets/stylesheets/application.scssにファイルを読み込むように書きます。
@import "../../../node_modules/bootswatch/dist/flatly/variables"; @import "../../../node_modules/bootstrap/scss/bootstrap.scss"; @import "../../../node_modules/bootswatch/dist/flatly/bootswatch"; @import "../../../node_modules/bootstrap-icons/font/bootstrap-icons";
これでJSの読み込みができつつ、デザインが反映されるようになります。
その後は、dartsassによるファイル変更監視などを設定して、いつもどおりrailsアプリ開発と同じです。
テスト
個人サービスだし、変更はほとんど行わないので省略。
考察
lighthouseで高スコア
95点です。ユーザ体験も大きく向上できます。
popperのファイル読み込みが多数出るようになった
importmapの前提としては、HTTP2の利用があるので大した問題ではないかもしれませんが、細かく分割されたファイルが読み込まれるようになりました。全部popper関連です。流石に分割され過ぎのような気もしますが、キャッシュ効率を考えるとこれが良いのでしょう。
Google Spreadsheetで和暦表示が可能になります
Google Spreadsheetでは和暦を表示できません。Google App Scriptで西暦から和暦への変換プログラムを書いてよいのですが、なかなか複雑なので今回使ったサービスを使って和暦を表示してみたいと思います。
また、自分でロジックを書くと新たな和暦が登場したときにメンテナンスが必要になるという問題も発生します。
Google SpreadsheetではGoogle App Scriptを使うとREST APIの呼び出しと、JSONのパースができるようになります。
出力例とサンプルファイルのURL↓
Google App ScriptでREST APIを呼び出します。
function getWarekiFromSeireki(year) { const url = 'https://seireki.teraren.com/seireki/' + year + '.json'; try { ContactsApp response = UrlFetchApp.fetch(url, {'muteHttpExceptions': true}); response_object = JSON.parse(response); return response_object['wareki']; }catch(e){ Logger.log(e); } }
日付から、西暦を出力してあげたほうが親切かなと思いました。
その他
Webサービスの公開に際して以下のことも行っています
- XML Sitemapの出力とGoogle search consoleへの登録
- gretelを利用してパンくずリストの表示と、ld+jsonを利用したマークアップ。
- robots.txtの設置
- google-tag-manager-railsを入れてGTMを設定
まとめ
- rails7 + importmap-rails + dartsass-railsを使って最新の推奨組み合わせを使ってサイトを構築してみました。
- かかった時間は8時間ぐらいです。この記事を書くのに2時間ぐらいです。
Comments