React.js 開発環境を webpack4 + webpack-serve + babel 7 で作る(Deprecated)

追記: webpack-serve は非推奨となり webpack-dev-server が開発再開しました。新しく始める際はスタックに webpack-serve を採用しないほうがよいでしょう。[webpack-dev-server で同じような内容を書き残したのがこちら](https://qiita.com/ma2saka/items/ad8d1f129f52fba5e5ae)。

---

なんか普段開発しているフロントアプリケーションが依存しているツールスタックがそれぞれ少しずつバージョンあがっているので、手軽に環境用意するための手順を備忘録がてら書いておきます。この記事の流れで [React-Router 利用時のフォールバックコンテンツ返す設定は別に書いた](https://qiita.com/ma2saka/items/861aef843a85fec42564)。

- webpack 4.x
- webpack-serve 2
- babel 7
- babel-loader 8
- react 16.4.x
- node 10.8.0, npm 6.2.0

※ babel 7, babel-loader 8 がそれぞれリリースされました。 

# プロジェクト初期化とパッケージのインストール

以下、適当なプロジェクトディレクトリを作成してその中で行います。

```
npm init -y
npm install react react-dom
npm install --save-dev webpack webpack-serve webpack-cli
npm install --save-dev @babel/preset-env @babel/cli @babel/core @babel/preset-react
npm install --save-dev babel-loader style-loader css-loader html-webpack-plugin
npm install --save-dev babel-plugin-transform-class-properties
```

※　babel は 6系までは `babel-` って命名なので、スコープ付きパッケージを指定すると自動的に7系最新が入ります。

※ `babel-plugin-transform-class-properties` は実質的に必須なのでサンプルコードでは使っていませんが入れています。

# ファイルレイアウトを決める

以下のような構成にすることにします。

```
├── src/
│   └── index.css
│   └── index.js
│   └── index.html
├── dist/
├── package.json
├── babel.config.js
└── webpack.config.js
```

当初JS以外は `public/` 以下に静的ファイルとして置いていましたが、エントリポイントとなるHTMLは `html-webpack-plugin` で出力し、CSSは `style-loader` でJSに組み込む形に変えました。全部 webpack 経由でまとめた方が開発サーバでのリロードの挙動などが快適なことに気が付きました。またキャッシュバスター埋め込みなどを考えてもHTMLはwebpack管理下の方が何かと楽です。

# webpack.config.js を置く

```webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    mode: process.env.WEBPACK_SERVE ? 'development' : 'production',
    entry: {
      main: path.resolve(__dirname, './src/', "index.js")
    },
    output: {
        path: path.resolve(__dirname, './dist/'),
        publicPath: '/',
        filename: '[name]-[hash].js'
    },
    module: {
      rules: [
      {
        test: /\.jsx?$/,
        exclude:[ /node_modules/ ],
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        exclude: [/node_modules/],
        use: ["style-loader", { loader: "css-loader", options: { url: false, modules: true } }]
      }]
    },
    devtool: 'source-map',
    serve: {
        open: true,
        port: 8080,
        content: path.resolve(__dirname, 'public'),
    },
    plugins: [new HtmlWebpackPlugin({ template: "src/index.html" })]
}
```

ここでは serve.config.js をおかず webpack.config.js に webpack-serve の設定も書いています。`css-loader` は `options: { modules: true }` で CSS Modules サポートです。

# babel.config.js を置く

```babel.config.js
module.exports = {
  presets: [
    "@babel/preset-env", 
    "@babel/preset-react"
  ],
  plugins: ["transform-class-properties"]
}
```

# src/index.js を作成する

```
import React from "react";
import { render } from "react-dom";
import "./index.css";

class App extends React.Component {
  render() {
    return (
      <div>
        <h1> Hello React!</h1>
        <p> this is a component.</p>
      </div>
    );
  }
}

render(<App />, document.getElementById("content"));

```

`#content` 要素に `<App />` をマウントする簡単なコードです。

# src/index.css を作成する

懐かしい感じに黒背景に白文字にします。

```
body {
  color: #fff;
  background-color: #000;
}
```

# src/index.html を作成する

html、header、body などは省略しています。script 要素は `webpack-html-plugin` が補完してくれるので、Reactの描画ターゲットとなるdiv要素一つだけを置いています。

```
<div id="content"/>
```

# 開発サーバで実行する

```
$ npx webpack-serve
```

サーバが 8080 番ポートで立ち上がり、ブラウザが起動すると思います。（`http://0.0.0.0:8080`）

この時、環境変数 `WEBPACK_SERVE` が設定されるため、webpack の mode には development が設定されます。ブラウザで開いたまま `index.js` を編集すると、livereload 的に再読込が走っていることがわかります。

<img width="610" alt="スクリーンショット 2018-08-16 9.37.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/17911/a40b149c-b581-6414-3d48-b162fea1a13a.png">


# ビルドする

webpack コマンドでビルドを行います。

development

```
$ npx webpack --mode development

Hash: f368235ab2e8b186dd35
Version: webpack 4.17.1
Time: 1253ms
Built at: 2018-08-29 06:22:59
                           Asset      Size  Chunks             Chunk Names
    main-f368235ab2e8b186dd35.js   712 KiB    main  [emitted]  main
main-f368235ab2e8b186dd35.js.map   820 KiB    main  [emitted]  main
                      index.html  95 bytes          [emitted]
Entrypoint main = main-f368235ab2e8b186dd35.js main-f368235ab2e8b186dd35.js.map
[./node_modules/css-loader/index.js?!./src/index.css] ./node_modules/css-loader??ref--5-1!./src/index.css 213 bytes {main} [built]
[./src/index.css] 1.08 KiB {main} [built]
[./src/index.js] 2.78 KiB {main} [built]
    + 24 hidden modules
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html] 215 bytes {0} [built]
    [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 489 bytes {0} [built]
    [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 1 hidden module
```

production

```
$ npx webpack --mode production
Hash: 931ddeccf4361df4a522
Version: webpack 4.17.1
Time: 1263ms
Built at: 2018-08-29 06:27:30
                           Asset      Size  Chunks             Chunk Names
    main-931ddeccf4361df4a522.js   107 KiB       0  [emitted]  main
main-931ddeccf4361df4a522.js.map   270 KiB       0  [emitted]  main
                      index.html  95 bytes          [emitted]
Entrypoint main = main-931ddeccf4361df4a522.js main-931ddeccf4361df4a522.js.map
 [6] ./src/index.js 3.11 KiB {0} [built]
[15] ./src/index.css 1.08 KiB {0} [built]
[16] ./node_modules/css-loader??ref--5-1!./src/index.css 213 bytes {0} [built]
    + 17 hidden modules
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html 215 bytes {0} [built]
    [2] (webpack)/buildin/global.js 489 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 1 hidden module
```

約700kbから約100kbと、production ビルドにするとちゃんと minify かかっていますね。（そしてけっこう大きい）

# package.json に script を追加する

します。

```
  "scripts": {
    "start": "npx webpack-serve",
    "build": "npx webpack --mode production",
```

これで `npm start` して開発サーバが立ち上がるようになりました。

# まとめ

登場人物としては以下だけです。

- package.json
- webpack.config.js
- babel.config.js
- index.html
- index.js 
- index.css