Edited at

WebpackerでReactを導入するときにつまづいたこと

More than 1 year has passed since last update.

エイチームライフスタイルWebエンジニアの@okonomiと申します。

Ateam Lifestyle Advent Calendar 2017、15日目をお送りします。


はじめに

弊社ではRails製のWebサイトを複数運用しており、最近それらのサイトに徐々にWebpackerでReactを導入し始めています。

導入自体は他のエンジニアが進めているのですが、自分でも手順などを知っておきたくて試してみました。

webpacker gemを追加してHello Reactするところまでは楽勝でしたが、そこから実際にアプリを書き始めるとつまづくポイントがいくつかありました。

もしこれからWebpackerを導入しようというときに、何か参考になれば幸いです。

また、試行錯誤の結果をgithubに公開していますのでよろしければ合わせてご覧ください。

https://github.com/okonomi/rails-webpacker-sample


環境など


  • macOS High Sierra

  • Ruby 2.4.2

  • Node.js v8.9.0

  • Rails 5.1.2

  • Webpacker 3.1.1

Webpackerは現在も活発に開発が進められています。

そのため、バージョンによって挙動が食い違う可能性があります!

本記事では執筆時点で最新の3.1.1を対象にしています。


その1: FontAwesomeが読み込まれない


現象

コンポーネントの見栄えを良くしようとFontAwesomeを導入しようと思い、

react-fontawesomeのサンプルコードどおりに記述したのですがフォントが表示されません…


app/javascript/src/components/like_button.jsx

import React from 'react'

import FontAwesome from 'react-fontawesome'
import faStyles from 'font-awesome/css/font-awesome.css'

class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = {
likes: 0,
};
}

render() {
return (
<button onClick={() => {this.setState({likes: this.state.likes +1})}}>
<FontAwesome
name="thumbs-up"
cssModule={faStyles} // CSSを渡しているが効かない!
/> {this.state.likes}
</button>
);
}
}


2017-12-13_03-10-16_re.png


原因・対策

Webpackerのデフォルト設定ではCSS Modulesが有効になっていませんでした。

上記コードはFontAwesomeコンポーネントに対してCSS Modulesでスタイルを指定していますが、CSS Modulesが有効になっていないためfaStylesnullになってしまっていました。

CSS Modulesを有効にする方法はCHANGELOGに記載がありました。

https://github.com/rails/webpacker/blob/master/CHANGELOG.md#310---2017-12-11


config/webpack/environment.js

const { environment } = require('@rails/webpacker')

// Enable css modules with sass loader
const sassLoader = environment.loaders.get('sass')
const cssLoader = sassLoader.use.find(loader => loader.loader === 'css-loader')

cssLoader.options = Object.assign(cssLoader.options, {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
})

module.exports = environment


environmentからCSS Loaderのオブジェクトを取り出してオプションを追加します。

environmentはWebpackerにおけるwebpackの環境設定を統括するEnvironmentクラスのインスタンスで、実体はWebpackerのpackage/environment.jsで定義されています。

CSS Loaderのオプションはpackage/rules/css.jsで指定されていますが、modulesフラグが有効化されていないので追加します。これでCSS Modulesが有効になります!

CSS Loaderに指定できるオプションの一覧はこちらで確認できます。

https://github.com/webpack-contrib/css-loader#options

また、上記設定を加えるとCSS Modulesは.scss.sass に対して有効になりますので、FontAwesomeのファイルも.scssに変更しておきます。


app/javascript/src/components/like_button.jsx

- import faStyles from 'font-awesome/css/font-awesome.css'

+ import faStyles from 'font-awesome/scss/font-awesome.scss'

あと最後に、viewにstylesheet_pack_tagを書いてCSSファイルが読み込まれるようにします。


app/views/layouts/application.html.erb

+ <%= stylesheet_pack_tag 'like_button' %>


2017-12-13_22-51-17_re.png


補足

公式ドキュメントにもCSS Modulesを有効にする設定例が記載されています。

https://github.com/rails/webpacker/blob/master/docs/webpack.md#overriding-loader-options-in-webpack-3-for-css-modules-etc

こちらの設定もやっていることは同じで、違いは設定値の追加に使うのがObject.assignwebpack-mergeかだけです。

今回の用途ではwebpack-mergeは大げさかなと思いますが、どちらにするかは好みで良さそうです。


その2: turbolinks有効時にJSがうまく動かない


現象

Reactコンポーネントを設置したページを直接開くと問題ないのですが、トップページからReactコンポーネントを設置したページに遷移してくるとコンポーネントが起動しません。なぜ…

2017-12-13 03.04.13.gif


原因・対策

ページ読み込み完了を契機にコンポーネントを起動していたのですが、listenするイベントに問題がありました。


app/javascript/packs/application.js

document.addEventListener('DOMContentLoaded', () => {

const elem = document.getElementById('like-button');
if (elem) {
ReactDOM.render(<LikeButton />, elem)
}
})

turbolinksは御存知の通りページ遷移時にコンテンツの内容だけを入れ替えることでページ遷移を高速化しますが、そのためDOMContentLoadedイベントが発火しなくなります。

替わりにturbolinks側で用意されているturbolinks:loadイベントをlistenすることでページ遷移がキャッチできます。


app/javascript/packs/application.js

- document.addEventListener('DOMContentLoaded', () => {

+ document.addEventListener('turbolinks:load', () => {
const elem = document.getElementById('like-button');
if (elem) {
ReactDOM.render(<LikeButton />, elem)
}
})

turbolinks側で用意されているイベントはこちら。

https://github.com/turbolinks/turbolinks#full-list-of-events

また、<body>タグ内に<script><style>を置いてしまうとページ遷移時にJSが読まれてturbolinksの意味が薄れるので<head>に移動しました。


その3: デプロイ時assets:precompileがなぜか失敗


現象

bundle exec cap production deployでデプロイ実行すると、assets:precompileの段階でwebpackのCompiling…から進まなかったり

2017-12-13_01-22-59_re.png

エラー終了したりする現象が発生しました。

2017-12-13_01-32-09_re.png


原因・対策

サーバのメモリ不足が原因でした。

デプロイ実行中にサーバ側のターミナルが「Cannot allocate memory」のようなメッセージを多発し始めたため、試しにdstatでデプロイ時のメモリの推移を見てみると空きメモリがみるみる減っていました。

2017-12-13_01-36-33_re.png

今回はVagrantでデプロイ対象のサーバを立ち上げていたのですが、無設定だとメモリが512MBだったので、Vagrantfileを編集してメモリを1GBに増やしました。


Vagrantfile

  Vagrant.configure("2") do |config|

# ...省略...

+ config.vm.provider "virtualbox" do |vb|
+ vb.memory = "1024"
+ end

# ...省略...
end


webpackのメモリ消費に関する情報はいくつか見かけるため、確証はないですが、webpackでビルドするときはサーバのメモリ容量に気をつけたほうがよさそうです。

また、公式ドキュメントにビルドパフォーマンスについてのページがあるので、ここを参考にすればメモリ使用量の改善ができるかもしれません。

https://webpack.js.org/guides/build-performance/

この辺り、詳しい方のご意見を伺いたいところです。


おわりに

以上、WebpackerでReactを導入するときにつまづいたことをまとめてみました。

Webpackerはフロントエンド開発の色々なややこしいことを肩代わりしくれますが、連携して動いているミドルウェアやライブラリを理解していないと、一見不可解な挙動に振り回されてしまうことも分かりました。

挙動をひとつひとつ把握しつつ、React導入を進めていこうと思います。

明日は新卒デザイナーの@xrxoxcxoxさんがAdobe XDについて書いてくれるようです。よろしくお願いします!


株式会社エイチームライフスタイルでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。

http://www.a-tm.co.jp/recruit/