create-react-app で作成したコードをrollupで整形する

#はじめに
最近はreactアプリを作るときは必ず`create-react-app`を使うのですが、正直言って組み込みの `react-script build`で生成されたものって納品しにくいんですよね。`widget`タイプのものを作成した場合は特にそう思います。パフォーマンスの最適化や、ソースコードのサイズも気になるところでして。

で、公式は何て言っているのかなーと思って[覗いてみると](https://reactjs.org/docs/optimizing-performance.html#use-the-production-build)、`brunch`や`browserfy`なんかの他に [`rollup`](https://rollupjs.org/guide/en/)があるのを発見。遅ればせながらこんなツールがあるのを知りました。

ところが、以下のサイトや、[公式の薦める情報](https://gist.github.com/Rich-Harris/cb14f4bc0670c47d00d191565be36bf0)を参考にしたんですが、中々思うように動作しません。

- [Rollupがちょうどいい感じ](https://qiita.com/cognitom/items/e3ac0da00241f427dad6)

色々試行錯誤の末、動作が確認出来ましたので公開します。

#前提
`create-react-app v3.0.1`
`react v16.8.6`

#設定方法
##始めに
アプリを作ります。

```
$ create-react-app sample
$ cd sample
```

##rollupのインストール
本体とプラグインもろとも一緒に

```
$ yarn add --dev rollup \
 rollup-plugin-commonjs \
 rollup-plugin-replace \
 rollup-plugin-babel \
 rollup-plugin-uglify-es \
 rollup-plugin-css-porter \
 rollup-plugin-svg \
 rollup-plugin-node-resolve
```

##rollup.config.jsの設定
プロジェクトフォルダ直下に記述します。

```rollup.config.js
import replace from 'rollup-plugin-replace';
import babel from 'rollup-plugin-babel';
import css from 'rollup-plugin-css-porter';
import svg from 'rollup-plugin-svg';
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify-es';

const env = process.env.NODE_ENV;

export default {
  input: 'src/index.js',
  output: {
    file: 'build/js/bundle.js',
    format: 'iife',
  },
  plugins: [
    replace({ 'process.env.NODE_ENV': JSON.stringify(env) }),
    babel({
      exclude: ['node_modules/**'],
      presets: ['@babel/env', '@babel/preset-react'],
    }),
    css({ dest: 'build/css/bundle.css', raw: false, minified: true }),
    svg({ base64: true }),
    nodeResolve({ browser: true }),
    commonjs(),
    env === 'production' && uglify(),
  ],
};

```
プラウザアプリの場合は `format:'iife'`で問題無いですが、`Node.js`で動かす場合にはまた[別のフォーマットになります](https://rollupjs.org/guide/en#exporting)。
`output:{file:[path]}`, `plugins:{css:{dest:[path]}}`で`ｊｓ`と`css`それぞれの出力先を変更します。

各`plugin`の記述順序は重要です。特に **`babel()`と`commonjs()`の記述順序を入れ変えると動作しません。**


##package.jsonの修正

pakage.json の `"build": "react-script build",`
を以下のように修正します。

```package.json
"scripts": {
  "build": "export NODE_ENV=production && rollup -c",
},
```

##ビルド実行

```
$ yarn build
```

成功すると `build/css/bundle.min.css` `bundle/js/bundle.js`が生成されていますので、htmlに組み込んで下さい。

#htmlも自動作成
`js`や`css`のアウトプット時に パラメーター`[hash]`を指定すると内容をハッシュ化した値がファイル名に追記されるようになります。また、そういった場合はhtmlも自動生成したいですよね。

パッケージ `rollup-plugin-bundle-html`を追加して、テンプレートファイルを作成します。
`[hash]`値が追記されてファイルが作成されるようになると、一度`build`内を消去しないと偉いことになりますので `rollup-plugin-cleaner`も入れておきます。

```
$ yarn add --dev rollup-plugin-bundle-html rollup-plugin-cleaner
```

テンプレートファイルを作成します。(例：`public/index.html`を参考に)

```template/index.html
<!DOCTYPE html>
<html lang="jp">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

```
roolup.config.jsを修正します。

```rollup.config.js
//...
import html from 'rollup-plugin-bundle-html';
import cleaner from 'rollup-plugin-cleaner';

//...

export default {
  input: 'src/index.js',
  output: {
    file: 'build/js/bundle-[hash].js',
    format: 'iife',
  },
  plugins: [
    cleaner({
      targets: ['build/js/', 'build/css/'],
    }),

    //...

    css({ dest: 'build/css/bundle-[hash].css', raw: false, minified: true }),

    //...

    html({
      template: 'template/index.html',
      dest: 'build',
      filename: 'index.html',
      inject: 'body',
    }),
  ],
};

```

ビルドすると `build/index.html`が生成されます。以下のような感じになります。

```build/index.html
<!DOCTYPE html>
<html lang="jp">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="theme-color" content="#000000">
    <title>React App</title>
    <link rel="stylesheet" href="css/bundle-788e2801d0a5197580d3d834ccb87a8a.min.css">
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  
    <script type="text/javascript" src="js/bundle-d6579d4e543ab201447a819af62c3a76.js"></script>
  </body>
</html>
```

#名前付きexportについて

いろんなモジュールを追加してプログラムを作成していくと`build`に失敗することがあります。
たとえば以下のようなエラーが出ます。名前付きのexportを解釈できない場合があるようです。

```
[!] Error: 'createFactory' is not exported by node_modules/react/index.js
https://rollupjs.org/guide/en#error-name-is-not-exported-by-module-
node_modules/recompose/dist/Recompose.esm.js (1:16)
1: import React, { createFactory, Component, createElement } from 'react';
```

こういった場合は`commonjs()`プラグインに以下のような記述をして回避して下さい。

```rollup.config.js
  plugins: [
    commonjs({
      namedExports: {
        'node_modules/react/index.js': [
          'createFactory',
          'Component',
          'createElement',
        ],
      },
    }),
  ]

```
#その他エラー時
調子こいてステートレス関数なんか書いていくとコンパイルエラーになります。そういうときはエラーメッセージをよく読むと対応方法が書いてありますので確認してみて下さい。
自分の場合は `@babel/plugin-proposal-class-properties`を追加しました。

```rollup.config.js
 /*...*/
    babel({
      exclude: ['node_modules/**'],
      presets: ['@babel/env', '@babel/preset-react'],
      plugins: ['@babel/plugin-proposal-class-properties'],
    }),
 /*...*/
```
#修正履歴
- 2019/07/10 使用モジュールを`rollup-plugin-uglify` から　`rollup-plugin-gulify-es`に変更
- 2019/07/10 「その他」セクションを追加