Tailwind on Rails

## 2020/1/15追記
Rails公式から、TailwindをRailsに組み込むためのgemが出たのでこっちの方が良いかもです。
https://github.com/rails/tailwindcss-rails

## なぜTailwind on Rails?
- クラス名を決める必要がなくなる
- クラス名の衝突がなくなり、BEMやCSS設計から開放される
- デザインの修正により不要になったCSSが残ってしまうことがなくなる
- どの要素にどんなスタイルが当たっているかがすぐにわかる
- カラーコードやフォントサイズ、ブレイクポイント等の統一性を保ちやすい
- ネット上に転がっているサンプルコードを気軽に取り入れやすい(他の人が書いたコードでもカスタマイズが楽)
- スタイルの修正のたびに`app/assets/stylesheets/任意のフォルダ/任意のファイル`を開く必要がなくなる

## 環境
Rails 6.0.3

## 導入
```zsh
$ yarn add tailwindcss
$ yarn tailwindcss init
$ mkdir app/javascript/css
$ touch app/javascript/css/tailwind.css
```
```app/javascript/css/tailwind.css
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
```
```app/javascript/packs/application.js
import '../css/tailwind.css';
```
```postcss.config.js
module.exports = {
  plugins: [
    //...
    require("tailwindcss"), //追加
    require("autoprefixer"), //追加
    require("postcss-preset-env")({
      autoprefixer: {
        flexbox: "no-2009",
      },
      stage: 3,
    }),
  ],
};
```
```diff:app/views/layouts/application.html.erb
-  <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+  <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 
```

##動作確認
```zsh
$ rails g controller test index
```
```config/routes.rb
root to: 'tests#index'
```
```erb:app/views/tests/index.html.erb
<div class="max-w-sm mx-auto bg-white shadow-lg rounded-lg overflow-hidden">
  <div class="sm:flex sm:items-center px-6 py-4">
    <img class="block mx-auto sm:mx-0 sm:flex-shrink-0 h-16 sm:h-24 rounded-full" src="https://randomuser.me/api/portraits/women/17.jpg" alt="Woman's Face">
    <div class="mt-4 sm:mt-0 sm:ml-4 text-center sm:text-left">
      <p class="text-xl leading-tight">Erin Lindford</p>
      <p class="text-sm leading-tight text-gray-600">Customer Support Specialist</p>
      <div class="mt-4">
        <button class="text-purple-500 hover:text-white hover:bg-purple-500 border border-purple-500 text-xs font-semibold rounded-full px-4 py-1 leading-normal">Message</button>
      </div>
    </div>
  </div>
</div>
```

##applyを使う
Tailwind CSSにはapplyという機能があり、複数のクラスをまとめて適用することができます。例えば同じボタンがあらゆる箇所に出現する場合、毎回`font-bold py-2 px-4 rounded bg-red-500 text-white hover:bg-red-700`などと書くのは大変なので、`btn`というクラスを指定するだけで上記のクラスを適用するためにapplyを使います。

### 【方法1】utilityの後に書く
この方法だと、widthのピクセル指定など、Tailwindのクラスでは表現できない部分のスタイルも指定できます。[公式](https://tailwindcss.com/docs/extracting-components/)では、applyを使う場合utilityの前に書けと言われていますが、railsで使う場合utilityの後に書かないとうまく動作しませんでした。
※クラス名がユニークになるように気をつけてください。

```app/javascript/css/tailwind.css
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
.btn {
  @apply font-bold py-2 px-4 rounded bg-red-500 text-white hover:bg-red-700;
  width: 300px;
}
```
```erb:app/views/tests/index.html.erb
<a href='#' class='btn'>ボタン</btn>
```

### 【方法2】helper関数で対応する
```zsh
$ rails g helper tailwind
```
```app/helpers/tailwind_helper.rb
module TailwindHelper
  def btn
    'fosnt-bold py-2 px-4 rounded bg-red-500 text-white hover:bg-red-700'
  end
end
```
```app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  helper TailwindHelper
end
```
```erb:app/views/tests/index.html.erb
<a href='#' class='<%= btn %>'>ボタン</btn>
```

##懸念点
TailwindCSSでは、想定されうるあらゆるユーティリティークラスが用意されているので、他のCSSフレームワークよりファイルサイズが大きいです。
この問題を、PurgeCSSという機能を使いビルド時に実際に使われているクラスに関するスタイルだけを抽出する方法で解決しています。

~~しかしRails上でTailwindを使う場合、PurgeCSSが使えません。(正確には設定方法がわかりません。分かる方がいたら教えてください。)
そのため通常よりファイルサイズが大きくなってしまいます。~~

解決しました↓↓↓

## Purge CSS
使っているクラスのスタイルだけを抽出して、CSSのファイルサイズを軽くします。

```zsh
$ yarn add @fullhuman/postcss-purgecss
```
```postcss.config.js
let environment = { // 設定を変数にしまう
  plugins: [
    require('tailwindcss'),
    require('autoprefixer'),
    require('postcss-import'),
    require('postcss-flexbugs-fixes'),
    require('postcss-preset-env')({
      autoprefixer: {
        flexbox: 'no-2009'
      },
      stage: 3
    }),
  ]
}

// 使っているクラスだけを抽出する設定
if (process.env.RAILS_ENV === "production") {
  environment.plugins.push(
    require('@fullhuman/postcss-purgecss')({
      content: [
        './app/**/*.html.erb',
        './app/**/*.js.erb',
        './app/helpers/**/*.rb',
      ],
      defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
    })
  )
}

module.exports = environment // エクスポート
```
デプロイ前後で、本番環境以下のコマンドを叩き、ファイルサイズが小さくなっていることを確認

```zsh
$ RAILS_ENV=production bin/webpack

Asset       Size
css/application-72aa1758.css   20.2 KiB
css/application-72aa1758.css.map   6.54 KiB
js/application-dbc0495e28056a133c61.js    844 KiB
js/application-dbc0495e28056a133c61.js.map    898 KiB
manifest.json  640 bytes         
```

## デプロイ後にエラーが発生した場合の対処
CapistranoでVPSにデプロイしたところ、ブラウザからアクセスできなくなってしまいました。
`log/production.log`を確認したところ、以下のようなエラーメッセージが記録されていました。

```
ActionView::Template::Error (Webpacker can't find application in /var/www/html/namatsuba/releases/20200728115729/public/packs/manifest.json. Possible causes:
1. You want to set webpacker.yml value of compile to true for your environment
   unless you are using the `webpack -w` or the webpack-dev-server.
2. webpack has not yet re-run to reflect updates.
3. You have misconfigured Webpacker's config/webpacker.yml file.
4. Your webpack configuration is not creating a manifest.
Your manifest contains:
{
  "application.js": "/packs/js/application-b63d405284038a2c85dc.js",
  "application.js.map": "/packs/js/application-b63d405284038a2c85dc.js.map",
  "entrypoints": {
    "application": {
      "js": [
        "/packs/js/application-b63d405284038a2c85dc.js"
      ],
      "js.map": [
        "/packs/js/application-b63d405284038a2c85dc.js.map"
      ]
    }
  }
}
):
```

### 原因
`config/webpacker.yml`を確認すると、デフォルトでは本番環境だけcssをextractする設定になっているようでした。
capistranoデプロイ時に実行される`rake assets:precompile`が`RAILS_ENV=production`なしで実行されており、extract_cssがfalseの状態でprecompileされてしまっていたことが原因だったみたいです。

### 解決策
`config/webpacker.yml`を以下のように修正して解決。

```diff:config/webpacker.yml
- extract_css: false
+ extract_css: true

//...
production:
  <<: *default
  compile: false
  extract_css: true
```

## 参考
- [RailsでTailwind CSSを使用する - Qiita](https://qiita.com/tabakazu/items/4c152de6e9a2c293d1f2)
- [How to Remove Unused CSS Classes from Tailwind CSS, Boostrap, and more with PurgeCSS (Example) | GoRails](https://gorails.com/episodes/purgecss)
- [Webpacker in  production (Webpacker can&#39;t find application in...) · Issue #2071 · rails/webpacker](view-source:https://github.com/rails/webpacker/issues/2071)