倒れるときは前のめり。

カレーが好きです。

シェルを zsh から fish に乗り換えたらモノクロームだった日常がカラフルに色づきはじめた

Tl;dr

控えめに言って最高でした。

やったこと

コミットを追っていってください :pray:

Introduce fish

平成生まれの非 POSIX シェル。 Friendly Interactive SHell の略称らしい。

bashzsh とは異なり、オートコンプリートなどの便利機能が設定不要でいきなり使えることが大きな特徴。

brew install fish

chsh -s でログインシェルにはせずにターミナル側でよしなに fish を呼び出してやる。 iTerm2 の場合は Preferences > General > Command から /usr/local/bin/fish を指定してやれば OK。

設定ファイル

$HOME/.config/fish/config.fish に記述する。ターミナル上から fish_config と入力すると Web ブラウザ上から fish の設定を変更できる。

f:id:cheezenaan:20180625185745p:plain

fisherman でプラグイン管理

curl -Lo ~/.config/fish/functions/fisher.fish --create-dirs git.io/fisherman

zsh でいうところの zplug 的なやつ。使い勝手的には gem とか bundler っぽい。

fzf と ghq を fish でも使えるようにする

zplug 経由で ghq を導入できたが fisherman には対応するものがないので素直に homebrew からインスコする。

brew install ghq fzf

fish 上から fzf や ghq を操作するプラグインがあるのでよしなにインスコ

fisher decors/fish-ghq fzf

自動補完

fish_update_completions

man コマンドから補完を自動生成してくれるらしい。なにこれやばい。

環境変数の定義

bashzshexport PATH=/usr/local/bin:$PATH としていたのを set -x PATH /usr/local/bin $PATH と変更していく。

環境変数の定義をしていくと、セッションを呼び出すたびに PATH がどんどん長くなる問題にぶち当たった。 set -x の使い方に問題があったので、 fish.config の末尾にこんなワンライナーを付け足して解消した。

# ./config/fish/config.fish

# Remove redundant paths
set -x PATH (echo $PATH | tr ' ' '\n' | sort -u)

スペース区切りで並んだ $PATH から sort -u で重複を削除している。

エイリアスの設定とか

zsh で使用していたエイリアス集を別ファイルから読み込ませた。alias foo='bar'alias foo 'bar' となっただけで瞬殺で移行できた。

tmux とのつなぎこみ

reattach-to-user-namespace で使用するシェルを fish に変更する。

# ~/.tmux.conf

# ...

set-option -g default-command "reattach-to-user-namespace -l $(which fish)"

ベンチマークしてみた

$ for i in (seq 1 5) ; time fish -ic exit ; end
        0.12 real         0.08 user         0.04 sys
        0.11 real         0.08 user         0.03 sys
        0.11 real         0.07 user         0.04 sys
        0.11 real         0.07 user         0.03 sys
        0.11 real         0.08 user         0.04 sys

な ん で す か 、 こ れ 。

今までの努力はなんだったのか。

所感

手持ちの設定ファイルの量にもよるけど、シュッとデフォルト設定のまま試してみてよさそうなら移行を考えてもいいのでは。自分の場合はそこまでがっつりカスタマイズして zsh 固有のなにかを使ってたわけでもないし、とりあえず ghqfzf が気持ちよく使えればよかったので、今回を機に fish へ移行してみて概ね満足している。

pros

  • zsh に比べて設定ファイルの記述量が圧倒的に少なくて済む
    • ゼロコンフィグの波をシェルにも感じる
  • しかも速い!!!!!
    • スピード is 大正義
  • 日本語のドキュメントが充実してるので割と安心

cons

参考資料

fish 全般

rbenv まわり

プロンプトのカスタマイズ

環境変数とか

zsh の設定ファイルを整理して GitHub 上で管理することにした

f:id:cheezenaan:20180624111652p:plain

n 番煎じ感しかないけれど、半年に 1 回は訪れる「開発環境を根本から見直したい欲求」に従うままやってみた。

はじめに

  • .zshrc が育ってきたせいか zsh の起動が遅くなってきた
    • zsh の設定ファイル見直してダイエットしたい
  • これまでは Mackup で Dropbox 上に設定ファイルをバックアップしていた

Tl;dr

$ git clone git@github.com:cheezenaan/dotfiles.git ~/dotfiles
$ cd ~/dotfiles
$ sh /setup.sh
# That's all!

やったこと

ディレクトリ構造を見直した

$ tree ~/dotfiles -a -I .git
.
├── .zsh
│   ├── .zshenv ... 環境変数(PATH や EXPORT) を定義
│   ├── .zshrc ... rc/ 以下の設定ファイルを読み込ませたり autoload を定義してる
│   └── rc
│       ├── aliases.zsh ... もろもろのエイリアス集
│       ├── keybindings.zsh ... カスタムキーバインド集
│       ├── options.zsh ... setopt をまとめて定義
│       ├── plugins.zsh ... zplug で使用するプラグイン集
│       └── variables.zsh ... もろもろの変数集
├── .zshenv ... ただのエントリーポイント
├── README.md
└── setup.sh ... ホームディレクトリにシンボリックリンクを貼るシェルスクリプト

/dotfiles 直下の .zshenv$ZDOTDIR を定義して source $ZDOTDIR/.zshenv をするだけで、設定の実態は /.zsh/ 以下のファイルで行うようにした。

zplug でプラグイン管理する

prezto は使い勝手こそよかったのだけど基本全部入りなため、シェルの起動に時間がかかりがちだった。必要なものだけをプラグインとして読み込ませるようにすれば多少はマシになるのでは…という思いからプラグインマネージャーの導入を検討。 prezto などの外部ライブラリのプラグインやテーマも管理できる点に惹かれて zplug を採用。 README が充実してるのも好印象。

自分が入れているプラグインの一覧は以下の通りで、 prezto からは autocompletion 用の module を持ち込んだ。あれがないと生活できない身体になってしまったのでな…。

# .zsh/rc/plugins.zsh

# Clone zplug if not exist
if [[ ! -d ~/.zplug ]]; then
  git clone https://github.com/zplug/zplug ~/.zplug
fi

source ~/.zplug/init.zsh

# zsh plugins
zplug "zsh-users/zsh-autosuggestions"
zplug "zsh-users/zsh-completions"
zplug "zsh-users/zsh-syntax-highlighting", defer:2

zplug "junegunn/fzf-bin", as:command, from:gh-r, rename-to:fzf
zplug "mafredri/zsh-async", from:github
zplug "mollifier/anyframe", lazy:true
zplug "motemen/ghq", from:gh-r, as:command, rename-to:ghq, lazy:true
zplug "sindresorhus/pure", use:pure.zsh, from:github, as:theme

zplug 'modules/utility', from:prezto
zplug "modules/completion", from:prezto

# Install uninstalled plugins
zplug check --verbose || zplug install
zplug load

peco から fzf に乗り換えた

これまで peco を使っていて不満もなかったけど、見た目がより好みだったのでこの機に乗り換えてみた。 fzf もまた README が非常に充実しているのがよい。 vim 用のプラグインもあるみたいだけど今回は導入見送り。

もともと anyframe を使用していたおかげで peco からの移行もスムーズ。

シンボリックリンクを貼るシェルスクリプトを用意した

dotfiles.git のシンボリックリンク$HOME 以下に貼り付けるかんたんなスクリプトをシュッ書いた。

#!/bin/sh

basepath=$(
   cd $(dirname $0)
   pwd
)

if [ ! -d ~/.zsh/rc ]; then
    mkdir -p ~/.zsh/rc
fi

ln -sf $basepath/.zshenv ~/.zshenv

for rcfile in .zsh/rc/*; do ln -sfv $basepath/$rcfile ~/.zsh/rc; done
for zfile in .zsh/.z*; do ln -sfv $basepath/$zfile ~/.zsh; done

ベンチマークをとった

$ for i in $(seq 1 5) ; do time ($SHELL -l -ic exit) ; done
( $SHELL -l -ic exit; )  0.43s user 0.46s system 102% cpu 0.867 total
( $SHELL -l -ic exit; )  0.41s user 0.44s system 103% cpu 0.822 total
( $SHELL -l -ic exit; )  0.44s user 0.48s system 100% cpu 0.919 total
( $SHELL -l -ic exit; )  0.44s user 0.48s system 96% cpu 0.954 total
( $SHELL -l -ic exit; )  0.43s user 0.47s system 102% cpu 0.877 total

整理前は 2 ~ 3 秒かかっていたことを考えると、1 秒未満ならまぁ許容範囲…だと思いたい。

まとめ

homebrwe で zsh さえインストールしさえすれば、 git cloneスクリプトを実行するだけで、シェル周りの環境が整うようになってとても気持ちがいい。今までなんとなくで書いていた .zshrc まわりの設定ファイルの意味や読み込み順が理解できたし、役割ごとにファイルを分割したことで少しはメンテナンスしやすくなったと思う。頃合いを見て vim やほかの CLI ツールの設定ファイルもも dotfiles リポジトリに移していきたい。

参考資料

zsh 全般

zplug

fzf / peco + anyframe

Docker for Mac と Dinghy のパフォーマンスを比較してみた

はじめに

Mac x Docker 環境下で Rails アプリを開発している際にどれくらい性能差が出ているのか、n 番煎じであることは承知の上で実際に試してみた。

検証環境(ホスト)

検証環境(Docker)

Docker for Mac も Dinghy も共通して、 VM には CPU 2 コア、メモリーを 8GB 割り当てる。

For Mac

$ docker -v
Docker version 18.03.1-ce, build 9ee9f40

$ docker-compose -v
docker-compose version 1.21.1, build 5a3f1a3

stable 版だとディスクイメージのフォーマットに qcow2 を使用しているけど、今回の検証では raw に変更する。

vim ~/Library/Group\ Containers/group.com.docker/settings.json

diskpPath の拡張子 qcow2raw に変更したら Docker.app を再起動する。 手っ取り早く変えるには↓のワンライナーを叩く。

sed -i -e 's/qcow2/raw/g' ~/Library/Group\ Containers/group.com.docker/settings.json

PreferencesDisk > Disk image location に記載されたパスの拡張子が raw になっていれば OK。

Dinghy

$ docker -v
Docker version 18.05.0-ce, build f150324

$ docker-compose -v
docker-compose version 1.21.2, build unknown

$ docker-machine -v
docker-machine version 0.14.0, build 89b8332

$ docker-machine inspect dinghy
{
    "ConfigVersion": 3,
    "Driver": {
        "IPAddress": "192.168.99.100",
        "MachineName": "dinghy",
        "SSHUser": "docker",
        # ...
        "CPU": 2,
        "Memory": 8192,
        "DiskSize": 20000,
        # ...
    },
  # ...
}

検証方法

Rails チュートリアルで作成していたサンプルアプリの RSpec を回す。

$ bundle exec rake stats --all
+----------------------+--------+--------+---------+---------+-----+-------+
| Name                 |  Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Controllers          |    291 |    218 |      10 |      34 |   3 |     4 |
| Helpers              |     81 |     63 |       0 |      12 |   0 |     3 |
| Jobs                 |      4 |      2 |       1 |       0 |   0 |     0 |
| Models               |    135 |     94 |       4 |      14 |   3 |     4 |
| Mailers              |     21 |     14 |       2 |       2 |   1 |     5 |
| Channels             |     12 |      8 |       2 |       0 |   0 |     0 |
| Libraries            |      0 |      0 |       0 |       0 |   0 |     0 |
+----------------------+--------+--------+---------+---------+-----+-------+
| Total                |    544 |    399 |      19 |      62 |   3 |     4 |
+----------------------+--------+--------+---------+---------+-----+-------+
  Code LOC: 399     Test LOC: 0     Code to Test Ratio: 1:0.0
$ docker-compose run --rm --service-port spring sh

して Docker コンテナに入ってから

$ bundle exec rails db:create db:migrate
$ for i in $(seq 1 10); do time bundle exec rspec --profile --format progress; done;

を実行した。とりあえず 10 回くらい回せばええやろ。

結果

数値の単位はいずれも秒(sec) です 。

for Mac

Dinghy time - real RSpec - Finished in RSpec - files took to load
Ave 39.86 34.44 4.58
Min 35.34 31.31 2.99
Max 51.09 40.98 12.98

Dinghy

Dinghy time - real RSpec - Finished in RSpec - files took to load
Ave 39.84 34.99 3.96
Min 35.55 29.73 3.47
Max 45.55 40.93 4.62

考察とまとめ

rspec コマンドの呼び出しから終了までにかかった実時間(time - real の項目)も RSpec の実行時間(RSpec - Finished in の項目)も、 Docker for Mac と Dinghy とで目に見えた差が生じなかった。

今回使用した Rails チュートリアルくらいの規模のアプリケーションであれば、 Docker for Macraw フォーマットを使用してボリュームマウントで cached オプションを指定してやれば、 Dinghy と同じくらいのパフォーマンスを発揮してくれる…と言ってよさそうなのかな。

実際にプロダクションで 5 年とかのスパンで運用しているようなそれなりに大きい規模の Web アプリケーションでどれくらいパフォーマンスに差が生じるかは今後の課題ということで、どなたかお願いします(他力本願

参考資料

TypeScript + Webpack4 な素振り環境を作った

今さらながら TypeScript で静的型付けに入門したくなったので、サクッと素振り環境を作ってみた。

tl;dr

docker-compose run --rm yarn install
docker-compose up

ゴール

  • ローカル PC に依存せずに TypeScript を書いてコンパイルできる環境を作る
    • node のバージョン違いで yarn install 落ちたりつらい
    • yarn コマンドも Docker コンテナ上で叩く(Mac 上で Docker 動かすと I/O 遅くてつらいけど)

Webpack 4

docker-compose run --rm node yarn add -D webpack webpack-cli webpack-serve

webpack-dev-server の後継らしい webpack-serve を導入してみた。

TypeScript さえビルドできればいいので余計な Loader とかプラグインを入れてないからかもしれないけど webpack.config.js がわりとスッキリしていてよい。

Introduce TypeScript

docker-compose run --rm node yarn add -D typescript ts-loader
docker-compose run --rm yarn tsc --init

生成された tsconfig.jsonStrict Type-Checking OptionsAdditional Checks をすべて有効にしておく。

Introduce Linter / Formatter

TSLint を使うか ESLint に TypeScript のパーサーを突っ込んで使うか選択肢があるらしいんだけど、 ESLint のほうが設定できるルールが多いらしいので後者を採用(要調査)。

Vue.js 向けの ESLint プラグインを公式が用意しているのも、なにげに評価高い。

ESLint

docker run --rm node add -D eslint eslint-conifig-airbnb-base eslint-plugin-import typescript-eslint-parser eslint-plugin-typescript

React(JSX) 抜きで普遍的に使える構成にしたかったので eslint-config-airbnb ではなく eslint-config-airbnb-base を導入した。 eslint-plugin-typescript はおまけ。

Prettier

docker run --rm node add -D prettier eslint-config-prettier eslint-plugin-prettier

問答無用で入れる。 ESLint と併存させるとより幸せになれる。

  • eslint-config-prettier は ESLint 上で Prettier と競合するフォーマット関連のルールを無効にしてくれる
  • eslint-plugin-prettier は Prettier のルールを ESLint が読み込んで、 ESLint 上で Prettier を実行してくれる

.eslintrc

// .eslintrc
{
  "env": {
    "browser": true
  },
  "extends": ["airbnb-base", "plugin:prettier/recommended"],
  "parser": "typescript-eslint-parser",
  "plugins": ["typescript", "prettier"],
  "rules": {
    "no-undef": "off",
    "no-unused-vars": "off",
    "import/extensions": "off",
    "import/no-unresolved": "off",
    "import/prefer-default-export": "off",
    "prettier/prettier": "error"
  }
}

prettier 以外に rules に追加しているのは以下:

  • "no-undef": typescript-eslint-parser で未対応なので無効化
  • "import/extensions", "import/no-unresolved": import するときに .ts および .tsx を解釈してくれないので無効化
  • "import/prefer-default-export": 1 ファイルで 1 つだけ export const するときに不便なので消した

参考

Tachikoma.io でライブラリ依存関係を自動チェックさせてみた

はじめに

  • 素振り用の Rails 開発環境で、 Gemfile の脆弱性アラートが出現していた
  • とはいえ手動で bundle update する仕事つらい
  • tachikoma.io を使うと自動で依存関係をチェックして差分があれば PR を作成してくれるらしいので入れてみる(イマココ

Tachikoma.io を入れてみる

Tachikoma.io がライブラリ依存地獄を取り除いてくれます。あなたがやりたいのは依存のアップデートですか?アプリケーションを書くことですか?

Tachikoma.io のできることは Tachikoma.io に任せて、あなたはあなたのやりたいことをやろう。

なにこれ素敵…!ということで即導入。GitHub 連携して、適用したいリポジトリにチェックを入れるだけ。3分クッキングよりもかんたん。

設定ファイルを追加する

Configuration - Tachikoma.io

Rails アプリケーションに Tachikoma.io を使用したい場合は、明示的にストラテジーbundler にしてやる必要がある。

ルートディレクトリに .tachikoma.yml を置いて、

# .tachikoma.yml
strategy: 'bundler'

あるいはターミナル上から ↓ の command を叩く。

# Download setting about strategy for bundler
$ curl -O http://tachikoma.io/example/bundler/.tachikoma.yml

使用例

こんなぐあいに PR を作成してくれる。CI が通っていれば即 master にマージ。所要時間 n 分。優しい世界。

Rails on Docker な素振り環境の Rails バージョンアップをした(v5.1.4 => v5.2.0)

タイトルの通り。所詮サンプルアプリケーションだし楽勝かな…と思ったが意外なところで躓いたので、備忘録として残しておく。

5.2.0 に上げる

Gemfile の Rails のバージョン指定を ~> 5.2.0 に修正して、

bundle update rails

からの

bin/rails app:update

いろいろとファイルが書き換えられてしまうので、よしなに git diff しながら目視で確認していく。

テストが落ちる

覚悟はしていたのだけど、Rails のバージョン上げたらテストが落ちるようになった。エラーメッセージを読みつつ binding.pry を適宜はさみながらデバッグして直していった。

Helper Spec が落ちる

たとえばこれ

https://github.com/cheezenaan-sandbox/sample_app_rev4/commit/1925113

Failures:

  1) UsersHelper gravatar_for when user is present when email is present
     Failure/Error: end

     ArgumentError:
       Can't resolve image into URL: undefined method `to_model' for #<Pathname:0x0000556238b7cd58>
       Did you mean?  to_yaml
     # ./app/helpers/users_helper.rb:11:in `gravatar_for'
     # ./spec/helpers/users_helper_spec.rb:7:in `block (3 levels) in <top (required)>'
     # ./spec/helpers/users_helper_spec.rb:31:in `block (5 levels) in <top (required)>'
     # -e:1:in `<main>'
     # ------------------
     # --- Caused by: ---
     # NoMethodError:
     #   undefined method `to_model' for #<Pathname:0x0000556238b7cd58>
     #   Did you mean?  to_yaml
     #   ./app/helpers/users_helper.rb:11:in `gravatar_for'

v5.2.0 へ上げた際に落ちるようになった。どうやら image_tag が怪しそうので git diff v5.1.4..v5.2.0 actionview/lib --stat しながら Rails のコードを追っていくと、AssetTagHelper#image_tag に修正入ってた。たぶん ActiveStorage もろもろの対応の影響なんだろう。

# actionview/lib/action_view/helpers/asset_tag_helper.rb
def image_tag(source, options = {})
  options = options.symbolize_keys
  check_for_image_tag_errors(options)
  skip_pipeline = options.delete(:skip_pipeline)

  # このへんが v5.2.0 へ向けて追加された箇所っぽい
  options[:src] = resolve_image_source(source, skip_pipeline)

  if options[:srcset] && !options[:srcset].is_a?(String)
    options[:srcset] = options[:srcset].map do |src_path, size|
      src_path = path_to_image(src_path, skip_pipeline: skip_pipeline)
      "#{src_path} #{size}"
    end.join(", ")
  end

  options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
  tag("img", options)
end

ref. https://github.com/rails/rails/compare/v5.1.4...v5.2.0#diff-d36c3bf0d0a61b0b5dbae953a4852ad2R332

image_tag -> resolve_image_sourceからの、 polymorphic_url(source) へ進んだ先で to_model に反応できなくなって落ちるようになったようす。

# actionview/lib/action_view/helpers/asset_tag_helper.rb
def resolve_image_source(source, skip_pipeline)
  if source.is_a?(Symbol) || source.is_a?(String)
    path_to_image(source, skip_pipeline: skip_pipeline)
  else
    polymorphic_url(source) # <= これ
  end
rescue NoMethodError => e
  raise ArgumentError, "Can't resolve image into URL: #{e}"
end

ref.

なので、明示的に Pathname#to_s してやった。ここまでで System Spec 以外のテストが通るようになった。

bin/rspec spec/helpers spec/models spec/requests

System Spec が倒せない

たとえばこんなかんじ。

~/s/g/c/c/sample_app_rev4 » docker-compose exec spring bin/rspec spec/system/static_pages_spec.rb --fail-fast               bump_rails_v5.2 ✔
Running via Spring preloader in process 103

StaticPages
  Home page
Capybara starting Puma...
* Version 3.11.4 , codename: Love Song
* Min threads: 0, max threads: 4
* Listening on tcp://127.0.0.1:40933
    should have title "Ruby on Rails Tutorial Sample App" (FAILED - 1)

Failures:

  1) StaticPages Home page should have title "Ruby on Rails Tutorial Sample App"
     Failure/Error: it { is_expected.to have_title full_title }
       expected "" to include "Ruby on Rails Tutorial Sample App"

     [Screenshot]: tmp/screenshots/failures_r_spec_example_groups_static_pages_home_page_example_at___spec_system_static_pages_spec_rb_13_223.png


     # ./spec/system/static_pages_spec.rb:13:in `block (3 levels) in <top (required)>'
     # -e:1:in `<main>'

Finished in 4.12 seconds (files took 4.18 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/system/static_pages_spec.rb:13 # StaticPages Home page should have title "Ruby on Rails Tutorial Sample App"

Coverage report generated for RSpec to /app/coverage. 17 / 45 LOC (37.78%) covered.

なるほど puma が使われるようになった。

tcp://127.0.0.1:40933…🤔ポート番号がテスト回すたびに変わってるな。このポート番号って Docker で穴を開けてやらんといかんのかな🤔

いろいろと試行錯誤した結果、、Capybara.server_host を明示的に "0.0.0.0" に指定した上で Capybara.server:puma ではなく :webrick にすることで動いた。

# spec/support/capybara.rb
(略)
Capybara.server_host = "0.0.0.0"
# TODO: Use puma as Capybara.server
Capybara.server = :webrick

ref. https://github.com/cheezenaan-sandbox/sample_app_rev4/pull/29/commits/74b13c57

いまいち納得できてないので要調査。

おわりに

Rails バージョンアップ、 いやおうなく Rails 本体のコードを読み進めていくことになるので勉強になるし、これくらいのちっちゃい環境ならいくらでも壊れても問題ないので、積極的にバージョン上げて経験積んでいきたい。

あとは Tachikoma.io で bundle update 自動化していきたい。

JavaScript だけでなくスタイルシートと画像ファイルも webpack 管理下に置いて、 Asset Pipeline から完全にサヨナラした

よくある Rails アプリケーションから JavaScript のビルドを webpack に移譲した前回に続いて、今回はスタイルシートや画像ファイルといった静的アセットもすべて webpack の管理下に置くことにした。これによりフロントエンド管理を Rails が提供する Asset Pipeline のしくみから webpack に完全移行できた(と思っている)。

これまでのあらすじは以下のリンクから。

cheezenaan.hatenablog.jp

Tl;dr

github.com

例に漏れず Pull Request を作成しているので、物好きな人はコミットを追ってたらいいと思う。

構成

~/s/g/c/c/sample_app_rev4 » tree frontend -I node_modules
frontend
├── config
│   └── webpack.config.js
├── package.json
├── postcss.config.js
├── src
│   ├── images
│   │   └── rails.png
│   ├── javascripts
│   │   └── application
│   │       ├── Hello.js
│   │       └── index.js
│   └── stylesheets
│       ├── _common.scss
│       ├── _footer.scss
│       ├── _header.scss
│       ├── _mixin.scss
│       └── application.scss
├── yarn-error.log
└── yarn.lock

6 directories, 13 files

スタイルシートのバンドル

$ docker-compose exec node yarn add -D node-sass style-loader css-loader sass-loader postcss-loader autoprefixer extract-text-webpack-plugin

webpack で sass を扱えるようにした。スタイルシート関連の loader が乱立しており当初は混乱したけど、以下の URL が理解の助けに役立った。

qiita.com

css-loader は CSS 間の依存関係解決、 sass-loader や postcss-loader は CSS へのコンパイルを行っている。style-loader はバンドル時に CSS の内容を style タグとして出力するのだけど、extract-text-webpack-plugin を使うと style タグに出力する内容を別途 CSS ファイルとして生成できる。あと autoprefixer がコンパイル時にベンダープレフィックスを自動で追加してくれるので控えめに言って最高。

bootstrap-sass を webpack で管理する

$ docker-compose exec node yarn add bootstrap-sass

bootstrap-sass を webpack で管理する。

// frontend/src/stylesheets/index.scss
@charset "utf-8";

// ...
@import './_mixin.scss';

// ...
// frontend/src/stylesheets/_mixin.scss
@charset "utf-8";

@import '~bootstrap-sass/assets/stylesheets/bootstrap-sprockets';
@import '~bootstrap-sass/assets/stylesheets/bootstrap';

$gray-medium-light: #eaeaea;

bootstrap-sprockets からにじみ出てくる投げやり感。。

画像ファイルのバンドル

$ docker-compose exec node yarn add -D file-loader

file-loader 導入にあたりファイルの出力場所や 読み込み先の設定で躓いたのだが、以下のページが大変参考になった。

qiita.com

エントリーポイントの整理

Rails 側で使用するテンプレートファイルの構造とバンドルファイルの関係を揃えてみた。たとえば、app/views/static_pages/home で使用するアセット群は、public/assets/frontend/static_pages/home という名前のエントリーポイントでバンドルして、

# app/views/layouts/application.html.erb
<%= stylesheet_link_tag frontend_asset_path("layouts/application.css"), media: 'all' %>
# app/views/static_pages/home.html.erb
<%= javascript_include_tag frontend_asset_path("static_pages/home.js) %>

という具合で呼び出す。画像ファイルを Railsimage_tag で読み込むためだけに javascript_include_tag しているのが残念ポイントではある。

Asset Pipeline とサヨナラする

アセットパイプライン | Rails ガイド を参考に、 sass-rails と uglifer, coffee-rails を Gemfile から消し去る。ここまでの作業で app/assets 以下は完全に不要になるので、

$ rm -rf app/assets

で削除。これで Asset Pipeline のない世界線に到達できた。

参考: webpack.config.js

そこまでごちゃごちゃカオスになっていない…はず。

おまけ: Heroku デプロイ時に node でフロントエンドビルドを走らせる

qiita.com

以下 2(+1) つの準備が必要。

  • Heroku Buildpack for Node.js を導入する
  • ルートディレクトリにある package.json を編集する
  • bin/yarn を削除する

Heroku Buildpack for Node.js を導入する

package.json に記載されてる devDependencies のパッケージ群もインストールしてほしいので、NPM_CONFIG_PRODUCTION環境変数をいじる。

$ heroku buildpacks:add --index 1 heroku/nodejs
$ heroku config:set NPM_CONFIG_PRODUCTION=false

ルートディレクトリにある package.json を編集する

Heroku はルートディレクトリの package.json を認識して自動でビルドを走らせてくれるので、最低限必要な設定を追記する。

まずは engines に node と yarn のバージョンを記載する(記載しないとエラーで落ちる)。

// package.json
{
  "engines": {
    "yarn": "1.3.2",
    "node": "9.4.0"
  },
}

scriptspostinstall に定義した内容を Heroku が実行してくれる。--prefix オプションをつけることでビルド時の起点となるディレクトリを設定できる。意外と地味に便利。

// package.json
{
  "scripts": {
    "postinstall":
      "npm install --prefix frontend && npm run release --prefix frontend"
  },
}

bin/yarn を削除して assets:precompile へのフックを消す

いつからか Railsrake assets:precompile を叩く際に rake yarn:install もセットで走るようになった。 Heroku デプロイ時に node 側でビルド→ Railsassets:precompile と2回ビルドが走るようになってしまい完全に「余計なお世話」である。bin/yarn を消し去ることで assets:precompile へのフックもなくなった。

ref. https://github.com/rails/rails/blob/master/railties/lib/rails/tasks/yarn.rake#L3-L8

所感

「手段の目的化」が若干否めなかったが、多少なりとも webpack とは仲良くなれたと思っている。世間やネットの情報が言うほど設定ファイルがカオスになるわけではなかったし、あの膨大なプラグインや設定にも意味があることが理解できたのが収穫。なによりフロントエンドの依存管理を Rails(Asset Pipeline)から切り離せたので、もし仮にこの先 webpack に次ぐ新たなツールが現れたとしても Rails 側への影響を抑えて乗り換えられるようになったし、もっというとサーバサイドの開発言語を Rails 以外にスイッチしたり API / クライアントの分離…という選択肢も見えてきた。

webpacker は…機会があったらまた別にさわろう。