yutaponのブログ

javascript界隈の興味あるネタを備忘録的に残しておく場所

フロントエンドでもES6構文使ってみる【webpack+babel-loader(旧6to5-loader)】

【--- 追記(2015/02/22)---】

2/15に6to5がBabelと名称変更したので、記事の内容もBabelを使うよう変更しました。

Not Born to Die · Babel

【--- 追記ここまで ---】

2/7にnode.js v0.12.0がリリースされました。
Node v0.12.0 (Stable)

安定版のメジャーアップデートでES6構文がいよいよ一般的になるのではないでしょうか。

そして最近良く聞くBabel(旧6to5)
f:id:sskyu:20150222173946p:plain

ES6+のコードをES5に変換してくれるということで、ES6構文を動かせる環境が浸透していないフロントエンドで役立つライブラリです。

Babelはgrunt, gulp, browserifyなど多数のビルドツールで扱えるプラグインが提供されているので、jsのビルド工程があるプロジェクトであれば簡単に導入できると思います。

今回は前の記事で作ったgulp+webpackプロジェクトをES6構文で動くようにしてみます。

はじめに

ES6で新しく使えるようになった構文はたくさんあるので、この記事では

  • クラス
  • モジュール
  • Promise

だけ軽く触れます。(メインはES6構文を使えるようになることですので)

ちなみにES6で追加された機能はこちら。
lukehoban/es6features · GitHub

最終的なコードはこちらに上げておきました。
sskyu/gulp-webpack-skeleton at blog-20150207v2 · GitHub

babel-loader

前の記事ではgulpのタスクを細かく分けて、jsのビルドにwebpackを使うところまでやりました。

webpack向けにbabel-loaderが提供されているので、これをサクッと組み込んでES6構文を使えるようにします。

babel-loaderをインストールします。

$ npm install -D babel-loader


次にwebpackの設定でbabel-loaderを読み込むようにします。

// @file gulp/config.js

  webpack: {
    entry: src + '/js/app.js',
    output: {
      filename: 'bundle.js'
    },
    resolve: {
      extensions: ['', '.js']
    },
    module: {  // ここを追記
      loaders: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'babel-loader'
        }
      ]
    }
  },

拡張子が.jsで終わるファイル対象にしつつ、node_modules/以下は変換の対象外にします。

これでES6構文でJSを書けるようになりました。

クラス

ES6でよくあるclass構文が使えるようになりました。

今まではこんな感じでクラスを作っていたのが、

function Hello() { // クラス名定義
  'use strict';    // このスコープがコンストラクタ

  this.message = 'Hello!';
}
Hello.prototype.say = function () { // メソッド生やす
  console.log(this.message);
}

var hello = new Hello();
hello.say(); // => Hello!


こんな感じに書けます。

class Hello {
  this.message = 'Hello!';

  say() {
    console.log(this.message);
  }
}

var hello = new Hello();
hello.say(); // => Hello!

まとまりができて、可読性があがったように見えます。

クラスの継承もすごく簡単に書けるようになり、積極的に使っていきたいところ。

クラスについてはこちらの記事が参考になります。
Class構文が実装された - JS.next

モジュール

ES5でCommonJSスタイルだとこんな感じで読み込んでいました。

// @file Hello.js
module.exports = function Hello() {
  console.log('Hello!');
}
// @file app.js
'use strict';

var Hello = require('./Hello');
new Hello(); // => Hello!


ES6のモジュール構文だとこうなります。

// @file Hello.js
export default class Hello {
  console.log('Hello!')
}
// @file app.js
import Hello from './Hello'

new Hello() // => Hello!


使ってみて思ったのは、defaultキーワードの使い所が難しいところ。
たとえばHello.jsでもう一つ値をexportするように変更すると、参照する側はこうなります。

// @file Hello.js
export default class Hello {
  console.log('Hello!')
}
export var message = 'hogehoge'
// @file app.js
import Hello, { message } from './Hello'
console.log(message) // => hogehoge

{}の中にexportされた変数名を書けば参照を得られます。

逆にHello.jsの export default class Hello 部分を
export class Hello とすると、app.jsのimport句はこのようになります。

import { Hello, message } from './Hello'

default使わないほうが統一性がある気もするけど、defaultを使うとこれがメインの処理だってことを明示的に示すことができそう。

他にもasキーワードで参照名を短縮したりできますが、その辺は参考になる記事があるのでそちらで。

JavaScript Modules

Promise

フロー制御のちゃんとした仕組みがES6で入りました。
Promise自体は数年前からいろんなライブラリで実装されていたので、既にPromise系ライブラリを使っていたら違和感なく使えると思います。

Promiseについてはこちらを一読しておくと理解が深まると思います。

JavaScript Promiseの本

Promiseを使ったコードをHello.jsに追加しました。

// @file components/Hello.js
export default class Hello {
  constructor(message = '') {
    this.message = message
  }

  say() {
    console.log(this.message)
  }

  later() { // 追加
    return new Promise((resolve, reject) => {
      setTimeout(resolve, 1000)
    })
  }
}


呼び出し側でlater()メソッドを実行します。

// @file app.js
import Hello from './components/Hello'

var hello = new Hello('Hello')

hello.say()
hello.later().then(() => hello.say())

このコードを実行するとページ表示直後、コンソールにHelloと表示され、1000ms後にもう一度Helloと表示されます。

このコードを実行したのはChromeブラウザのver40になりますが、実行するブラウザによってはPromiseが実装されていない可能性があります。

この場合、bluebirdのようなPromiseライブラリを使うか、Babelのpolyfillを使うとよいです。

babel-runtime

Babelのpolyfillを使う場合、まずはじめにbabel-runtimeをインストールする必要があります。

$ npm install -S babel-runtime

polyfillを使う場合は成果物に影響するのでdependenciesに追記するようインストールします。

次にwebpackのloaderの設定を変更します。

    module: {
      loaders: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'babel-loader?experimental&optional=selfContained' // パラメータ渡す
        }
      ]
    }

これでビルドすると、PromiseなどES6の実装が間に合っていない環境においてもPolyfillが働いて動作するようになります。

ちなみにruntimeを含めてビルドすると無圧縮で87KB、圧縮して29KBになりました。
runtimeを含めない場合は無圧縮で3KB、圧縮して0.9KBでした。

PromiseだけPolyfillするならライブラリ1つ入れたほうが軽そうです。
ES6のSymbolとか他にも使うのであればバランスを見てruntimeを含めるか検討するといいと思います。

おわりに

Babelのおかげでブラウザの実装を待つことなくES6構文が使えるようになりました。

gulp-webpack-skeletonリポジトリは何か作り始めるときのテンプレートを整備している段階なので、
次はwebserverとテストを追加しようと思います。