[React][JavaScript] localidentnameを使った開発/テスト/ビルド環境整備

**:warning: DEPRECATED (2019.07.06) :warning:**
古くからの「HTMLとスタイルは別ファイルに分割すべし」との考えに従ってスタイルはコンポーネント毎に専用のCSSを用意してcss-loaderで読み込ませていたが、Css in JSの方が有用と判断したため本記事は有用性を失っている。

---

css-loaderによるlocalidentnameを開発/テスト/ビルド環境で使うための整備方法の備忘録。

**環境**
- node: 10.11.0
- react: 16.5.2
- webpack: 4.20.2

# Eject実行
開発とビルドで異なるwebpack設定を用いるため、Ejectを行う。

```bash
$ yarn eject && yarn
```

# 開発環境整備
開発環境整備の手順を記す。

## Packageアップデート
`config/webpack.config.dev.js`で使われているパッケージをアップデートする。

```bash
$ yarn add autoprefixer path webpack html-webpack-plugin case-sensitive-paths-webpack-plugin react-dev-utils
```

## `react-dev-utils`の最新化
html-webpack-pluginがwebpack 4に対応していないため、エラーが発生する。

```bash
Plugin could not be registered at 'html-webpack-plugin-before-html-processing'. Hook was not found.
BREAKING CHANGE: There need to exist a hook at 'this.hooks'. To create a compatibility layer for this hook, hook into 'this._pluginCompat'.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```
<br>`node_modules/react-dev-utils/InterpolateHtmlPlugin.js`にて、プラグイン追加のコードがwebpack 4と整合しないので`react-dev-utils`を最新化する。

```bash
$ yarn add react-dev-utils@next
```
<br>コードの差分は以下の通り。

```diff:node_modules/react-dev-utils/InterpolateHtmlPlugin.js
-  constructor(replacements) {
-    this.replacements = replacements;
-  }
+  constructor(htmlWebpackPlugin, replacements) {
+    this.htmlWebpackPlugin = htmlWebpackPlugin;
+    this.replacements = replacements;
+  }

-  apply(compiler) {
-    compiler.plugin('compilation', compilation => {
-      compilation.plugin(
-        'html-webpack-plugin-before-html-processing',
-        (data, callback) => {
-          // Run HTML through a series of user-specified string replacements.
-          Object.keys(this.replacements).forEach(key => {
-            const value = this.replacements[key];
-            data.html = data.html.replace(
-              new RegExp('%' + escapeStringRegexp(key) + '%', 'g'),
-              value
-            );
-          });
-          callback(null, data);
-        }
-      );
-    });
-  }
+  apply(compiler) {
+    compiler.hooks.compilation.tap('InterpolateHtmlPlugin', compilation => {
+      this.htmlWebpackPlugin
+        .getHooks(compilation)
+        .beforeEmit.tap('InterpolateHtmlPlugin', data => {
+          // Run HTML through a series of user-specified string replacements.
+          Object.keys(this.replacements).forEach(key => {
+            const value = this.replacements[key];
+            data.html = data.html.replace(
+              new RegExp('%' + escapeStringRegexp(key) + '%', 'g'),
+              value
+            );
+          });
+        });
+    });
+  }
```

## `new InterpolateHtmlPlugin`の引数変更
`html-webpack-plugin`に関数`getHooks`が存在しないため、エラーになる。

```bash
$ yarn start
yarn run v1.10.1
$ node scripts/start.js
this.htmlWebpackPlugin.getHooks is not a function
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```
<br>
`config/webpack.config.dev.js`にて、インスタンス`InterpolateHtmlPlugin`作成時の引数を変更する。

```diff:config/webpack.config.dev.js
-   new InterpolateHtmlPlugin(env.raw),
+   new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
```
<br>
加えて、`html-webpack-plugin`を最新化する。

```bash
$ yarn add html-webpack-plugin@next
```

## `eslint-loader`の最新化

`eslint`が古いため、エラーが発生する。

- https://github.com/webpack-contrib/eslint-loader/issues/215

```bash
$ yarn start
Failed to compile.

./src/index.js
Syntax error: Cannot read property 'eslint' of undefined
URIError: Failed to decode param '/%PUBLIC_URL%/favicon.ico'
```

<br>`eslint-loader`を最新化する。

```bash
$ yarn add eslint-loader@latest
```

## `file-loader`の最新化
`file-loader`が古いため、コンパイルに失敗する。

- https://github.com/webpack-contrib/worker-loader/issues/125

```bash
$ yarn start
Failed to compile.

./src/logo.svg
Syntax error: Cannot read property 'context' of undefined
URIError: Failed to decode param '/%PUBLIC_URL%/favicon.ico'
```

<br>`file-loader`を最新化する。

```bash
$ yarn add file-loader@latest
```

## 動作確認
`yarn start`が成功し、ページが表示されることを確認する。

```bash
$ yarn start
Compiled with warnings.
configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

Search for the keywords to learn more about each warning.
To ignore, add // eslint-disable-next-line to the line before.
```

## `mode`警告を抑制
`mode`を設定して警告を抑制する。

```diff:config/webpack.config.dev.js
module.exports = {
+   mode: 'development',
```

## 最終確認
警告なく`yarn start`が成功することを確認する。

```bash
$ yarn start
Compiled successfully!
You can now view et in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://192.168.11.6:3000/

Note that the development build is not optimized.
To create a production build, use yarn build.
```

## ここまでのまとめ
開発環境整備のために実施したコマンド、編集の要点は以下の通り。

```bash
$ yarn add autoprefixer path webpack html-webpack-plugin@next case-sensitive-paths-webpack-plugin react-dev-utils@next eslint-loader@latest file-loader@latest
```

```diff:config/webpack.config.dev.js
module.exports = {
+   mode: 'development',

-   new InterpolateHtmlPlugin(env.raw),
+   new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
```

# テスト環境整備
テスト環境整備の手順を記す。

## `src/App.js`の編集
`src/App.js`を以下のように編集する。

```javascript:src/App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import style from './App.css';

class App extends Component {
  render() {
    return (
      <div className={style.App}>
        <header className={style.appHeader}>
          <img src={logo} className={style.appLogo} alt="logo" />
          <h1 className={style.appTitle}>Welcome to React</h1>
        </header>
        <p className={style.appIntro}>
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;
```

## `config/webpack.config.dev.js`の編集
`config/webpack.config.dev.js`を以下のように編集する。

```diff:config/webpack.config.dev.js
-          {
-            test: /\.css$/,
-            use: [
-              require.resolve('style-loader'),
-              {
-                loader: require.resolve('css-loader'),
-                options: {
-                  importLoaders: 1,
-                },
-              },
-              {
-                loader: require.resolve('postcss-loader'),
-                options: {
-                  // Necessary for external CSS imports to work
-                  // https://github.com/facebookincubator/create-react-app/issues/2677
-                  ident: 'postcss',
-                  plugins: () => [
-                    require('postcss-flexbugs-fixes'),
-                    autoprefixer({
-                      browsers: [
-                        '>1%',
-                        'last 4 versions',
-                        'Firefox ESR',
-                        'not ie < 9', // React doesn't support IE8 anyway
-                      ],
-                      flexbox: 'no-2009',
-                    }),
-                  ],
-                },
-              },
-            ],
-          },
+          {
+            test: /\.css$/,
+            use: [
+              require.resolve('style-loader'),
+              {
+                loader: 'css-loader', 
+                options: {
+                  modules: true,
+                  sourceMap: true, 
+                  camelCase: true, 
+                  localIdentName: '[local]-[hash:base64:5]',
+                  importLoaders: 1
+                }
+              }
+            ]
+          },
```

## `css-loader`動作確認
`yarn start`を停止して再実行し、デザインに変化が無いことを確認する。

```bash
^C
$ yarn start
```

## テストコード追記
`src/App.test.js`にて、以下のテストを追記する。

```diff:src/App.test.js
+ import renderer from 'react-test-renderer';
+ it('snapshot test', () => {
+   const tree = renderer
+     .create(<App />)
+     .toJSON();
+   expect(tree).toMatchSnapshot();
+ });
```

## テスト実行
テストを実行し、スナップショットを作成する。

```bash
$ yarn add react-test-renderer
$ yarn test
```

<br>生成されたスナップショットにて、クラス名が展開されていないことを確認する。

```text:src/__snapshots__/App.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`snapshot test 1`] = `
<div
  className={undefined}
>
  <header
    className={undefined}
  >
    <img
      alt="logo"
      className={undefined}
      src="logo.svg"
    />
    <h1
      className={undefined}
    >
      Welcome to React
    </h1>
  </header>
  <p
    className={undefined}
  >
    To get started, edit 
    <code>
      src/App.js
    </code>
     and save to reload.
  </p>
</div>
`;
```

## `identity-obj-proxy`インストール
パッケージ`identity-obj-proxy`をインストールし、`package.json`を以下の通りに追記する。

```bash
$ yarn add -D identity-obj-proxy
```

```diff:package.json
    "moduleNameMapper": {
-      "^react-native$": "react-native-web"
+      "^react-native$": "react-native-web",
+      "\\.(css|less)$": "identity-obj-proxy"
    },
```

## 動作確認
`yarn test`を停止して再実行し、スナップショット内でクラス名が展開されていることを確認する。

```bash
$ yarn test
 FAIL  src/App.test.js
  ● snapshot test

    expect(value).toMatchSnapshot()

    Received value does not match stored snapshot 1.

    - Snapshot
    + Received

    @@ -1,24 +1,24 @@
     <div
    -  className={undefined}
    +  className="App"
     >
       <header
    -    className={undefined}
    +    className="appHeader"
       >
         <img
           alt="logo"
    -      className={undefined}
    +      className="appLogo"
           src="logo.svg"
         />
         <h1
    -      className={undefined}
    +      className="appTitle"
         >
           Welcome to React
         </h1>
       </header>
       <p
    -    className={undefined}
    +    className="appIntro"
       >
         To get started, edit
         <code>
           src/App.js
         </code>

      at Object.<anonymous>.it (src/App.test.js:16:16)
          at new Promise (<anonymous>)
      at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)

  ✓ renders without crashing (21ms)
  ✕ snapshot test (12ms)

Snapshot Summary
 › 1 snapshot test failed in 1 test suite. Inspect your code changes or press `u` to update them.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   1 failed, 1 total
Time:        2.955s
Ran all test suites related to changed files.

Watch Usage
 › Press u to update failing snapshots.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press q to quit watch mode.
 › Press Enter to trigger a test run.
```

<br>`u`キーを押下してスナップショットを更新する。snapファイル内でクラス名が展開されていること、テストを全てパスすることを確認する。

```bash
 PASS  src/App.test.js
  ✓ renders without crashing (5ms)
  ✓ snapshot test (4ms)

Snapshot Summary
 › 1 snapshot updated in 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   1 updated, 1 total
Time:        0.047s, estimated 1s
Ran all test suites.

Watch Usage: Press w to show more.
```

```text:src/__snapshots__/App.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`snapshot test 1`] = `
<div
  className="App"
>
  <header
    className="appHeader"
  >
    <img
      alt="logo"
      className="appLogo"
      src="logo.svg"
    />
    <h1
      className="appTitle"
    >
      Welcome to React
    </h1>
  </header>
  <p
    className="appIntro"
  >
    To get started, edit 
    <code>
      src/App.js
    </code>
     and save to reload.
  </p>
</div>
`;
```

## ここまでのまとめ
テスト環境整備のために実施したコマンド、編集の要点は以下の通り。

```bash
$ yarn add -D identity-obj-proxy
```

```diff:package.json
    "moduleNameMapper": {
-      "^react-native$": "react-native-web"
+      "^react-native$": "react-native-web",
+      "\\.(css|less)$": "identity-obj-proxy"
    },
```

```diff:config/webpack.config.dev.js
-          {
-            test: /\.css$/,
-            use: [
-              require.resolve('style-loader'),
-              {
-                loader: require.resolve('css-loader'),
-                options: {
-                  importLoaders: 1,
-                },
-              },
-              {
-                loader: require.resolve('postcss-loader'),
-                options: {
-                  // Necessary for external CSS imports to work
-                  // https://github.com/facebookincubator/create-react-app/issues/2677
-                  ident: 'postcss',
-                  plugins: () => [
-                    require('postcss-flexbugs-fixes'),
-                    autoprefixer({
-                      browsers: [
-                        '>1%',
-                        'last 4 versions',
-                        'Firefox ESR',
-                        'not ie < 9', // React doesn't support IE8 anyway
-                      ],
-                      flexbox: 'no-2009',
-                    }),
-                  ],
-                },
-              },
-            ],
-          },
+          {
+            test: /\.css$/,
+            use: [
+              require.resolve('style-loader'),
+              {
+                loader: 'css-loader', 
+                options: {
+                  modules: true,
+                  sourceMap: true, 
+                  camelCase: true, 
+                  localIdentName: '[local]-[hash:base64:5]',
+                  importLoaders: 1
+                }
+              }
+            ]
+          },
```

# ビルド環境整備
ビルド環境整備の手順を記す。

## Packageアップデート
`config/webpack.config.prod.js`で使われているパッケージをアップデートする。なお、開発環境構築時に追加済みのパッケージは除外する。

```bash
$ yarn add webpack-manifest-plugin sw-precache-webpack-plugin
```

## `UglifyJsPlugin`の削除
`UglifyJsPlugin`はwebpack 4にて廃止されているため、エラーが発生する。

```bash
$ yarn build
yarn run v1.10.1
$ node scripts/build.js
/Users/thara/Desktop/just-eject/node_modules/webpack/lib/webpack.js:185
                        throw new RemovedPluginError(errorMessage);
                        ^

Error: webpack.optimize.UglifyJsPlugin has been removed, please use config.optimization.minimize instead.
    at Object.get [as UglifyJsPlugin] (/Users/thara/Desktop/just-eject/node_modules/webpack/lib/webpack.js:185:10)
    at Object.<anonymous> (/Users/thara/Desktop/just-eject/config/webpack.config.prod.js:266:26)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at Object.<anonymous> (/Users/thara/Desktop/just-eject/scripts/build.js:21:16)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

<br>`config/webpack.config.prod.js`より、`UglifyJsPlugin`の記述を削除する。

```diff:config/webpack.config.prod.js
-    // Minify the code.
-    new webpack.optimize.UglifyJsPlugin({
-      compress: {
-        warnings: false,
-        // Disabled because of an issue with Uglify breaking seemingly valid code:
-        // https://github.com/facebookincubator/create-react-app/issues/2376
-        // Pending further investigation:
-        // https://github.com/mishoo/UglifyJS2/issues/2011
-        comparisons: false,
-      },
-      mangle: {
-        safari10: true,
-      },
-      output: {
-        comments: false,
-        // Turned on because emoji and regex is not minified properly using default
-        // https://github.com/facebookincubator/create-react-app/issues/2488
-        ascii_only: true,
-      },
-      sourceMap: shouldUseSourceMap,
-    }),
```

## `new InterpolateHtmlPlugin`の引数変更
開発環境整備と同様のエラー。

```bash
$ yarn build
yarn run v1.10.1
$ node scripts/build.js
Creating an optimized production build...
Failed to compile.

this.htmlWebpackPlugin.getHooks is not a function


error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

<br>同様に修正する。

```diff:config/webpack.config.prod.js
-    new InterpolateHtmlPlugin(env.raw),
+    new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
```

## `extract-text-webpack-plugin`の置き換え
`extract-text-webpack-plugin`がwebpack 4に対応していないため、エラーが発生する。

- https://github.com/webpack/webpack/issues/6568

```bash
$ yarn build
yarn run v1.10.1
$ node scripts/build.js
Creating an optimized production build...
(node:52482) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
/Users/thara/Desktop/just-eject/node_modules/webpack/lib/Chunk.js:827
                throw new Error(
                ^

Error: Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead
    at Chunk.get (/Users/thara/Desktop/just-eject/node_modules/webpack/lib/Chunk.js:827:9)
    at /Users/thara/Desktop/just-eject/node_modules/extract-text-webpack-plugin/dist/index.js:176:48
    at Array.forEach (<anonymous>)
    at /Users/thara/Desktop/just-eject/node_modules/extract-text-webpack-plugin/dist/index.js:171:18
    at AsyncSeriesHook.eval [as callAsync] (eval at create (/Users/thara/Desktop/just-eject/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:7:1)
    at AsyncSeriesHook.lazyCompileHook (/Users/thara/Desktop/just-eject/node_modules/tapable/lib/Hook.js:154:20)
    at Compilation.seal (/Users/thara/Desktop/just-eject/node_modules/webpack/lib/Compilation.js:1215:27)
    at hooks.make.callAsync.err (/Users/thara/Desktop/just-eject/node_modules/webpack/lib/Compiler.js:541:17)
    at _done (eval at create (/Users/thara/Desktop/just-eject/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:9:1)
    at _err1 (eval at create (/Users/thara/Desktop/just-eject/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:32:22)
    at _addModuleChain (/Users/thara/Desktop/just-eject/node_modules/webpack/lib/Compilation.js:1066:12)
    at processModuleDependencies.err (/Users/thara/Desktop/just-eject/node_modules/webpack/lib/Compilation.js:982:9)
    at process._tickCallback (internal/process/next_tick.js:61:11)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

<br>`extract-text-webpack-plugin`を`mini-css-extract-plugin`に置き換える。

- https://github.com/webpack-contrib/extract-text-webpack-plugin/tree/0b69b72aa7cf702a01a42a733432da5c344eed1c#usage

```bash
$ yarn add mini-css-extract-plugin
```

```diff:config/webpack.config.prod.js
- const ExtractTextPlugin = require('extract-text-webpack-plugin');
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin");

- // ExtractTextPlugin expects the build output to be flat.
- // (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
- // However, our output is structured with css, js and media folders.
- // To have this structure working with relative paths, we have to use custom options.
- const extractTextPluginOptions = shouldUseRelativeAssetPaths
-   ? // Making sure that the publicPath goes back to to build folder.
-     { publicPath: Array(cssFilename.split('/').length).join('../') }
-   : {};

-           // The notation here is somewhat confusing.
-           // "postcss" loader applies autoprefixer to our CSS.
-           // "css" loader resolves paths in CSS and adds assets as dependencies.
-           // "style" loader normally turns CSS into JS modules injecting <style>,
-           // but unlike in development configuration, we do something different.
-           // `ExtractTextPlugin` first applies the "postcss" and "css" loaders
-           // (second argument), then grabs the result CSS and puts it into a
-           // separate file in our build process. This way we actually ship
-           // a single CSS file in production instead of JS code injecting <style>
-           // tags. If you use code splitting, however, any async bundles will still
-           // use the "style" loader inside the async code so CSS from them won't be
-           // in the main CSS file.
-           {
-             test: /\.css$/,
-             loader: ExtractTextPlugin.extract(
-               Object.assign(
-                 {
-                   fallback: {
-                     loader: require.resolve('style-loader'),
-                     options: {
-                       hmr: false,
-                     },
-                   },
-                   use: [
-                     {
-                       loader: require.resolve('css-loader'),
-                       options: {
-                         importLoaders: 1,
-                         minimize: true,
-                         sourceMap: shouldUseSourceMap,
-                       },
-                     },
-                     {
-                       loader: require.resolve('postcss-loader'),
-                       options: {
-                         // Necessary for external CSS imports to work
-                         // https://github.com/facebookincubator/create-react-app/issues/2677
-                         ident: 'postcss',
-                         plugins: () => [
-                           require('postcss-flexbugs-fixes'),
-                           autoprefixer({
-                             browsers: [
-                               '>1%',
-                               'last 4 versions',
-                               'Firefox ESR',
-                               'not ie < 9', // React doesn't support IE8 anyway
-                             ],
-                             flexbox: 'no-2009',
-                           }),
-                         ],
-                       },
-                     },
-                   ],
-                 },
-                 extractTextPluginOptions
-               )
-             ),
-             // Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
-           },
+           {
+             test: /\.css$/,
+             use: [
+               MiniCssExtractPlugin.loader,
+               {
+                 loader: 'css-loader', 
+                 options: {
+                   modules: true,
+                   camelCase: true, 
+                   minimize: true, 
+                   localIdentName: '[local]-[hash:base64:5]',
+                   importLoaders: 1
+                 }
+               }
+             ]
+           },

-     // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
-     new ExtractTextPlugin({
-       filename: cssFilename,
-     }),
+     new MiniCssExtractPlugin({
+       filename: 'static/css/[name].[hash:8].css',
+     }),
```

## ビルド確認
ビルドが成功することを確認する。

```bash
$ yarn build
yarn run v1.10.1
$ node scripts/build.js
Creating an optimized production build...
Compiled with warnings.

configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

Search for the keywords to learn more about each warning.
To ignore, add // eslint-disable-next-line to the line before.

File sizes after gzip:

  37.58 KB  build/static/js/main.bb9d475b.js
  308 B     build/static/css/main.3f8bfe3a.css

The project was built assuming it is hosted at the server root.
You can control this with the homepage field in your package.json.
For example, add this to build it for GitHub Pages:

  "homepage" : "http://myname.github.io/myapp",

The build folder is ready to be deployed.
You may serve it with a static server:

  yarn global add serve
  serve -s build

Find out more about deployment here:

  http://bit.ly/CRA-deploy

✨  Done in 17.77s.
```

## `mode`警告を抑制
`mode`を設定して警告を抑制する。

```diff:config/webpack.config.prod.js
module.exports = {
+   mode: 'production',
```

## 動作確認
警告なくビルドが完了する。`serve -s build`を実行して正常な動作を確認すること。

```bash
$ yarn build
yarn run v1.10.1
$ node scripts/build.js
Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  37.58 KB  build/static/js/main.bb9d475b.js
  308 B     build/static/css/main.243fe7ef.css

The project was built assuming it is hosted at the server root.
You can control this with the homepage field in your package.json.
For example, add this to build it for GitHub Pages:

  "homepage" : "http://myname.github.io/myapp",

The build folder is ready to be deployed.
You may serve it with a static server:

  yarn global add serve
  serve -s build

Find out more about deployment here:

  http://bit.ly/CRA-deploy

✨  Done in 7.29s.
```

## ここまでのまとめ
ビルド環境整備のために実施した編集の要点は以下の通り。

```bash
$ yarn add webpack-manifest-plugin sw-precache-webpack-plugin　mini-css-extract-plugin
```

```diff:config/webpack.config.prod.js
- const ExtractTextPlugin = require('extract-text-webpack-plugin');
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
+   mode: 'production',

- // ExtractTextPlugin expects the build output to be flat.
- // (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
- // However, our output is structured with css, js and media folders.
- // To have this structure working with relative paths, we have to use custom options.
- const extractTextPluginOptions = shouldUseRelativeAssetPaths
-   ? // Making sure that the publicPath goes back to to build folder.
-     { publicPath: Array(cssFilename.split('/').length).join('../') }
-   : {};

-           // The notation here is somewhat confusing.
-           // "postcss" loader applies autoprefixer to our CSS.
-           // "css" loader resolves paths in CSS and adds assets as dependencies.
-           // "style" loader normally turns CSS into JS modules injecting <style>,
-           // but unlike in development configuration, we do something different.
-           // `ExtractTextPlugin` first applies the "postcss" and "css" loaders
-           // (second argument), then grabs the result CSS and puts it into a
-           // separate file in our build process. This way we actually ship
-           // a single CSS file in production instead of JS code injecting <style>
-           // tags. If you use code splitting, however, any async bundles will still
-           // use the "style" loader inside the async code so CSS from them won't be
-           // in the main CSS file.
-           {
-             test: /\.css$/,
-             loader: ExtractTextPlugin.extract(
-               Object.assign(
-                 {
-                   fallback: {
-                     loader: require.resolve('style-loader'),
-                     options: {
-                       hmr: false,
-                     },
-                   },
-                   use: [
-                     {
-                       loader: require.resolve('css-loader'),
-                       options: {
-                         importLoaders: 1,
-                         minimize: true,
-                         sourceMap: shouldUseSourceMap,
-                       },
-                     },
-                     {
-                       loader: require.resolve('postcss-loader'),
-                       options: {
-                         // Necessary for external CSS imports to work
-                         // https://github.com/facebookincubator/create-react-app/issues/2677
-                         ident: 'postcss',
-                         plugins: () => [
-                           require('postcss-flexbugs-fixes'),
-                           autoprefixer({
-                             browsers: [
-                               '>1%',
-                               'last 4 versions',
-                               'Firefox ESR',
-                               'not ie < 9', // React doesn't support IE8 anyway
-                             ],
-                             flexbox: 'no-2009',
-                           }),
-                         ],
-                       },
-                     },
-                   ],
-                 },
-                 extractTextPluginOptions
-               )
-             ),
-             // Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
-           },
+           {
+             test: /\.css$/,
+             use: [
+               MiniCssExtractPlugin.loader,
+               {
+                 loader: 'css-loader', 
+                 options: {
+                   modules: true,
+                   camelCase: true, 
+                   minimize: true, 
+                   localIdentName: '[local]-[hash:base64:5]',
+                   importLoaders: 1
+                 }
+               }
+             ]
+           },

-    new InterpolateHtmlPlugin(env.raw),
+    new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),

-    // Minify the code.
-    new webpack.optimize.UglifyJsPlugin({
-      compress: {
-        warnings: false,
-        // Disabled because of an issue with Uglify breaking seemingly valid code:
-        // https://github.com/facebookincubator/create-react-app/issues/2376
-        // Pending further investigation:
-        // https://github.com/mishoo/UglifyJS2/issues/2011
-        comparisons: false,
-      },
-      mangle: {
-        safari10: true,
-      },
-      output: {
-        comments: false,
-        // Turned on because emoji and regex is not minified properly using default
-        // https://github.com/facebookincubator/create-react-app/issues/2488
-        ascii_only: true,
-      },
-      sourceMap: shouldUseSourceMap,
-    }),

-     // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
-     new ExtractTextPlugin({
-       filename: cssFilename,
-     }),
+     new MiniCssExtractPlugin({
+       filename: 'static/css/[name].[hash:8].css',
+     }),
```