RailsのWebpacker+Reactでフロントエンドを作っているのですが、コンポーネントへのスタイルの当て方にも色々あるんですね。
今回はCSS Modulesという設計に基づいたbabel-plugin-react-css-modulesというパッケージを入れてCSSのローカルスコープ化をしたのですが、使い方がよくわからず
Warning: React does not recognize the `styleName` prop on a DOM element.
というWarningが出続けてかなりハマったのでメモを残します。
注意
自分はwebpack/Babelともにそんなに詳しくないので、以下の内容は一部必要のない設定や誤解もあるかもしれません。何か気になったらコメントで指摘頂けるとありがたいです。
その前に:Webpackerのキャッシュを消す方法
/public/packsディレクトリにはWebpackerが作成したファイルが大量に作成されています。これは最新のもの以外必要ないですし、消せば作り直されます。
プラグイン等の設定を変更しても開発サーバがファイルをキャッシュして反映されない場合があるので、設定変更したのに何も変わらないと思ったときはpacks以下を全部消してしまうといいです。
パッケージインストール
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を使ってるのでインストール。
yarn add webpack-merge
config/webpack/environment.js
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のデフォルトに合わせるには
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を読み込むためのプラグインが必要みたいです。(後述:必要ないかも?)
yarn add postcss-scss
.babelrc
.babelrcにのpluginsにreact-css-modulesを追加します。
# 前半略
"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していても、読み込み時は
<% stylesheet_pack_tag 'components/hoge' %>
となります。
スタイルをコンポーネントに対してローカル化するという思想上コンポーネントのパスになるということかと思います。
補足:postcss-scssは必要ないかも?
postcss-sccsはPostCSSにscssをパースさせるための構文プラグイン(実際にSCSSをCSSにコンパイルするわけではない)なんですが、これを入れずに.babelrcを
["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のタグ不整合など)せいだったということもあったのでそれも確認してみてください。
コメント