ESM アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

Emacs でだって Docker で開発したい!

こんにちは。wat-aro です。

Docker 環境で開発する際に VSCode の Remote Container はとても便利ですね。
でも今まで Emacs で開発してきた人は VSCode ではなく Emacs を使いたいはずです。
ここでは僕が Emacs + Docker 環境でどのように開発しているかを紹介します。

docker コマンド

まずは docker コマンドを使えなくてはなりません。
Emacs 使いのみなさんはターミナルでなく Emacs から docker コマンドを叩きたいですよね。
そんなときは docker.el です。
https://github.com/Silex/docker.el
docker image コマンドや docker compose コマンドが Emacs から実行できます。
docker compose up で立ち上げたコンテナをリスタートすることなどもできます。
まずはこれでコンテナを立ち上げましょう。

docker コンテナに繋ぐ

コンテナを立ち上げたら Emacs からコンテナに入り、コンテナ内でファイルを編集します。
開発用のコンテナ内に Emacs を入れてしまう人もいるかもしれませんが、余計な依存を開発環境にも入れたくないためその方法は取りません。
また、Emacs からコンテナにアクセスせず、ホストの Emacs から docker や docker compose コマンドを叩けばいいのかと思われるかもしれません。
僕も始めは docker compose コマンドを叩く方法を検討しました。
しかし Emacs のパッケージの多くが外部コマンドにファイルの情報を渡す際にローカルの絶対パスを渡します。
コンテナにローカルの絶対パスを渡してもそのファイルがないため動きません。
プロジェクトルートからの相対パスを渡すことも可能かもしれませんが、flycheck などを見るとファイルパスを渡している部分がマクロになっていて変更が厳しそうだったため、Emacs からコンテナ内のファイルを編集する方法を取るようになりました。
仮にプロジェクトルートからの相対パスを渡せたとしても、様々なパッケージを入れるたびに相対パスを渡すために試行錯語するくらいならコンテナ内に入ってファイルを編集するほうが楽です。

Emacs からコンテナに入る方法です。
普段 Emacs を使っていて、ssh 先に繋ぐ際には tramp.el を使いますよね。
tramp.el ではホスト側の Emacs をそのまま使えるため、使い慣れた設定やパッケージを使えます。
ただし、外部コマンドを実行する場合はリモート先にその外部コマンドがインストールされている必要があります。

コンテナに繋ぐ際には docker-tramp.el が便利です。
https://github.com/emacs-pe/docker-tramp.el

C-x C-f /docker:user@container:/path/to/file  

でコンテナに繋ぐことができます。
毎回接続先を入力するのは大変なため、emacs-helm-trampcounsel-tramp を使うと便利です。
僕は counsel-tramp を使っていますが、コンテナを起動した状態で counsel-tramp を実行すると接続できるコンテナが補完候補として表示されます。
counsel-tramp でコンテナに入った時点では / ディレクトリにいるため Dockerfile に指定した WORKDIR に移動する必要があります。。

lsp-mode

昨今はLSPを使って開発する人が多いと思います。
僕は lsp-mode を使っていますが、 docker-tramp.el でコンテナに入っている際にローカルのままの設定の lsp-mode では Language Server が立ち上がりません。

https://emacs-lsp.github.io/lsp-mode/page/remote/#sample-configuration
emacs-lsp のドキュメントを見ると :new-connectionlsp-tramp-connection を使い :remote?t 設定するとよいようです。
これを参考に solargraph の設定を書くとこのようになります。

(with-eval-after-load "lsp-solargraph"  
  (lsp-register-client  
    (make-lsp-client :new-connection (lsp-tramp-connection '("solargraph" "stdio"))  
      :major-modes '(ruby-mode enh-ruby-mode)  
      :priority 1  
      :remote? t  
      :multi-root t  
      :server-id 'ruby-ls-remote  
      :initialized-fn (lambda (workspace)  
                        (with-lsp-workspace workspace  
                          (lsp--set-configuration  
                            (lsp-configuration-section "solargraph")))))))  

lsp-mode の設定にこれを追加することでリモートの solargraph を使うことができるようになります。

まとめ

僕の Emacs + Docker での設定を紹介しました。
よいプログラミングライフを!

Rails / OSS パッチ会オンライン 2022年2月のお知らせ

2022年2月の Rails / OSS パッチ会を 2月25日(金)に Discord でオンライン開催します。

この会をひとことでいうと、日頃のお仕事で使っている Rails をはじめとする OSS について、upstream にパッチを送る会です。

会には Ruby と Rails のコミッターである顧問の a_matsuda もいますので、例えば Rails に送るパッチのネタがあるけれど、パッチを送るに適しているかの判断やパッチを送る流れが悩ましいときなど a_matsuda に相談して足がかりにするなどできます。

開催時間は 17:00-19:00 となりますがご都合のあう方はぜひご参加下さい。

Discord の Rails/OSS パッチ会サーバーへの招待 URL は以下です👇

discord.gg

これからパッチ会に参加してみようという方も、ぜひどうぞ。Discord でお会いしましょう。


永和システムマネジメントでは、Ruby とアジャイルソフトウェア開発を通じてコミュニティと成長したいエンジニアを絶賛募集しています。

agile.esm.co.jp

U+301C from UTF-8 to Windows-31J (Encoding::UndefinedConversionError) に対応する

こんにちは。ima1zumi です。

私の開発している Rails アプリでは、Excel で読み込めるように 文字コードを Windows-31J に変換して CSV を出力する機能があります。 先日、CSV 出力にて Unicode の波ダッシュ を Windows-31J に変換しようとして Encoding::UndefinedConversionError が発生して CSV 出力に失敗したことがありました。なぜエラーになるのか、どうやって対応するのかをまとめました。

まとめ

encode メソッドの fallback オプションを使って未定義文字の変換先を定義することで変換できます。

str = "\u{2014 301C 2016 2212 00A2 00A3 00AC}"

undefined_signs = {
  "\u2014" => "\x81\x5C".force_encoding(Encoding::Windows_31J), # — EM DASH
  "\u301C" => "\x81\x60".force_encoding(Encoding::Windows_31J), # 〜 WAVE DASH
  "\u2016" => "\x81\x61".force_encoding(Encoding::Windows_31J), # ‖ DOUBLE VERTICAL LINE
  "\u2212" => "\x81\x7C".force_encoding(Encoding::Windows_31J), # − MINUS SIGN
  "\u00A2" => "\x81\x91".force_encoding(Encoding::Windows_31J), # ¢ CENT SIGN
  "\u00A3" => "\x81\x92".force_encoding(Encoding::Windows_31J), # £ POUND SIGN
  "\u00AC" => "\x81\xCA".force_encoding(Encoding::Windows_31J), # ¬ NOT SIGN
}

p str.encode(Encoding::Windows_31J, fallback: undefined_signs)

なぜこれらの文字がエラーになるのか

Unicode の WAVE DASH などの文字は Windows-31J に定義されていないからです。

文字コードを変換するとは、ある文字コードの1文字を別の文字コードの1文字に変換するということです。例えば、Unicode の「あ」は Windows-31J では「あ」に対応する、というように、変換元のある文字は変換先のどの文字に対応する、という関係が1対1で紐付けられています。Unicode の 「🥺」は Windows-31J に存在しないように、ある文字コードには存在していても変換先の文字コードには存在しないため変換できない文字もあります。

Unicode の WAVE DASH(U+301C)は Windows-31J には対応する文字がありません。このため、変換しようとすると定義がないため Encoding::UndefinedConversionError になります。ですが、Unicode の FULLWIDTH TILDE (U+FF5E) は Windows-31J の に対応しているため、 「~」は Windows-31J に変換できます。

"\u301C".encode(Encoding::Windows_31J) # WAVE DASH
# `encode': U+301C from UTF-8 to Windows-31J (Encoding::UndefinedConversionError)

"\uFF5E".encode(Encoding::Windows_31J) # FULLWIDTH TILDE
# => "\x{8160}"

Windows-31J の波ダッシュは FULLWIDTH TILDE に対応するため、 WAVE DASH は変換できないという対応付けになってしまっています*1が、実務上は形が同じ文字に変換してしまいたいことはあります。そういったときに、Ruby では変換未定義文字に変換テーブルを定義することで対応できます。

fallback

ということで、未定義文字のいくつかを自前で定義します。これは String#encode のオプションの fallback を使えます。 fallback には Hash, Proc, Method を渡すことができます。ここでは Hash を使います。キーには変換元である Unicode の文字を、変換先には Windows-31J で変換先に指定したい文字のバイト列を定義します。また、作成した Stringencodingforce_encoding で変更して、文字コードを揃えておきます。

str = "\u{2014 301C 2016 2212 00A2 00A3 00AC}"

undefined_signs = {
  "\u2014" => "\x81\x5C".force_encoding(Encoding::Windows_31J), # — EM DASH
  "\u301C" => "\x81\x60".force_encoding(Encoding::Windows_31J), # 〜 WAVE DASH
  "\u2016" => "\x81\x61".force_encoding(Encoding::Windows_31J), # ‖ DOUBLE VERTICAL LINE
  "\u2212" => "\x81\x7C".force_encoding(Encoding::Windows_31J), # − MINUS SIGN
  "\u00A2" => "\x81\x91".force_encoding(Encoding::Windows_31J), # ¢ CENT SIGN
  "\u00A3" => "\x81\x92".force_encoding(Encoding::Windows_31J), # £ POUND SIGN
  "\u00AC" => "\x81\xCA".force_encoding(Encoding::Windows_31J), # ¬ NOT SIGN
}

p str.encode(Encoding::Windows_31J, fallback: undefined_signs)

このように未定義文字に対して変換規則を作ることで対応ができます。

ref: String#encode (Ruby 3.1 リファレンスマニュアル)

UnicodeからWindows-31Jに変換できない文字はどのくらいあるか

Unicode は世界中の文字を収録した文字コードで Unicode 14.0 時点で 144,697 文字が使えます。Windows-31J は主に日本語が使える文字コードで約 7000 文字を収録しています。変換できない文字は非常に多くあります。 その中でもよく出てくる記号は先ほどのような波ダッシュ「〜」で、漢字では「𠀋」「㐂」「𠮷」(つちよし)など*2があります。これらの変換できない文字をすべて対応しようとするのはあまり現実的ではありません。また、記号であれば形が同じものを同じ文字とみなすことはありますが、特に人名において、字形の異なる漢字を同じ漢字としてみなすことには注意が必要です。このように、未定義文字は単純に別の文字で置き換えればいいという問題ではありません。

未定義文字にどう対応するか

対応方針はいくつか考えられますが、どれもメリット・デメリットがありどれがベストということはありません。扱いたい文章の性質によって対応を切り分けるのが良いと思います。

(1) 入力時に変換できない文字がないかチェックし、変換できない文字は入力させない

メリットは変換できない文字がデータとして入ってこないため、安全に扱えるということです。

デメリットは Windows-31J として入力できる文字かどうかのチェックが大変なことです。また、基本は Unicode として扱って一部 Windows-31J に変換したいような文字の場合、入力できない文字が多いことが不便です。

(2) 変換できない文字は ? のような別の文字に置き換える

メリットは未定義文字があっても変換に成功することです。

デメリットは変換後の文字列から「変換できなかった文字が何であるか」が分からないことです。また、人名や住所など置換すると意味をなさない文字列がある場合、この方法をとるべきではありません。(例:𠮷田が?田になると意味をなさない)

別の文字に置き換える場合、 String#scrub で置換できます。 String#scrub (Ruby 3.1 リファレンスマニュアル)

(3) 変換できない文字があった場合はエラーとし、処理を中断する

メリットは変換できない文字に対し個別に対応できることです。デメリットは、ユーザーは処理を中断されるため不便になることです。

おまけ: 補足と用語の整理

Unicode

世界中のあらゆる文字を収録することを目標とした符号化文字集合です。

UTF-8

Unicode の符号化方式のうちの1つで、8ビット単位の可変長です。

U+xxxx

Unicode codepoint の表記方法です。

\uxxxx, \u{xxxx xxxx}

Ruby で Unicode codepoint を指定して文字を表現する記法です。 {} で括ると複数の文字を指定できます。

ref: リテラル (Ruby 3.1 リファレンスマニュアル)

Shift_JIS

JIS X 0201を1バイトで、JIS X 0208を2バイトで符号化する可変幅文字符号化方式です。

Windows-31J, CP932

Shift_JIS のマイクロソフト拡張版文字コードで、Shift_JIS とは収録されている文字が一部異なります。 Ruby では Windows-31J, CP932 は同じ文字コードです。

WAVE DASH

ダッシュ「―」が波打っている記号で、日本語では波ダッシュと呼びます。

TILDE

ダイアクリティカルマーク(発音を区別する記号)の一種です。チルダ単独で使う場合は数学記号やコンピュータ・プログラミング言語において特別な意味を持つ記号として扱われます。

チルダ - Wikipedia

FULLWIDTH TILDE

いわゆる全角チルダで、互換用の文字です。

この符号位置は、1バイト文字と2バイト文字が混在する符号化方式における重複符号化を救済するための互換用に導入された符号位置です。具体的な用途としては、EUC-JPにおいてASCIIのチルダとJIS X 0212のチルダが重複してしまうのを解決するために、JIS X 0212のチルダに対応付けるための互換用として用いるのが妥当です。

引用元: 矢野 啓介 “WEB+DB PRESS plusシリーズ [改訂新版]プログラマのための文字コード技術入門" ページ位置85%

参考資料

*1:歴史的経緯により Windows-31J の波ダッシュは FULLWIDTH TILDE に対応していますが、適切な変換先ではないと考えられます。 ref: 波ダッシュはチルダではない

*2:参考: 髙﨑さん、草彅さん、𠮷田さん、あなたの名前はこうして化ける - Qiita

2ヶ月間の育児休暇を取得しました

こんにちは。

私は、2021年8月に妻が第1子出産をしたため、10月から2ヶ月間の育児休暇を取得しました。 男性の育児休暇取得は、所属のアジャイル事業部で初とのことで、育児休暇をテーマに記事にしてみます。

なぜ取得したか

育児休暇を取得した理由は、下記の2点が大きいです。

  • 出産後の3ヶ月間は育児が特に大変であるため
  • 育児休業給付金を利用できるため

子供は生後3ヶ月くらいまでは、2~3時間おきに泣き出すため、夫婦で協力して面倒を見ないと、睡眠時間ををまともに確保することができません。十分な睡眠ができないと、心身ともに脆くなってしまうので、この事態は避けたいなと考えていました。

また、国の育児休業給付金はなかなか良い制度です。少なくとも無給になることはないうえ、生活するには十分な額であるため、取得を決意できました。

Q&A~育児休業給付~

取得するまで

取得するまでに気をつけたことは、「早めに上長に育児休暇の時期と期間を相談すること」です。私の場合、2021年の3月末に相談しました。その際に、「妻は2021年9月出産予定であり、2021年10月から2ヶ月ほど取得したい」旨を相談しました。相談した場で快諾してもらえて、とても安心しました。

取得してどうだったか

2ヶ月間の育児休暇を振り返ると、取得してとても良かったです。

育児の大変さは理解していたつもりでしたが、実際に経験してみると想定の数倍大変でした。 おむつ替え、授乳、哺乳瓶消毒、沐浴、寝かしつけなどやるべきことがたくさんある中で日常の家事をこなす必要があり、1人でやるには至難の業だと思いました。育児休暇を取得できたので、夫婦2人で協力して育児・家事を分担できました。

また、いくつものイベントごとに立ち会うことができました。「お宮参り」や「お食い初め」の催事はコロナ禍を考慮し、平日に開催できました。また、はじめての予防注射に立ち会えたのも思い出深いです。

余談ですが、子供の睡眠サイクルが整うにつれ時間のゆとりが生まれ、毎日少しの個人時間を取れたのも良かったです。私は、Linuxやシステムプログラミングに興味が出て、この周辺をインプットしていました。

最後に

所属プロジェクトの方々含め、育休を快諾してもらえて感謝しています。

私は、2021年12月から職場に復帰しました。育児休暇を取得していた2ヶ月間は業務をしていなかったため、スムーズに適応できるか少し不安だったものの、始めの2週間で徐々に慣れたように思います。

最後に、永和システムマネジメントは、仕事と育児を両立したいエンジニアを絶賛募集しています。

agile.esm.co.jp

Rails / OSS パッチ会オンライン 2022年1月のお知らせ

2022年1月の Rails / OSS パッチ会を 1月19日(水)に Discord でオンライン開催します。

この会をひとことでいうと、日頃のお仕事で使っている Rails をはじめとする OSS について、upstream にパッチを送る会です。

会には Ruby と Rails のコミッターである顧問の a_matsuda もいますので、例えば Rails に送るパッチのネタがあるけれど、パッチを送るに適しているかの判断やパッチを送る流れが悩ましいときなど a_matsuda に相談して足がかりにするなどできます。

開催時間は 17:00-19:00 となりますがご都合のあう方はぜひご参加下さい。

Discord の Rails/OSS パッチ会サーバーへの招待 URL は以下です👇

discord.gg

パッチ会では、先月リリースされた Rails 7.0 や Ruby 3.1 に関する話題などが旬のネタとしてありそうです。

これからパッチ会に参加してみようという方も、ぜひどうぞ。Discord でお会いしましょう。


永和システムマネジメントでは、Ruby とアジャイルソフトウェア開発を通じてコミュニティと成長したいエンジニアを絶賛募集しています。

agile.esm.co.jp

CI fail は突然に ~CircleCI の cimg が compose v2 を使うようになって~

メリークリスマス! yucao24hours です。

こちらがおそらく 2021 年最後のアジャイル開発ブログとなります(予定)。今年も一年、私たちのブログに目を通していただきありがとうございました。

さて、今回は自分が関わっている数プロジェクトで発生した、突然の CI のふるまいの変化とその原因について調べた結果を書いてみます。

ことの発端

去る 12 月 8 日の夕方のことです。 GitHub にてとある Pull Request をマージしたところ、main ブランチにて CI が fail したことに気づきました。 CircleCI を使っているプロジェクトでしたので CircleCI の実行画面を見に行ったところ、以下のようなエラーが出ていました。

/bin/bash: line 5: docker-compose: command not found

Exited with code exit status 127
CircleCI received exit code 127

ちなみに、対応する .circleci/config.yml の記述は以下のとおりです。

- run:
    name: Create services
    command: |
      if [ -f /tmp/cache/images.tar ]; then
        docker load -i /tmp/cache/images.tar
      fi

      docker images
      docker-compose up --build --no-start --quiet-pull rails

つまり、最後の行の docker-compose コマンドが見つからないと言われているのです。

つい昨日まではなにも異常なく実行できていたし、CircleCI の設定も変更していないのに... こんなことってある!?

何が起きたか

状況から察するに、私たちの repository とは直接関係ないどこかでなにかの変更が入ったとみるのが妥当でしょう。

cimg のベースイメージの repository に、まさに今起きていることに関係していそうな issue が。

https://github.com/CircleCI-Public/cimg-base/issues/133

Starting with this month's base image release, Docker Compose is on v2 instead of v1. So instead of running docker-compose you'd run docker compose.

なるほど。今回起きたことに対する答えが、ほぼそのまま書いてありました。

が、せっかくなのでここで一旦立ち止まって、もう少し詳しく各要素のことを調べてみましょう。

cimg とは

まず、今見てきた cimg とはなんなのでしょうか。

cimg は、CircleCI があらかじめ用意している CI/CD 向けの Docker image です。

もともと CircleCI が開発・管理していた、 circleci/ というプレフィックスのついた公式 image を置き換えるものとしてリリースされています。

circleci/ からはじまるこのレガシーな CircleCI image は、2021 年 12 月 31 日で廃止となります。もうすぐですね。もしご自分の .circleci/config.yml に circleci/ruby などと書かれている場合は、移行が必要ですのでお忘れなく!

circleci.com

Git やその他ライブラリのインストールに加え、アプリケーションを動かすのに必要な動作環境をセットアップした状態の image がさまざまな言語向けに用意してあります。

そのため利用者は各 image を .circleci/config.yml に記載するだけで、すぐに CI/CD ワークフローを構築することができるというわけです。

私たちのプロジェクトでは cimg/base を使い、その Docker コンテナ上で Rails アプリケーション用の Docker コンテナを起動するようにしています。

実際に使いはじめてしばらく経ちますが、このおかげで config.yml の記述量が大幅に減りスッキリ見やすくなりましたし、CI に関するメンテナンス負荷がとても軽くなったと感じています。

circleci.com

なお、今回 issue を見に行った cimg/base は、その名の通り各種言語向け image のベースとなる image です。

circleci.com

先ほど見た issue のコメントを再掲しますが、

Starting with this month's base image release, Docker Compose is on v2 instead of v1. So instead of running docker-compose you'd run docker compose.

とのことで、このベースイメージ(のうち当時の stable タグがついたイメージ)で使われる Docker Compose のバージョンが v1 から v2 になったとあります。 1

では、Docker Compose v2 でどのような変更がなされてこのエラーが出たのでしょうか。

docker-compose が docker compose に

Docker Compose v2 についてのすべてを紹介するのは今回の記事の主目的ではないので割愛しますが、今回修正が必要な点は、以下の内容に関連していそうです。

docs.docker.com

You can test the Compose V2 by simply replacing the dash (-) with a space, and by running docker compose, instead of docker-compose.

即ち、 .circleci/config.yml に記載されている docker-compose は、 docker compose に変える必要があるということがわかりました。

コンテナ名の表記が変わった

さて、以上で対応完了!と思って再度 CI を run させたところ、今度は

docker: Error response from daemon: No such container: myapp_rails_1.

というエラーが出ました。

どうやらこれは、v2 で以下の変更が入ったことによるもののようです。

github.com

もともと docker-compose が自動生成していた project_service_number といったコンテナ名は hostname の表記規則に違反しているものであったため、v2 に移行する機会にこの変更が追加されることになったようです。

よって、 .circleci/config.yml に

- run:
    name: Copy rails dependencies
    command: |
      mkdir -p /tmp/cache
      docker cp myapp_rails_1:/usr/local/bundle/. /tmp/cache/bundle

のようにアンダースコア区切り(旧表記)のコンテナ名を記載している場合、 myapp-rails-1 のようにハイフン区切りに修正する必要があったのでした。

まとめ

今回は CircleCI の cimg がきっかけとなって、Docker Compose v2 の変更について少し触れることができました。

どうしてこういう事が起きるようになったのか?を確認するために、issue や Pull Request 、コード等を探索して影響箇所を突き詰めるのはとても楽しいですね!

この調子で来年も、楽しいエンジニアライフを過ごせたらいいな~と思います。

それではみなさまも、よいお年をお迎えください :D


  1. 本当は https://circleci.com/developer/images/image/cimg/base#image-tags を見れば、どのタグのイメージにバージョンいくつの Docker Compose が入っているかを調べられるはずなのですが、今見るとちょっとおかしな文字列が入っているようにみえて、ドキュメントの自動更新がうまくいっていないのかな… という状態です。

Railsアプリケーションでフロントエンドのパフォーマンス改善:初歩編

この記事は ESM Advent Calendar 2021 - Adventar の21日目の記事です。

こんにちは、swamp09です。

とあるRailsアプリケーション開発プロジェクトで、フロントエンドのパフォーマンスを計測するのに Lighthouse を使いました。パフォーマンスの項目を見てみると「レンダリングを妨げるリソースの除外」が指摘されており中身をみるとapplication.jsで多くの無駄が発生しているようです。Railsアプリケーションでは application.js が肥大化してしまうケースがたまに見られますね。 Eliminate render-blocking resources を読むと、まずはカバレッジを見て必要なリソースだけを読み込むこと、次いでスクリプトがレンダリングブロックしないようにするとのことです。ある画面ではGoogle ChromeのDevToolsのカバレッジを見るとapplication.jsは91%使用していないという結果になっていました、なんと…。未使用のコードが多すぎるという問題は画面ごとに必要なJSだけ読み込むように地道にリファクタリングしていくしかないですね…。

レンダリングブロックを除く

スクリプトがレンダリングブロックしないようにするには、application.jsを読み込みをasyncdeferにします。 これらがないとJavaScriptがHTMLパーサーをブロックしてしまうのでパフォーマンスに悪影響があります。asyncdeferの違いは、 ブラウザの仕組み: 最新ウェブブラウザの内部構造 の スクリプトとスタイル シートの処理順序 によると、asyncがHTMLのパースとは別スレッドで処理され、deferはHTMLのパースが終わってから順番に処理されるということのようです。application.jsではライブラリの読み込みなどを行っており、読み込む順序を意識する必要があったのでdeferを使うことにして、application.jsの読み込みを javascript_include_tag 'application', defer: true のように変更しました。

ちなみにパーサーブロックとレンダリングブロックは微妙に違うようなのですが、MDNではパーサーブロックと書いてあり Eliminate render-blocking resources ではレンダリングブロックと書いてあります。あんまり気にしなくて良いのかな…。

インラインスクリプトの対応が必要だった

さておき、よしこれでパーサー|レンダリング ブロックしなくなって良くなったな!!と思いきや問題が起き、application.jsで読み込んでいるライブラリに依存しているコードがインラインスクリプトにありそれらが動作しなくなってしまいました。じゃあインラインじゃなくして全部JSファイルにしてdeferで読み込めばいいか!!!と思ったのですがそんなことはなく、レンダリングを妨げる JavaScript を削除する によると、小さいコードならインラインが最適なケースがありなんでもかんでもJSファイルに切り出すのは良くないようです。

インラインスクリプトのままでJSファイルにあるライブラリ読み込みの遅延を待つために、DOMContentLoadedでライブラリの読み込みを待ったあとで処理を開始するようにします。defer によると「defer 属性の付いたスクリプトは、スクリプトが読み込まれて評価が完了するまで、 DOMContentLoaded イベントの発生が抑制されます。」とのことなのでdeferでライブラリを読み込み DOMContentLoadedイベントを待てば安心ですね。

おまけ

余談なのですが、最近リリースされたRails 7.0ではデフォルトでimportmap を使用しています。importmapはES Modulesを読み込むもので、ES moduleの読み込みはクラシックスクリプトとは別でモジュールスクリプトとなっており、defer は「defer 属性はモジュールスクリプトには効果がありません。」とのことなのでasyncしか使えないようです。deferとの違いを意識していないとimportmapを使うとき処理順序が起因のエラーが起きるのかもしれません。