フロントエンドでもES6構文使ってみる【webpack+babel-loader(旧6to5-loader)】
【--- 追記(2015/02/22)---】
2/15に6to5がBabelと名称変更したので、記事の内容もBabelを使うよう変更しました。
【--- 追記ここまで ---】
2/7にnode.js v0.12.0がリリースされました。
Node v0.12.0 (Stable)
安定版のメジャーアップデートでES6構文がいよいよ一般的になるのではないでしょうか。
そして最近良く聞くBabel(旧6to5)。
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
キーワードで参照名を短縮したりできますが、その辺は参考になる記事があるのでそちらで。
Promise
フロー制御のちゃんとした仕組みがES6で入りました。
Promise自体は数年前からいろんなライブラリで実装されていたので、既にPromise系ライブラリを使っていたら違和感なく使えると思います。
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とテストを追加しようと思います。