RailsのWebpacker+Reactでフロントエンドを作っているのですが、コンポーネントへのスタイルの当て方にも色々あるんですね。
今回はCSS Modulesという設計に基づいたbabel-plugin-react-css-modulesというパッケージを入れてCSSのローカルスコープ化をしたのですが、使い方がよくわからず
Warning: React does not recognize the `styleName` prop on a DOM element.
というWarningが出続けてかなりハマったのでメモを残します。
Contents
注意
自分はwebpack/Babelともにそんなに詳しくないので、以下の内容は一部必要のない設定や誤解もあるかもしれません。何か気になったらコメントで指摘頂けるとありがたいです。
その前に:Webpackerのキャッシュを消す方法
/public/packsディレクトリにはWebpackerが作成したファイルが大量に作成されています。これは最新のもの以外必要ないですし、消せば作り直されます。
プラグイン等の設定を変更しても開発サーバがファイルをキャッシュして反映されない場合があるので、設定変更したのに何も変わらないと思ったときはpacks以下を全部消してしまうといいです。
パッケージインストール
1 2 | yarn add babel-plugin-react-css-modules |
config/webpack/environment.js
CSS ModulesをWebpackerに入れる方法については公式ドキュメントに解説がありました。
webpacker/webpack.md at master · rails/webpacker
Webpackerでは素のWebpackの設定ファイルであるwebpack.config.jsではなくconfig/webpack/以下にあるjsを使うようです。environment.jsがベースの設定、test/development/productionはそれぞれの環境固有の設定だと思います。上のリンクにあるとおりenvironment.jsを編集します。
その前にwebpack-mergeを使ってるのでインストール。
1 2 | yarn add webpack-merge |
config/webpack/environment.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | const { environment } = require('@rails/webpacker') const merge = require('webpack-merge') const myCssLoaderOptions = { modules: true, sourceMap: true, localIdentName: '[path]___[name]__[local]___[hash:base64:5]' } const CSSLoader = environment.loaders.get('sass').use.find(el => el.loader === 'css-loader') CSSLoader.options = merge(CSSLoader.options, myCssLoaderOptions) module.exports = environment |
ただここには2つの注意点がありました。
localIdentName
CSS ModulesではCSSのローカルスコープ化のため、クラス名が改変されます。その際HTML側のclass名とCSSのclass名の改変後のローカルスコープ名が一致しないとスタイルが当然反映されないのですが、Webpackerドキュメントの例にあるlocalIdentNameだとbabel-plugin-react-css-modulesの出力するDOM側のスコープ化クラス名と食い違いが発生していました。
現在のbabel-plugin-react-css-modulesのデフォルトに合わせるには
1 2 | localIdentName: '[path]___[name]__[local]___[hash:base64:5]' |
とする必要がありました。
babel-plugin-react-css-modulesのスコープ名のデフォルト形式は公式のOption一覧のgenerateScopedNameで確認できます。
CSS/SCSS
environment.loaders.get(‘sass’)の部分はCSSしか使わない場合(‘css’)に変える必要があるみたいです。SCSSを使う場合は’sass’。
ただどっちにしろ同じcss-loaderがインスタンス化されて使われるせいか、混在する場合片方書くだけで動いた何故か両方動いたのですが、順序などで問題が起こるかもしれません。よくわかりません。
Babel設定
PostCSSプラグイン追加
SCSSを使うには追加のPostCSSがSCSSを読み込むためのプラグインが必要みたいです。(後述:必要ないかも?)
1 2 | yarn add postcss-scss |
.babelrc
.babelrcにのpluginsにreact-css-modulesを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # 前半略 "plugins": [ "syntax-dynamic-import", "transform-object-rest-spread", [ "transform-class-properties", { "spec": true } ], ["react-css-modules", { "filetypes": { ".scss": { "syntax": "postcss-scss" } } }] ] } |
こんな感じになります。配列なので追加する際1個上のプラグインにカンマを足すのを忘れずに。他のプラグインはプロジェクト作成初期から含まれてたものです。
SCSSを使わない場合はオプションを書く必要ありません。[“react-css-modules”]または”react-css-modules”で大丈夫です。
stylesheet_pack_tag
これも公式ドキュメントに書いてあるんですが、コンポーネントからのimportとは別にReactを使用するerb内でstylesheet_pack_tagでCSSを読み込む必要が何故かあります…。
ちなみにpublic/packs/manifest.jsonを見るとわかりますが、cssはどこに置いてもimportした側のjsと同じパスにマッピングされるようです。
例えば
javascript/packs/components/hoge.js
から相対パスで
javascript/src/hoge_style.css
をimportしていても、読み込み時は
1 2 | <% stylesheet_pack_tag 'components/hoge' %> |
となります。
スタイルをコンポーネントに対してローカル化するという思想上コンポーネントのパスになるということかと思います。
補足:postcss-scssは必要ないかも?
postcss-sccsはPostCSSにscssをパースさせるための構文プラグイン(実際にSCSSをCSSにコンパイルするわけではない)なんですが、これを入れずに.babelrcを
1 2 3 4 5 6 | ["react-css-modules", { "filetypes": { ".scss": {} } }] |
としたところ普通にscss使えてるような気がします。filetypesは書かないとscss拡張子が反応されないので必要です。
なぜドキュメントに反してこれが大丈夫だと思ったかというと、webpackerはデフォルトでcss-loaderの前にscssに対してsass-loaderを通してるはずで、sass-loaderはSCSSをCSSにコンパイルしているはずなんです。なのでcss-loaderに渡った時点でSCSSはCSSになってると思います。
ただ最初に言ったとおりwebpackもBabelも入門中なので理解が正しいかはわかりません。
補足2:CSSが出力されないとき
importを行っているのにCSSが出力されないと思ったら、コンポーネントのwebpackコンパイル自体が失敗している(jsxのタグ不整合など)せいだったということもあったのでそれも確認してみてください。