Reactとwebpack 2とBrowsersyncの連携

## 当記事はReact開発者向けです

1. Browsersync Overview
2. 動作確認の現状
3. 各種セットアップ
4. Browsersync実行

## Browsersync とは

そもそも[Browsersync](https://www.browsersync.io/)とは

- Network Throttle (通信速度のエミュレーション)
- Interaction sync (スクロール、クリック、フォーム操作の同期)
- File sync (ファイルの同期及び画面の自動リロード)
- UI or CLI control (Browsersyncの設定をコンソール画面やコマンドで操作)
- URL history

といった機能があり、あらゆる実機での確認を自動操作としてサポートしてくれるものです。
複数デバイス対応で機能やデザインを確認しながら開発する人に最も効果を発揮するツールだと思います。


## 動作確認の現状

[webpack](https://webpack.js.org/)から[webpack-dev-server](https://webpack.github.io/docs/webpack-dev-server.html)で動作確認するところまではサンプルとして

- React([create-react-app](https://github.com/facebookincubator/create-react-app))
- Vue([vue-cli](https://github.com/vuejs-templates/webpack))
- Angular([Angular CLI](https://cli.angular.io/))
- Riot([Riot](http://riotjs.com/ja/)) ※1

以上の主要フレームワークに用意されています。
それをそのまま使うことで当たり前のように動作確認できます。
※1 サンプル見当たらなかったのですがwebpackと連携して制作する人も居ましたので、どこかにサンプル相当なる記事があると思いますので一応記載

**今回はReactを題材にcreate-react-appでReact開発を始めたケースとします。**


## 以下手順

### Reactのセットアップ

```
npm install -g create-react-app

create-react-app my-app
cd my-app/
npm run eject
```

### React Hot Loader 3のセットアップ

```
npm install react-hot-loader@next find-cache-dir --save-dev
```

```js:my-app/config/webpack.config.dev.js
// 6行目あたりのrequire群に追加
const findCacheDir = require('find-cache-dir');


// 36行目付近
entry: [
  // for React Hot Loader and React Transform.
  require.resolve('react-hot-loader/patch'),
  // Include an alternative client for WebpackDevServer. A client's job is to
  // connect to WebpackDevServer by a socket and get notified about changes.
  // When you save a file, the client will either apply hot updates (in case
  // of CSS changes), or refresh the page (in case of JS changes). When you
  // make a syntax error, this client will display a syntax error overlay.
  // Note: instead of the default WebpackDevServer client, we use a custom one
  // to bring better experience for Create React App users. You can replace
  // the line below with these two lines if you prefer the stock client:
  // require.resolve('webpack-dev-server/client') + '?/',
  // require.resolve('webpack/hot/dev-server'),
  require.resolve('react-dev-utils/webpackHotDevClient'),
  // We ship a few polyfills by default:
  require.resolve('./polyfills'),
  // Errors should be considered fatal in development
  require.resolve('react-error-overlay'),
  // Finally, this is your app's code:
  paths.appIndexJs,
  // We include the app code last so that if there is a runtime error during
  // initialization, it doesn't blow up the WebpackDevServer client, and
  // changing JS code would still trigger a refresh.
],


// 168行目付近
// Process JS with Babel.
{
  test: /\.(js|jsx)$/,
  include: paths.appSrc,
  loader: require.resolve('babel-loader'),
  options: {
    // This is a feature of `babel-loader` for webpack (not Babel itself).
    // It enables caching results in ./node_modules/.cache/babel-loader/
    // directory for faster rebuilds.
    // cacheDirectory: true,
    cacheDirectory: findCacheDir({
      name: 'react-scripts'
    }),
    plugins: [
      require.resolve('react-hot-loader/babel')
    ]
  },
},
```



```js:my-app/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {AppContainer} from 'react-hot-loader';
import registerServiceWorker from './registerServiceWorker';

const render = Component => {
  ReactDOM.render(
    <AppContainer>
      <Component />
    </AppContainer>,
    document.getElementById('root')
  );
}
render(App)

if (module.hot) {
  module.hot.accept('./App', () => { render(App) })
}

registerServiceWorker();
```

### Browsersyncのセットアップ

```
npm install browser-sync --save-dev
```

```js:my-app/scripts/server.js
// 当ファイルは新規作成
'use strict';

// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';

// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
  throw err;
});

// Ensure environment variables are read.
require('../config/env');

const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const browserSync = require('browser-sync');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');

const paths = require('../config/paths');

const webpackDevConfig = require('../config/webpack.config.dev');
const bundler = webpack(webpackDevConfig);

// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
  process.exit(1);
}

browserSync({
  server: {
    baseDir: 'public',
    middleware: [
      webpackDevMiddleware(bundler, {
        // IMPORTANT: dev middleware can't access config, so we should
        // provide publicPath by ourselves
        publicPath: webpackDevConfig.output.publicPath,
        // pretty colored output
        stats: { colors: true }
        // for other settings see
        // http://webpack.github.io/docs/webpack-dev-middleware.html
      }),
      // bundler should be the same as above
      webpackHotMiddleware(bundler)
    ]
  },
  // no need to watch '*.js' here, webpack will take care of it for us,
  // including full page reloads if HMR won't work
  files: [
    'src/*.css',
    'src/**/*.css',
    'public/*.html'
  ]
});
```

```js:my-app/package.json
"scripts": {
  "start": "node scripts/start.js",
  "build": "node scripts/build.js",
  "test": "node scripts/test.js --env=jsdom",
  "server": "node scripts/server.js"
},
```

## Summary

以上で作業は完了です。

ターミナルで`npm run server`を実行。

```
[Browsersync] Access URLs:
 ------------------------------------
動作画面
       Local: http://localhost:3000
    External: http://192.168.0.5:3000
 ------------------------------------
Browsersyncの管理ページ
          UI: http://localhost:3001
 UI External: http://192.168.0.5:3001
 ------------------------------------
```