webpackとBabelの基本を理解する(3) ―webpackとBabel編―

独学の内容をまとめたものです。誤りがございましたら、ご連絡いただけると幸いです。

**リンク**
1. [webpackとBabelの基本を理解する(1) ―webpack編―](https://qiita.com/koedamon/items/3e64612d22f3473f36a4)
2. [webpackとBabelの基本を理解する(2) ―Babel編―](https://qiita.com/koedamon/items/92c986456e4b9e845acd)
3. webpackとBabelの基本を理解する(3) ―webpackとBabel編―（本記事）
4.  [webpackとBabelの基本を理解する(4) ―React編―](https://qiita.com/koedamon/items/8fb75a7c001da40c7bea)
5. [webpackとBabelの基本を理解する(5) ―Sass編―](https://qiita.com/koedamon/items/a5be9456e0591db4d1f4)

# 概要
## この記事の概要

- 目的
  - フロントエンドの環境構築に利用されるツールへの理解を深める
- 本記事のゴール
  - webpackにBabelオプションを追加する方法を知る
- 対象者
  - WEBフロント担当者
  - HTML,CSS,JavaScript(es2015含む)の基本的な構文を理解している人
  - npmの利用方法を理解している人
- 環境・バージョン
  - Windows10
  - Node.js(推奨版) 10.15.01
  - npm 6.4.1
  - webpack 4.29.6
  - webpack-cli 3.2.3
  - babel-core 7.3.4
  - babel-cli 7.2.3 
  - preset-env 7.3.4

# webpackのLoader
webpackは、基本的にimport（require）文を頼りに、芋づる式にJSまたはJSONファイルをまとめていくツールです。 
JS以外のファイル（例えばCSS）なども対象としたい場合や、まとめる前にコードをコンパイルする必要がある場合、元のソースをwebpackがバンドル可能な状態に変換するために、Loaderを利用します。

## Loaderの指定方法
`webpack.config.js`に`module.rules`を追加します。`rules`に記述する基本の設定は二つです。

1. test: どのタイプのファイルを変換対象とするかを文字列または正規表現で指定します。 
2. use: どのloaderを使用するかを指定します。

これについては、公式サイトのコンセプト説明が分かりやすかったです。[Loaders](https://webpack.js.org/concepts#loaders)

```js
module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }
};
```

>> "Hey webpack compiler, when you come across a path that resolves to a '.txt' file inside of a require()/import statement, use the raw-loader to transform it before you add it to the bundle."
>
> やあ！webpackコンパイラ、君がimportかrequire文で'.txt'に該当するファイルに遭遇したら、バンドる前に`raw-loader`を使ってそのファイルを変換してくれないかな。

配列を使って、loaderを複数指定することも可能です。この場合、末尾のloaderから順に適用されます。

```js
module: {
  rules: [
    {
      test: /\.txt$/,
      use: ['raw-loader', 'a-loader', 'c-loader']
    }
  ]
}
```

**exclude**
node_modulesフォルダ内のファイル群など、loaderの対象外としたい場合は、`rules`に`exclude`を追加します。

**options**
loaderに設定情報を持たせたい場合は、`use`の項目を`loader`と`options`に分けて記述します。

```js
module.exports = {
  module: {
    rules: [
      { 
        test: /\.txt$/,
        exclude: /node_modules/,　//対象外ディレクトリ
        use: [
          {
            loader: 'raw-loader', //loader名
            options: {...}        //設定情報
          },
          'a-loader',
          'c-loader'
        ]
      }
    ]
  }
};
```

# babel-loader
## インストール
バンドる前にバベるには、babel-loaderを利用します。

```console
$ npm install babel-loader --save-dev
```

もちろんBabel本体も必須なので、まだの方はこちら

```console
$ npm install babel-loader @babel/core @babel/preset-env --save-dev
```
## 設定
`webpack.config.js`に、設定値を追加します。`options`の記述方法は基本的に`.babelrc`と同様です。プリセットの名前を記述するだけの設定ならば、`webpack.config.js`の`options`に集約でき、`.babelrc`は不要になります。

```js
module.exports = {
  /* 略 */
  module: {
    rules: [
      { 
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',   //loader名
          options: {                //Babelの設定
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
};
```

## 実行
下記の様に、es2015以降の構文をふんだんに盛り込んだファイルを準備します。

```
sample/
  ├ src/
  │   ├ component/
  │   │    └ test.js
  │   └ index.js 
  └ dist/
```

```js
/**
 * /src/component/test.js
 */ 
const test = ({ foo, bar, ...others }) => {
  console.log(others);
}

export { test };


/**
 * /src/index.js
 */
import { test } from './component/test';

const keyName = 'hei';

let params = {
  foo: 'foooooo',
  bar: 'baaaaa',
  baz: 'zzzzzz',
  [keyName]: 'moimoi'
};

test(params);
```

webpack.config.jsはこんな感じ

```js
module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',
  entry: './src/index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'sample.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
};
```

**webpackを**実行します。

```console
$ npx webpack
```

出力された`/dist/sample.js`では、es5の構文に変換されていることが確認できます。

```js
/* 略 */

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(...);
function _objectWithoutProperties(source, excluded) {...}

function _objectWithoutPropertiesLoose(source, excluded) {...}

var test = function test(_ref) {
  var foo = _ref.foo,
      bar = _ref.bar,
      others = _objectWithoutProperties(_ref, ["foo", "bar"]);

  console.log(others);
};

/* 略 */
```

## 設定ファイルの統合とpolyfillの設定
上記では、polyfillの設定をしていないので、例えばPromiseなどの新機能は変換されません。しかし、下記設定ではエラーが発生しました。

```js
/**
 * webpack.config.js
 */
use: {
  loader: 'babel-loader',
  options: {
    presets: [
      [
        '@babel/preset-env', {
          'useBuiltIns': 'usage' // useBuiltInsオプションを追加
        }
      ]
    ]
  }
}

/**
 * /src/component/test.js
 */
const test = ({ foo, bar, ...others }) => {
  console.log(foo, bar);
  // polyfillが必要
  new Promise((resolve, rejects) => {
    setTimeout(() => {
      resolve(others);
    }, 2000);

  }).then(data => {
    console.log(data);
  })
}

export { test }; 
```

```
# エラーの一部

ERROR in ./src/component/test.js
Module not found: Error: Can't resolve 'core-js/modules/es6.array.index-of' in 'C:\sample\src\component'
 @ ./src/component/test.js 1:0-44
 @ ./src/index.js
```

:thinking: polyfillのモジュールを`node_module`ではなく、元ファイルがあるフォルダを起点に探している？？同じ内容で`.babelrc`または`package.json`で設定した場合は、無事コンパイルされます。
語学力が低いのでちゃんと読めているか不安ですが、こちらのやり取りを見る限り`webpack.config.js`だけでは不十分なようです。
[@babel/preset-env doesn't seem to work in combination with babel-loader and .browserlistrc](https://github.com/babel/babel/issues/8578)

じゃあ、webpack.config.jsではPresetsの指定はいらないんじゃないかと思って、`use:'babel-loader'`だけにするとまたエラーになります。Presetsの指定自体は必要です。

- webpack.config.jsでは、Presetsの名前の列挙のみする。
- package.jsonで、Presetsの設定を行う。

この2点で無事にPolyfillを利用できるようになりました。
なお、このエラーは`@babel/preset-env`の`useBuiltIns`オプションで遭遇したものです。他のPresetsでも発生しているわけではありません。2019年2月現在「useBuiltIns:'usage'」の項目には「experimental(実験的)」とあります。（将来的には解決するかも？）

```js
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}

// package.json
{
  "babel": {
    "presets": [
      [
        "@babel/preset-env", {
          "useBuiltIns": "usage"
        }
      ]
    ]
  }
}
```

いやいや、あなたの設定方法がそもそも間違っているんだよとか、こうするとうまくいくよ的な情報がございましたら、コメントいただけると幸いです。