Babelの基本的な使い方

先日[webpack.config.jsがわからない](https://qiita.com/one-kelvin/items/b810aafb6b5ef90789a3)と嘆いたところ多くの方にいいねをいただきました。Loaderに関する部分で続きを書いているのですが、[Babel](https://babeljs.io/)もわからなくて困っていた時期があったのでそのときのことをまとめたいと思います。

# `babel-cli`でBabelと仲良くなる

[ドキュメント](https://babeljs.io/docs/usage/cli/)

初めにBabelをコンソールから使えるようにする`babel-cli`を使ってみたいと思います。`mybabel`ディレクトリを作り、その中でやっていきます。nodeはv9.2.0を使っています。

```sh:shell
$ mkdir mybabel
$ cd mybabelc
$ npm init -y
```

`package.json`ファイルは今のところ下のようになっています。

```sh:shell
$ cat package.json
{
    "name": "mybabel",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC"
}
```

## `babel-cli`のインストール

`babel-cli`をインストールします。`babel-cli`は次のような理由からグローバルではなく、プロジェクトごとにインストールするごとが推奨されています。

- 一つのマシン内に存在する異なるプロジェクトで、別のバージョンのBabelを利用できる
- プロジェクトそれぞれのBabelで個別にアップデートができる
- 開発環境が暗黙的にBabelに依存しているということがなくなる
- プロジェクトを再設定することが簡単になる

一つのプロジェクトは依存関係の観点でマシンからは独立しているべきであり、依存関係を表す`package.json`の情報さえあれば誰でもどこにでも同じ環境が作れるということが重要なのだと言っているのだと思います。
ローカルに`babel-cli`をインストールするには`$ npm install --save-dev babel-cli`を実行します。`--save-dev`オプションは付けなくても問題ありませんが、開発時に必要となる依存パッケージだということを表しています。`package.json`に`devDependencies`の項目が追加されます。

```sh:shell
"devDependencies": {
    "babel-cli": "^6.26.0"
}
```

## `babel-cli`を使ってみる

`babel-cli`でJavascriptをトランスパイルしてみます。次のような`script.js`ファイルを用意します。

```js:script.js

const arr = [1, 2, 3]
arr.map(n => n ** 2)
```

ES6から導入されたアロー関数が使われています。きっとこの部分が変換されるはずです。トランスパイルして結果をコンソール上に表示してみます。

```sh:shell
$ ./node_modules/.bin/babel script.js

const arr = [1, 2, 3];
arr.map(n => n ** 2);
```

ほぼ変わってません。セミコロンが付いたくらいです。実は`babel-cli`だけではES5的な記法にまでは変換してくれません。

## `babel-preset-env`のインストールと`.babelrc`の設定

ES6で書かれたJavascriptをES5にまでトランスパイルするためには、`babel-preset-env`をインストールします。ローカルインストールなので`$ npm install --save-dev babel-preset-env`を実行します。`babel-preset-es2015`も同じ機能を提供しますが、これからは`babel-preset-env`を使います。次に、Babelに対して`babel-preset-env`を使うということを通知してあげます。次のような`.babelrc`ファイルを`package.json`があるディレクトリに作成します。

```sh:.babelrc
{
    "presets": ["env"]
}
```

Babelの設定は基本的に`.babelrc`ファイルに記述することが推奨されています。`.babelrc`ファイルに設定を記述する以外にも`package.json`に`"babel"`という項目を作る、トランスパイル時のオプションとしてコンソールで指定するなどが可能です。今回は`.babelrc`を使います。オプションを指定したので、もう一度、`script.js`をトランスパイルしてみましょう。

```sh:shell
$ ./node_modules/.bin/babel script.js
"use strict";

var arr = [1, 2, 3];
arr.map(function (n) {
  return Math.pow(n, 2);
});
```

かなり見た目が変わりました。strictモードが設定され、`const`は`var`に変わり、アロー関数は`function`記法に変わり、`return`も追加されました。他のES6構文でも試してみましょう。`class.js`ファイルを作ります。

```js:class.js
class BaseClass {
    constructor() {
        this.name = "BaseClass"
    }

    Base_func() {
        console.log('I am Base_a method.')
    }
}

class ExtendedClass extends BaseClass {
    constructor() {
        super()
        this.name = "ExtendedClass"
    }

    Extended_func() {
        const msg = `I'm a method of ${this.name}.`
        console.log(msg)
    }
}

const e = new ExtendedClass
e.Extended_func()
```

トランスパイルを実行します。

```sh:shell
$ ./node_modules/.bin/babel class.js
"use strict";

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var BaseClass = function () {
    function BaseClass() {
        _classCallCheck(this, BaseClass);

        this.name = "BaseClass";
    }

    _createClass(BaseClass, [{
        key: "Base_func",
        value: function Base_func() {
            console.log('I am Base_a method.');
        }
    }]);

    return BaseClass;
}();

var ExtendedClass = function (_BaseClass) {
    _inherits(ExtendedClass, _BaseClass);

    function ExtendedClass() {
        _classCallCheck(this, ExtendedClass);

        var _this = _possibleConstructorReturn(this, (ExtendedClass.__proto__ || Object.getPrototypeOf(ExtendedClass)).call(this));

        _this.name = "ExtendedClass";
        return _this;
    }

    _createClass(ExtendedClass, [{
        key: "Extended_func",
        value: function Extended_func() {
            var msg = "I'm a method of " + this.name + ".";
            console.log(msg);
        }
    }]);

    return ExtendedClass;
}(BaseClass);

var e = new ExtendedClass();
e.Extended_func();

```

一気に長くなりました。わたしが書いたクラス構文はどこかへ行ってしまい、`BaseClass`、`ExtendedClass`には`function`が代入されています。ES5の書き方になっています。あまりに長すぎるので、ファイルへ出力してから実行してみます。`--out-file`オプションまたは`-o`オプションをつけることで任意の場所にトランスパイル後のファイルを生成できます。

```sh:shell
$ ./node_modules/.bin/babel class.js --out-file compiled-class.js
$ node compiled-class.js
I'm a method of ExtendedClass.
```

コンパイル後のファイルも実行できました。実は`$ node class.js`のようにトランスパイル前のファイルも実行できるのですが、これはnodeで実行しているからです。ブラウザなど最新のJavascriptが実行できない環境でもトランスパイル後のファイルは動きます。これでBabelの最も基本的な動きがわかりました。ES6以降で導入された構文もES5以前からある構文もまとめて変換をし、最新機能の実装が進んでいないブラウザなどでも実行できるようにしてくれます。

## 複数のファイルのトランスパイル

さらに、別のパターンでトランスパイルを試してみます。`src`ディレクトリを作って上の例を3つのファイルに分け、ES6の`import`、`export`構文を使ってみます。

```sh:shell
$ mkdir src
$ touch ./src/index.js ./src/BaseClass.js ./src/ExtendedClass.js
```

```js:BaseClass.js
export default class BaseClass {
    constructor() {
        this.name = "BaseClass"
    }

    Base_func() {
        console.log('I am Base_a method.')
    }
}
```

```js:ExtendedClass.js
import BaseClass from './BaseClass'

export default class ExtendedClass extends BaseClass {
    constructor() {
        super()
        this.name = "ExtendedClass"
    }

    Extended_func() {
        const msg = `I'm a method of ${this.name}.`
        console.log(msg)
    }
}
```

```js:index.js
import ExtendedClass from './ExtendedClass'

const e = new ExtendedClass
e.Extended_func()
```

この状態で`$ node ./src/index.js`を実行しようとしてもエラーが発生します。nodeであれば新しい構文でも実行できるということではなく、できないものもあります。3つのファイルをトランスパイルしなければ実行できないようです。このように複数のファイルをトランスパイルしたい場合はディレクトリをトランスパイル対象として指定します。`dist`ディレクトリを作ってその中に出力してみましょう。

```sh:shell
$ mkdir dist
$ ./node_modules/.bin/babel ./src --out-dir ./dist
src/BaseClass.js -> dist/BaseClass.js
src/ExtendedClass.js -> dist/ExtendedClass.js
src/index.js -> dist/index.js
```

`dist`ディレクトリの中にそれぞれのファイルが同じ名前で出力されました。この中の`index.js`を実行してみます。

```sh:shell
$ node ./dist/index.js
I'm a method of ExtendedClass.
```

実行できました。各機能をモジュールに分割し、importしながらの開発が可能になりました。

## ここまでのまとめ

- `babel-cli`だけではES5的な記法にまでは変換してくれない
- `babel-preset-env`が同時に必要
- `preset`を導入したときは`.babelrc`ファイルでBabelの設定が必要
- トランスパイル対象が1ファイルか複数ファイルかによってコマンドが異なる

とりあえずES6でJavascriptの勉強がしたいということであれば、上記で十分かと思います。わたしは現在もJavascriptそのものの勉強をするときはこのような環境で行っています。次回はwebpackのLoaderの部分と合わせてまとめたいと思います。