webpack+React(+Redux+React Router)を使ったimport文の減らし方

# webpackのrequire.contextについて

`webpack`の「`require.context`」を使うことで、ディレクトリ構造をしっかりしていれば`import`の記述を減らす事が出来ます。

# 通常のimport及びexport

## ディレクトリ構成

```
├ component
│└App.js
└  icon
　└ custom
　　├ Person.js
　　├ Animal.js
　　└ xxx.js
```
※`custom`ディレクトリ直下にアイコンファイルが随時増えると想定

## 記述

```js:icon/custom/Person.js
import { default as React } from 'react'
import SvgIcon from 'material-ui/SvgIcon';
 
export default class Person extends React.Component {
    render() {
        return (
            <SvgIcon viewBox="0 0 256 256" {...this.props}>
             ...
            </SvgIcon>
        )
    }
}
```
```js:icon/custom/Animal.js
import { default as React } from 'react'
import SvgIcon from 'material-ui/SvgIcon';
 
export default class Animal extends React.Component {
    render() {
        return (
            <SvgIcon viewBox="0 0 256 256" {...this.props}>
             ...
            </SvgIcon>
        )
    }
}
```
```js:component/App.js
import { default as React } from 'react'
import {List, ListItem} from 'material-ui/List';

import Person from 'icon/custom/Person.js';
import Animal from 'icon/custom/Animal.js';
...//★　
 
export default class App extends React.Component {
    render() {
        return (
            <List >
                <ListItem
                    primaryText="太郎"
                    leftIcon={<Person />}
                />
                <ListItem
                    primaryText="ポチ"
                    leftIcon={<Animal />}
                />
            </List>
        )
    }
}
```

上記だと、ファイルが増えるたびに**★**の部分に`import`の記述をする必要が出てきます。
※今回は`icon/custom/`直下にフラットにjsファイルを置いていますが、フォルダ階層が深い場合は記述が非常に面倒くさいです。

# webpackのrequire.contextを使った自動export及びexport

## ディレクトリ構成

```
├ component
│└App.js
└ icon
　├ CustomIcons.js //★　←　追加（全てのサブモジュールを梱包するオブジェクトの下準備用ファイル）
　└ custom
　　├ Person.js
　　├ Animal.js
　　├ ...
　　└ extend
　　　└ ...

```
※`custom`ディレクトリ配下に**サブディレクトリ含め**アイコンファイルが随時増えると想定

## 記述

```js:icon/CustomIcons.js
const modules = require.context("./custom/", true, /^\.\/.*\.js$/);//customディレクトリを起点として配下のmodule情報を正規表現を基に取得
let CustomIcons = {};//default指定する空のObjectを用意
modules.keys().forEach((key, index)=> {//keyにはmoduleのパス情報が入る(ex. "./Person.js")
  let module = modules(key);//keyを指定してmoduleを取得
  CustomIcons[module.default.name] = module.default;//moduleのdefaultに指定したオブジェクトの名称をキーにdefaultで指定したオブジェクトを格納
});
export default CustomIcons;//Objectをdefault指定
```
```js:component/App.js
import { default as React } from 'react'
import {List, ListItem} from 'material-ui/List';

import CustomIcons from 'icon/CustomIcons.js';//CustomIconsの中に全てのmoduleをObjectで持っているので、import文を増やす必要が無くなる
 
export default class App extends React.Component {
    render() {
        return (
            <List >
                <ListItem
                    primaryText="太郎"
                    leftIcon={<CustomIcons.Person />}//ここで対象のmoduleを指定する
                />
                <ListItem
                    primaryText="ポチ"
                    leftIcon={<CustomIcons.Animal />}//ここで対象のmoduleを指定する
                />
            </List>
        )
    }
}
```

開発初期にちょっとした下準備（`CustomIcons.js`）とディレクトリ構成をしっかりしておけば幸せになれます。

# 応用①：Reduxの自動export及びimport

## ディレクトリ構成

```
App.js
├ index
│└Index.js
├ actions//アクション関連
│├ Actions.js
│└ ...
├ components//コンポーネント関連
│├ Components.js
│└ ...
├ containers//コンテナ関連
│├ Containers.js
│└ ...
├ constants//定数関連
│├ Constants.js
│└ ...
├ middleware//ミドルウェア関連
│├ Middleware.js
│└ ...
├ reducers//レデューサー関連
│├ Reducers.js
│└ ...
└ routes//ルート関連
　├ Routes.js
　└ ...
```

※各ディレクトリ配下の`export`指定の名称は**一意**にする必要があります。(`Object`の`Key`に指定されるので)

## 記述(例：Containers.jsの場合)

```js:container/Containers.js
const modules = require.context("./", true, /^\.\/.*\/.*.js$/);//自分自身を含まないようにサブディレクトリ配下の指定をする
let Containers = {};
modules.keys().forEach((key, index)=> {
  let module = module(key);
  Containers[module.default.WrappedComponent.name] = module.default;
});
export default Containers;
```
```js:routes/Rourts.js
import App from "../App.js"
import Index from "../index/Index.js"
import Containers from "../containers/Containers.js"

let childRoutes = [];
Object.keys(Containers).forEach((key) => {
    let container = Containers[key];
    childRoutes.push(
        {
            path: container.WrappedComponent.name,//ラップしたComponentの名称をパスに指定
            component: container//Containerを指定
        },
    );
});

export default {
  path: '/',
  component: App,
  indexRoute: {
    component: Index
  },
  childRoutes
}
```

```js:App.js
...
import Reducers from "./reducers/Reducers.js";
import Middlewares from './middleware/Middlewares.js';
import Routes from './routes/Routes.js'

let store = createStore(
  Reducers,
  applyMiddleware(...Middlewares)
)

const history = syncHistoryWithStore(hashHistory, store)

export default class App extends React.Component {
    render() {
        return (
            <div>
                <Router history={history} routes={Routes} />
            </div>
        )
    }
}

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('app')
)

```

これで、

- `Actions.js`
- `Components.js`
- `Containers.js`
- `Constants.js`
- `Middleware.js`
- `Reducers.js`

に関しては、`Actions.{module名}.xxx`のような指定で各moduleにアクセス可能になり、`import`の記述量や記述ミスが減り、

- `Routes.js`

に関してはファイルが増えるたびに自動でルーティングが更新されます。

ただ、`Routes.js`に関しては`module`名がパスに含まれてしまったり、`Containers.xxx`の数だけルーティングが増えるので、制御したい場合はやWebシステムのようにパスが可視化されてしまう場合は何かしらの考慮が必要です。

※筆者は上記方法での`Electron`アプリ開発なので、開発者ツールを使用不可にすれば基本的にはルーティングのURLは見えないですし、1機能(`Component`)1ルートなので、自動化しても特に問題が出ていない状態です。

# 終わりに

散々書きましたが、なかなかごり押しな部分もあるので、良いか悪いかはご自身で判断してご利用ください。

じゃあの。