ES2015(ES6)+webpack+babel-loaderで開発環境を構築

# やること

ES2015(ES6)とwebpackを組み合わせて、簡単なWebアプリケーションを作ってみます。
**webpack** はjsやcssなど、webページで読み込まれる部品の依存関係を解決し、合成してくれるモジュールバンドラーです。

webpackはgulp,gruntなどのタスクランナーと組み合わせて使うことも多いですが、今回は使用しません。

以下の条件を満たすプロジェクトを作成します。

- classを継承する
- classをimportして使用する
- IE11が対応していない、ES6のPromiseを使う


#開発環境
- Mac OS X High Sierra
- Webpack 4.20.2


# Node.jsをインストール

必要なモジュールはnpm(Node Package manager)でインストールが出来ます。
まずは[Node.js](https://nodejs.org/en/)をインストールします。
`brew install node`

これでnpmが使えるようになります。


# ディレクトリ構成

今回は以下のようなディレクトリ構成を実現します。

```
dist
└── main.js
lib
├── BusinessMember.js
└── Member.js
node_modules
.babelrc
index.js
index.html
package.json
webpack.config.js
```


# プロジェクトの作成

まずは任意のディレクトリを作成し、その中で以下コマンドでプロジェクトを作成します。
-yオプションをつけないと、各種項目について都度聞かれます。
-yを付けずにそのままEnterを連打してもかまいません。

```bash

$ npm init -y
```

実行するとpackage.jsonが出来上がります。
後述しますが、このファイルに記載されたライブラリ全てをnpm installで
インストールできるようになりますので、他の人に配布する際に環境構築が簡単にできます。


# 各種モジュールのインストール

npmで必要な各種モジュールのインストールをします。

2018/10/3現在、babel-loaderの最新バージョンは8系になります。
8系と7系でコマンドやファイルの内容が異なりますので、2つのバージョンについて説明をします。
以下、バージョンについて特に記載がないものは、どちらでも使用可能です。

```bash:babel-loader8用
$ npm install --save-dev @babel/core @babel/polyfill @babel/preset-env babel-loader webpack webpack-cli
```

```bash:babel-loader7用
$ npm install --save-dev babel-core babel-polyfill babel-preset-env babel-loader@7 webpack webpack-cli
```


ビルド時には必要ですが実行時には不要なパッケージなので、**--save-dev** で入れます。

また、特定のバージョンをインストールしたい場合には、インストールできるバージョンの一覧が以下のコマンドで確認できますので、使用するといいでしょう。

```bash
$ npm info パッケージ名 versions --json
```


--save-devを付けると *package.json* のdevDependenciesに記載されます。

モジュールが無い状態でnpm installを実行すると、
devDependenciesに書かれたライブラリがnode_modulesにインストールされます。
そのためgit管理からはnode_modulesディレクトリは外して問題ありません。


これでライブラリのインストールが終わりました。

確認をしてみましょう。
`npm ls` で確認が出来ます。`--depth 0`をつけるとトップのモジュールのみが確認できます。


```bash

$ npm ls --depth 0
├── @babel/core@7.1.2
├── @babel/polyfill@7.0.0
├── @babel/preset-env@7.1.0
├── babel-loader@8.0.4
├── webpack@4.20.2
└── webpack-cli@3.1.2
```


# HTML,jsファイルの作成

次にHTML,jsファイルを作成します。

## lib/Member.js

```javascript:lib/Member.js
'use strict';

export default class Member {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getName() {
        return this.lastName + ' ' + this.firstName;
    }

    get firstName() {
        return this._firstName;
    }

    set firstName(value) {
        this._firstName = value;
    }

    get lastName() {
        return this._lastName;
    }

    set lastName(value) {
        this._lastName = value;
    }
}

```

## lib/BusinessMember.js
```javascript:lib/BusinessMember.js
'use strict';

import Member from './Member';

export default class BusinessMember extends Member{
    constructor(firstName, lastName, company) {
        super(firstName, lastName);  // 親クラスのコンストラクタは、コンストラクタの1行目で記載する必要がある
        this.company = company;
    }

    get company() {
        return this._company;
    }

    set company(value) {
        this._company = value;
    }

    getName() {
        return this.lastName + ' ' + this.firstName + '/' + this.company;
    }
}
```




## index.js

```javascript:index.js
'use strict';

import BusinessMember from './lib/BusinessMember';

let pro = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('ok');
    }, 500)
});

pro.then(response => {
    let user = new BusinessMember('taro', 'yamada', 'G社');
    console.log(user.getName());
});
```

## index.html

```html:index.html
<!DOCTYPE html>
<html lang="ja-jp">
<head>
    <meta charset="UTF-8">
    <script src="dist/main.js"></script>
    <title>ES6のテスト</title>
</head>
<body>

</body>
</html>
```


# webpackの準備

最後にwebpackを実行するための設定ファイルを作成します。
**.babelrc**には、Babelの設定を記載します。
webpack.config.jsの中に書くことも出来ますが、本格的な開発では分けることが多いと思いますので、分けました。

envの指定は、よくあるbabel-preset-envの書き方です。
https://babeljs.io/docs/plugins/preset-env/


## .babelrc(babel-loader8用)

```javascript:.babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry"
      }
    ]
  ]
}
```

## .babelrc(babel-loader7用)
```javascript:.babelrc
{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["last 2 versions", "safari >= 7"]
      }
    }]
  ]
}
```

**webpack.config.js** にはwebpackコマンド実行時の設定を記載します。

## webpack.config.js(babel-loader8用)

```javascript:webpack.config.js

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: ['@babel/polyfill', './index.js'], // polyfill はIE11などで必要
  output: {
    path: `${__dirname}/dist`,
    filename: 'main.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  }
};
```

## webpack.config.js(babel-loader7用)

```javascript:webpack.config.js

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: ['babel-polyfill', './index.js'], // babel-polyfill はIE11などで必要
  output: {
    path: `${__dirname}/dist`,
    filename: 'main.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  }
};
```




# webpack実行

それではwebpackを実行します。

```bash
$ npx webpack
Hash: 53476175c174b19d1a89
Version: webpack 4.20.2
Time: 2695ms
Built at: 2018/10/03 1:56:45
      Asset     Size  Chunks             Chunk Names
    main.js  370 KiB    main  [emitted]  main
main.js.map  294 KiB    main  [emitted]  main
Entrypoint main = main.js main.js.map
[./index.js] 300 bytes {main} [built]
[./lib/BusinessMember.js] 2.95 KiB {main} [built]
[./lib/Member.js] 1.37 KiB {main} [built]
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 509 bytes {main} [built]
[0] multi @babel/polyfill ./index.js 40 bytes {main} [built]
    + 276 hidden modules

```

これでmain.jsが出来上がりました。

# 動作確認

ChromeやIE11でindex.htmlにアクセスし、コンソールに **yamada taro/G社** と表示されれば成功です。

# npm scriptsの編集
npm scriptsにコマンドを記載することで、簡単に処理が実行できます。
package.jsonの `scripts` にコマンドを記載します。
webpackは `--watch` を付けることで、対象となるファイルが更新されたときに自動でビルドを実行することが可能です。
`watch` という名前で実行できるようにしましょう。
ついでに、webpack自体は`build`という名前で実行できるようにしておきます。


```javascript:package.json
〜
  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch"
  },
〜
```

以下のようにコマンドを実行すると監視のジョブが常駐します。
jsファイルに何かしら処理の変更を加えると、ビルドが自動的に走ることが確認できます。


```bash
$ npm run watch
```

# おまけ
## Jestでテストを行う
Jestを使ってテストを書いていきます。
JestはFacebook製のJSのテストツールです。
https://facebook.github.io/jest/en/

まずはJestをインストールしましょう。
##
```bash
$ npm install --save-dev jest
```

次にテスト用のディレクトリである`test`を作成します。

```bash
$ mkdir test
```

testディレクトリの中にテストを作っていきます。

```javascript:test/BusinessMember.test.js

'use strict';

import BusinessMember from '../lib/BusinessMember';

test('BusinessMember.getName() to be "firstName lastName/company"', function() {
  let user = new BusinessMember('ken', 'sato', 'A社');
  expect(user.getName()).toBe('sato ken/A社');
});
```

```javascript:test/Member.test.js
'use strict';

import Member from '../lib/Member';

test('Member.getName() to be "firstName lastName"', function() {
  let user = new Member('ken', 'sato');
  expect(user.getName()).toBe('sato ken');
});
```

それではテストを実行しましょう。

```bash
$ npx jest
 PASS  test/BusinessMember.test.js
 PASS  test/Member.test.js

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.195s
Ran all test suites.
```

`test`という名前で `npm scripts` にも記載します。

```javascript:package.json
〜
  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch",
    "test": "jest"
  },
〜
```

`test`というコマンドについては、`npm run`の`run`は不要ですので、以下のように実行できます。

```bash
$ npm test
```

以下のように引数を渡せば、カバレッジを取ることも可能です。

```bash
$ npm test -- --coverage
> jest "--coverage"

 PASS  test/BusinessMember.test.js
 PASS  test/Member.test.js
-------------------|----------|----------|----------|----------|-------------------|
File               |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files          |      100 |      100 |      100 |      100 |                   |
 BusinessMember.js |      100 |      100 |      100 |      100 |                   |
 Member.js         |      100 |      100 |      100 |      100 |                   |
-------------------|----------|----------|----------|----------|-------------------|

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.352s
Ran all test suites.

```

# 参考記事
- https://tech.recruit-mp.co.jp/front-end/post-14752/
- https://qiita.com/tiny-studio/items/ce28bf84c76aba53122f