43
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ateam LifestyleAdvent Calendar 2017

Day 15

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

Last updated at Posted at 2017-12-14

エイチームライフスタイル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/

43
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?