Hirooooo’s Labo

開発メモ、ガジェット、日記、趣味など、思った事を思ったまんま書くブログ

React × Redux 初心者入門(2日目:webpackでHotModuleReplacementとソースマップ出力)

f:id:hirooooo-lab:20160713002241j:plain

前回は環境構築から簡単なカウンターアプリをReact Reduxで作成してみました。

今回は更に開発効率が上がるように開発時はHotReloadの機能と、ソースマップの出力機能を実装していきたいと思います。

HotReload機能実装

まずは実行しながら修正した場合に、画面をリロードするのではなく、修正部分だけを反映してくれるHotModuleReplacementを実装していきたいと思います。

前回の状態では、実行中にコードを修正すると、自動でリロードはしてくれるのですが、stateの内容がすべて初期化されてしまう状態です。
それを、state等は保持したまま、修正箇所だけLiveUpdateしてくれるようにしてみたいと思います。

react-hot-loaderのインストール

HMRを実現するのに、react-hot-loaderを使用したいと思います。
webpackのHotModuleReplacementのページの下部にreact用のライブラリリンクが有りました。
Hot Module Replacement

リンク先のGitHubページを見てみると、現在のversion3なのですが、v4がそろそろ出るようです。
(2018-01-18時点)
f:id:hirooooo-lab:20180118162530p:plain

なので、この記事では先行して次期バージョンのv4を対象に書いていきたいと思います。

現在はbetaなので下記コマンドでインストールします。

$ npm install --save react-hot-loader@next

packagejsonに以下が追記されたと思います。

   "react-hot-loader": "^4.0.0-beta.15",

.babelrcの修正

.babelrcファイルに以下を追記します。

// .babelrc
{
  "plugins": ["react-hot-loader/babel"]
}

/containers/App.jsxの修正

ルートコンポーネントである/containers/App.jsxを修正します。
exportにhotを実装します。

// hotを読み込み
import { hot } from 'react-hot-loader';

// exportを書き換え
// export default connect(
//   mapStateToProps,
//   mapDispatchToProps,
// )(App);

export default hot(module)(connect(
  mapStateToProps,
  mapDispatchToProps,
)(App));

最終的にはこうなります。

// containers/App.jsx

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';

import Header from '../components/Header';
import CounterActions from '../actions/counter';

class App extends Component {

  static propTypes = {
    counter: PropTypes.object.isRequired,
    actions: PropTypes.object.isRequired,
  };

  render() {
    const { counter, actions } = this.props;
    return (
      <div>
        <Header />
        <h2>count={counter.value}</h2>
        <button onClick={actions.increment}>増加</button>
        <button onClick={actions.decrement}>減少</button>
      </div>
    );
  }
}

// Appコンポーネントにstateを流し込む
function mapStateToProps(state) {
  return {
    counter: state.counter,
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Object.assign({}, CounterActions), dispatch),
  };
}

export default hot(module)(connect(
  mapStateToProps,
  mapDispatchToProps,
)(App));

これでHotModuleReplacementの実装は終わりました。
試しに動かしてみましょう。

実行してみる

  1. まずはnpm startから実行して増加ボタンを2回押してCount=2にしておきます。
    f:id:hirooooo-lab:20180118162603p:plain

  2. components/Header.jsxの実行中のまま修正します。
    ヘッダだYO → ヘッダだZE に変更してみたいと思います。
    f:id:hirooooo-lab:20180118162728p:plain

  3. 画面のCountは2のままクリアされずに、ヘッダーが変更されました。
    f:id:hirooooo-lab:20180118163519p:plain

とりあえずこれでHotModuleReplacementの実装は大丈夫っぽいです。

続いてsourcemap出力の実装です。

ソースマップ出力実装

現在のままだと、chromeの開発者ツールなどでデバッグする場合や、consoleで吐かれたエラーなどがbundle.jsを参照してしまいます。

それだとデバッグがしづらいので、ソースマップを出力してbundleされる前のファイルを表示したいと思います。

webpack.config.jsの修正

webpack.config.jsに以下の1行を追加します。

devtool: 'inline-source-map',

全体像はこんな感じ

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  context: __dirname,
  entry: [
    './src/index.jsx',
  ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/',
    filename: 'bundle.js',
  },
  devtool: 'inline-source-map',
  module: {
    loaders: [
      { test: /\.jsx?$/, enforce: 'pre', exclude: /node_modules/, loader: 'eslint-loader' },
      { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel-loader'] },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
      inject: 'body',
    }),
    new webpack.LoaderOptionsPlugin({
      options: {
        eslint: {
          configFile: './.eslintrc',
        },
      },
    }),
  ],
};

実装方法はこれだけです。

実行してみる

  1. errorを出力したときをわかりやすくするために、reducers/counter.jsxにconsole.error('increment');を仕込んでおきます。
// reducers/counter.jsx

import * as types from '../constants/ActionTypes';

const initialState = {
  value: 0,
};

export default function counter(state = initialState, action) {
  switch (action.type) {
    case types.INCREMENT:
      console.error('increment');
      return { value: state.value + 1 };
    case types.DECREMENT:
      return { value: state.value - 1 };
    default:
      return state;
  }
}
  1. ターミナルからnpm startを実行して、表示されたページのchromeの開発者ツールを表示します。
  2. 増加ボタンをクリックして、consoleにエラーを表示させます。
    すると、無事bundle前のファイル名が表示され、エラーの行数もreducers/counter.jsxを参照してくれます。
    f:id:hirooooo-lab:20180118171336p:plain

エラー箇所をクリックしてSourcesを表示しても、ちゃんとbundle前のファイルで表示されます。
f:id:hirooooo-lab:20180118171522p:plain

ちなみに、ソースマップが出力される前はこんな感じでbundleファイルが表示されていました。
f:id:hirooooo-lab:20180118171559p:plain

まとめ

今回は開発時の効率を良くするための設定方法の説明でした。
webpackを使いこなすことで、もっといろいろとできることもありますが、今回はこんな感じで終わります。
これだけでもデバッグが捗ること間違い無し。 webpackに関してはいずれ開発環境と本番環境の切り分けとかについても書いていきたいと思います。

参考にさせていただいたサイト

次回予告

次回予告というか、あとやらなきゃいけないと思ってること

  • 開発環境の効率化をするためにHotReloadとソースマップを実装する → Done!
  • ESLintでAirbnbのスタイルガイドを実装する
  • MaterialデザインとCSSを実装する
  • ページルーティングを実装する
  • REST通信を実装する
  • ログイン制御のフローを実装する
  • デプロイ方法を検討、実装する

次回の記事

www.hirooooo-lab.com

今までの記事