Redux用のnpmモジュールを作る

これは[Nextremer Advent Calendar 2016](http://qiita.com/advent-calendar/2016/nextremer)の第12日目の記事です。

# はじめに
Reduxに限らず、よく使う処理やプラグイン的に追加したい処理なんかを別途npmモジュールとして作りたいというケースは多いと思います。

Reactの場合だと、パラメータや子コンポーネントを受け取るようなコンポーネントを作ってnpmモジュールとして提供すればいいのですが、Reduxの場合はコンポーネント以外にもActionやReducerなどが絡んでくるため、それらを（必要なら）全て提供する必要があります。

今回はそれらを提供する手順をざっとまとめてみます。

### ※今回の開発環境
* Node.js 6.2.2
* npm 3.10.5

# 作ってみる
早速作っていきましょう。

## 下準備
適当な作業ディレクトリを作って、そこで作業します。
`npm init`を実行し、適宜情報を入力しましょう。

今回は、以下の構成で作りたいと思うのでそれぞれインストールします。

* Webpack
* Babel(ECMAScript 2015 + Stage 0)

```
npm i -D webpack babel-core babel-loader babel-polyfill babel-preset-react babel-preset-es2015 babel-preset-stage-0 babel-plugin-transform-decorators-legacy
```

もちろん、React, Redux(saga, actionsも一緒に)も必要なのでそれもインストール

```
npm i -S react redux react-redux redux-saga redux-actions
```

## Webpackの準備
おそらく、React-Reduxとしてビルドするための最小構成はこんな感じかと思います。

``` webpack.config.js
const webpack = require('webpack');

module.exports = {
  entry: [ 'babel-polyfill', __dirname + '/js/index.js' ],
  output: {
    path: './',
    filename: 'index.js',
  },
  module: {
    loaders: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel'
    }]
  },
  plugins: [],
  externals: []
}
```

npmモジュールとしてビルドするために、まずは以下の設定を追加しましょう。

``` webpack.config.js
  output: {
    path: './',
    filename: 'index.js',
    library: 'hoge-module', // ★ ココ
    libraryTarget: 'umd' // ★ ココ
  },
```

`library`はビルドするnpmモジュール名、`libraryTarget`はビルド形式の設定です。
umdはUniversal Module Definitionの略で、大体AMDとかCommonJSで読み込める形式にしてくれる指定らしいです。（詳しくは[ココ](https://github.com/umdjs/umd)を参照）

続いて、npmモジュールとして出力する際に、`node_modules`の出力は不要なので、`node_modules`以下のファイルを出力しないよう設定しましょう。

`webpack-node-externals`というライブラリを使用します。

`npm i -D webpack-node-externals`でインストールして、`webpack.config.js`に以下の通り設定を追加しましょう。

``` webpack.config.js
const nodeExternals = require('webpack-node-externals'); // ★ ココ

module.exports = {
  target: 'node', // ★ ココ
    :
    :
  externals: [ nodeExternals() ] // ★ ココ
};
```

## package.jsonの設定
`package.json`にもnpmモジュールとしてビルドするための設定をしましょう。といっても、パッケージ名の`name`と、インポート対象のファイルとなる`main`が設定されていれば大丈夫なようです。

``` package.json
{
  "name": "hoge-module",
  "main": "index.js",
}
```

## モジュールの作成
いよいよモジュール本体を作成しましょう。
今回は、Action, Reducer, Component, Sagaの４つを提供するモジュールを作ります。
内容は、、、簡単に **１秒ごとにカウンタの値を増やして表示する**だけのモジュールにでもしましょう。

この辺は今回はさっくり飛ばしていきますよ。

### ファイル構成
```
/js
  /components
    /Count.js
  /actions.js
  /index.js
  /reducers.js
  /sagas.js
```

### Action
```actions.js
import { createAction } from 'redux-actions';

export const START_TIMER = 'START_TIMER';
export const startTimer = createAction( START_TIMER );
export const STOP_TIMER = 'STOP_TIMER';
export const stopTimer = createAction( STOP_TIMER );
export const TIMER_EXPIRED = 'TIMER_EXPIRED';
export const timerExpired = createAction( TIMER_EXPIRED );
```

### Reducer
```reducer.js
import * as actions from './actions';
export const initial = 0;

const handlers = {
  [actions.TIMER_EXPIRED]: ( state, { payload } ) => {
    return ++state;
  },
};

export default ( state = initial, action ) => {
  const handler = handlers[ action.type ];
  return !handler ? state : handler( state, action );
}
```

### Saga
```sagas.js
import { takeEvery, delay } from 'redux-saga';
import { fork, call, take, put, race, actionChannel } from 'redux-saga/effects';
import * as actions from './actions';

export function* startTimerEventWatcher() {
  const channel = yield actionChannel( actions.START_TIMER );

  while( yield take( channel ) ) {
    while( true ) {
      const winner = yield race({
        stopped: take( actions.STOP_TIMER ),
        expired: call( delay, 1000 )
      });

      if ( winner.stopped ) {
        break;
      } else {
        yield put( actions.timerExpired() );
      }
    }
  }
}

export function* timerModuleRootSaga() {
  yield fork( startTimerEventWatcher );
}
```

### Component
```component/Count.js
import React, { Component } from 'react';
import { connect } from 'react-redux';

@connect( state => ({
  count: state.timerModule,
}), {})
export default class Count extends Component {
  render() {
    return (
      <div>{ `count: ${ this.props.count }` }</div>
    );
  }
}
```

### 最後に↑のを全部まとめるためのindexファイル
```index.js
import * as actions from './actions';
export { actions }

export { default as timerModuleReducer } from './reducers';
export Count from './components/Count';
export { timerModuleRootSaga } from './sagas';
```

## 出来上がったらビルド
ここまでざっと作ったら`webpack`コマンドでビルドしましょう。
ビルドが通ればnpmモジュールの完成です。

# 使ってみる
さっそく使ってみましょう。って言われてもどうやって使うのん？って話ですよね。
自作のnpmモジュールには３つの使い方があります。（他にもあるかもだけど、知らない('A`)

## npmモジュールの使い方

### 1. npmに登録して使う
汎用的に使えるモジュールが出来たらnpm公式に登録しちゃいましょう。
登録方法は[ココ](http://qiita.com/fnobi/items/f6b1574fb9f4518ed520)が詳しいです。
登録が終われば、 `npm install hoge-module`のような形でインストールして使うことが出来ます。

### 2. githubからインストールして使う
他の人の役には立たないかもしれないけど、自分ではよく使う。プライベートなプロジェクトで使うから公開するわけにはいかないけど、プロジェクトではよく使う。などの場合は、githubリポジトリにそのままあげちゃいましょう。
`npm install github:<githubリポジトリ名>`でgithubリポジトリからインストールして使えますョ。

### 3. ローカルのnpmモジュールをローカルのプロジェクトで使う
要するにデバッグです。`npm link`というコマンドで、ローカルのnpmモジュールをローカルの別プロジェクトにインストールして使うことが出来ます。（厳密にはシンボリックリンク貼ってるだけなのでインストールってわけじゃないですが）

1. npmモジュール側で`npm link`
2. npmモジュールを使うプロジェクトで`npm link hoge-modules`

を実行すれば1で作ったnpmモジュールを2のプロジェクトで利用することが出来ます。実体がシンボリックリンクなので、npmモジュールを修正して再ビルドすれば即座にリンクしているプロジェクトに反映されるのがいいところですね(・∀・)

ということで、適当なReduxプロジェクトを作って↑で作成したタイマーモジュールを使ってみましょう。

##　実際に使ってみる
今回のnpmモジュールでは、Action, Component, Reducer, Sagaの４つが提供されています。
Action, Componentはそのまま使いたい場所で使いたいときに使えばいいですが、Reducer, Sagaはそれぞれ、`combineReducers`、`rootSaga`に登録してやる必要があります。

```reducers/index.js
import { combineReducers } from 'redux';
    :
import { timerModuleReducer } from 'timer-module';

export default combineReducers({
    :
  timerModule: timerModuleReducer,
});

```

```sagas/index.js
import { fork } from 'redux-saga/effects';
    :
import { timerModuleRootSaga } from 'timer-module';

export default function* rootSaga() {
    :
  yield fork( timerModuleRootSaga );
}
```

こんな感じですかね。
このように、Reduxプラグインの中には、事前にReducerやSagaを登録してやる必要があるものがあります。

後はAction, Componentを適宜呼び出してやればOKです。

```components/App.js
import { connect } from 'react-redux';
import { Count, actions } from 'timer-module';

@connect( state => ({}), {
  start: actions.startTimer,
  stop: actions.stopTimer,
})
export default class App extends React.Component {
  render() {
    return (
      <div className="hgoe">
        <Count />
        <button onClick={ ::this.props.stop }>終了</button>
        <button onClick={ ::this.props.start }>開始</button>
      </div>
    );
  }
}
```

こんな感じですね。これで１秒ごとにカウンタがインクリメントされるタイマが完成しました。

![タイマ.gif](https://qiita-image-store.s3.amazonaws.com/0/16629/1b3116d6-c993-d58c-6aac-5b1645e62f2e.gif)


# まとめ
Reduxでnpmモジュールを作成しました。

見てもらえば分かるとおり、単純なもの（React　Componentにちょっとした非同期処理を付け加えたぐらい）であれば通常のReduxアプリケーションを作成するのとほぼ同じ内容でnpmモジュールを開発できます。（気をつけるのは`webpack`と`package.json`の設定ぐらいですかね。）

なんか色々もっといいやり方あるかもしれないので、また見つけたら報告します。

以上