先日webpack.config.jsがわからないと嘆いたところ多くの方にいいねをいただきました。Loaderに関する部分で続きを書いているのですが、Babelもわからなくて困っていた時期があったのでそのときのことをまとめたいと思います。
babel-cli
でBabelと仲良くなる
初めにBabelをコンソールから使えるようにするbabel-cli
を使ってみたいと思います。mybabel
ディレクトリを作り、その中でやっていきます。nodeはv9.2.0を使っています。
$ mkdir mybabel
$ cd mybabelc
$ npm init -y
package.json
ファイルは今のところ下のようになっています。
$ 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
の項目が追加されます。
"devDependencies": {
"babel-cli": "^6.26.0"
}
babel-cli
を使ってみる
babel-cli
でJavascriptをトランスパイルしてみます。次のようなscript.js
ファイルを用意します。
const arr = [1, 2, 3]
arr.map(n => n ** 2)
ES6から導入されたアロー関数が使われています。きっとこの部分が変換されるはずです。トランスパイルして結果をコンソール上に表示してみます。
$ ./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
があるディレクトリに作成します。
{
"presets": ["env"]
}
Babelの設定は基本的に.babelrc
ファイルに記述することが推奨されています。.babelrc
ファイルに設定を記述する以外にもpackage.json
に"babel"
という項目を作る、トランスパイル時のオプションとしてコンソールで指定するなどが可能です。今回は.babelrc
を使います。オプションを指定したので、もう一度、script.js
をトランスパイルしてみましょう。
$ ./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
ファイルを作ります。
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()
トランスパイルを実行します。
$ ./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
オプションをつけることで任意の場所にトランスパイル後のファイルを生成できます。
$ ./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
構文を使ってみます。
$ mkdir src
$ touch ./src/index.js ./src/BaseClass.js ./src/ExtendedClass.js
export default class BaseClass {
constructor() {
this.name = "BaseClass"
}
Base_func() {
console.log('I am Base_a method.')
}
}
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)
}
}
import ExtendedClass from './ExtendedClass'
const e = new ExtendedClass
e.Extended_func()
この状態で$ node ./src/index.js
を実行しようとしてもエラーが発生します。nodeであれば新しい構文でも実行できるということではなく、できないものもあります。3つのファイルをトランスパイルしなければ実行できないようです。このように複数のファイルをトランスパイルしたい場合はディレクトリをトランスパイル対象として指定します。dist
ディレクトリを作ってその中に出力してみましょう。
$ 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
を実行してみます。
$ 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の部分と合わせてまとめたいと思います。