NHN Cloud NHN Cloud Meetup!

今すぐES6を始めよう

はじめに

毎日溢れ出るES6の記事を見ていると、単に遠い未来の話だという気がします。ES6でサービスを開発するには、バージョン別のブラウザ使用率が足を引っ張っているからです。B2Cはまだ良いですが、B2Bはいろいろな制約からWindows XPを使用しているユーザーもいます。さらに、最新ブラウザを使いつつ「互換表示モード」(IE8シミュレーションモード)を設定して使用しているケースもあります。

// ES5
var tmp = getASTNode(),
op = tmp.op,
lhs = tmp.lhs,
rhs = tmp.rhs;

// ES6
var { op, lhs, rhs } = getASTNode();

// ----

// ES5
var html = '' + item.name + '' +
'' + item.description + '';

// ES6
const result = <span>${item.name}</span> <span class="${item.cssClass}">${item.description}</span>;

// ----

// ES5
'hello'.indexOf('ell') > -1; // true

// ES6
'hello'.includes('ell'); // true

// ----

// ES5の配列重複除去関数
function dedup(arr) {
for(i) ...
for(j) ...
}

// ES6の配列重複除去関数
const dedup = arr => [...new Set(arr)];

国内外のブラウザ使用率のデータはさておき、ES6対応ブラウザのシェアは上がっています。ベンダーはサブバージョンのサポートにメスを入れました。XPを使おうとすると、オペレーティングシステムからサブバージョンサポート終了に関するメッセージが立ち上がり、IE8を使うと最新ブラウザの案内ページが頻繁に表示されます。ブラウザはevergreenポリシーで続々、配布されています。

ES6を使用するには、ユーザーはこのように処理するとよいでしょう。

  1. 使用しているIE、またはオペレーティングシステムを更新する
  2. 他のブラウザをインストールする

ここでは、ES6の使用方法について取り扱います。まず、ES5のみに対応するモダンブラウザでES6を使用する方法を紹介して、さらにIE8でも使用できる設定について調べてみましょう。

Transpiler

transpilerはcompilerのようにコードを何かに変換します。
compilerコードをバイトコードに変換しますが、transpilerはコードを同じレベルの他の言語に変換します。

JS transpilerはCoffeeScript、TypeScript、Babel、Traceurがあります。このうち、CoffeeScript、TypeScriptは独自文法をJSに変換し、Babel、TraceurはJSコードをJSコードに変換します。なぜJSをJSに変換するtranspilerがあるのでしょうか?

現在(2016/5/20)、ES6スペックを100%サポートしているブラウザはありません。Chromeが93%程度をサポートし、他のブラウザはそれよりも低い状態です。ES6コードを元のまま実行させることができるブラウザはまだありません。

// ES6
function foo(name = 'john') {}

// ES5
function foo(name) {
name = typeof name === 'undefined' ? 'john' : name;
}

上のコードは、ES6スペックであるdefault function parameterを使用しています。この文法は、防御コードを簡潔にする主な文法の1つです。
比較的最新のブラウザであるEdge13(2015/12/05発売)のバージョンは、この構文をサポートしていません。(実行すると、文法自体がないためSyntax Errorを発生します。)またIE11では、関数のネストの複雑さを解消できるArrow functionスペックに対応していません。

// ES6
$.ajax('/users', {
success: res => { console.log(res); },
error: err => { console.log(err.message); }
);

// ES5
$.ajax('/users', {
success: (function(res) {
console.log(res);
}).bind(this),
error: (function(err) {
console.log(err.message);
}).bind(this)
});

では、ES6バージョンのコードを非対応のブラウザで動作するにはどうすればよいでしょうか?結論として、1つ1つ手作業で変更する必要があります。
ここで、BabelまたはTraceur transpilerを使用しましょう。ES6コードをすべてES5コードに変換し、現在までにリリースされたすべてのモダンブラウザで問題なく動作します。

一般的に、JSファイルは、パフォーマンスやセキュリティの問題上、圧縮後に配布します。圧縮前のtranspilerをES5基盤コードに変換する作業をするように設定しましょう。聞いただけでは難しそうですが、変換と圧縮を一度に実行してくれるツールがあります。設定ファイルを作成してコマンドを実行するだけです。

Babelとは何か

BabelはES6コードをES5に変換するtranspilerです。前述のSyntax Errorが発生するコード自体を自動でES5対応コードに変換します。では、classキーワードはどのように変換されるでしょうか?

ご存知のように、JSのOOPはシミュレーション方式用のヘルパー関数、すなわちクラスをまねるユーティリティ関数が必要です。Babelはclassキーワードにヒットすると自動でBabelが独自実装したクラスのシミュレーション関数を含めます。

// ORIGINAL
class User { test() {} }
class Admin extends User {}

// TRANSPILED
function _possibleConstructorReturn(self, call) { /*省略*/ }
function _inherits(subClass, superClass) { /*省略*/ }
function _classCallCheck(instance, Constructor) { /*省略*/ }

var User = function () {
function User() {
_classCallCheck(this, User);
}
User.prototype.test = function test() {
};
return User;
}();

var Admin = function (_User) {
_inherits(Admin, _User);
function Admin() {
_classCallCheck(this, Admin);
return _possibleConstructorReturn(this, _User.apply(this, arguments));
}
return Admin;
}(User);

_possibleConstructorReturn、_inherit、_classCallCheckは、Babelが持つクラスシミュレーションユーティリティ関数です。transpilerが、classキーワードとextendsキーワードに出会ったため、自動でソースに追加されました。また下部から、User、Admin関数が一般的なprototypal inheritanceパターンに変換されていることがわかります。

変換後のコードが少しすっきりしませんが、BabelはGoogleのTraceurに比べてはるかに見事に変換してくれます。

_callClassCheckは、生成されたクラスがnewと一緒に使用されていないとエラーを出す関数です。つまり、クラスをクラスとして使用するのか、検査するのかを判断しますが、ES6スペックに合わせて使用または検査コードを含み、これらをオプションで調整できます。リアル配布時はパフォーマンスを確保するために緩和します。

変換されたコードを使用すると、ほとんどのモダンブラウザでES6を問題なく使用できるようになります。しかし、IE8で動作するようにするには、もう少し設定が必要です。

Babelサブサポート設定 – Polyfill

Babelを用いてES6コードをモダンブラウザでも動作できるようにしました。ではIE8はどうでしょうか?次のコードを変換したと仮定しましょう。

// ORIGINAL
class UserList {
constructor() {
this.users = [];
}
contains(name) {
return this.users.indexOf(name);
}
}

// TRANSPILED
function _classCallCheck(instance, Constructor) {/ 省略 /}
var UserList = function () {
function UserList() {
_classCallCheck(this, UserList);
this.users = [];
}
UserList.prototype.contains = function contains(name) {
return this.users.indexOf(name);
};
return UserList;
}();

UserListのusersプロパティは配列で、contains()メソッドはこの配列のindexOf()に含まれているかどうかを検査しています。このコードはIE8で動作するでしょうか?答えは「NO」です。

[‘a’, ‘b’, ‘c’].indexOf(‘b’);は、IE8でエラーが発生します。IE8以下は、配列の要素を検索できるnative関数がありません。このため、通常のような動作をする関数を作成し、プロジェクトのどこかに置いて使用します。

function indexOf(arr, identity) {}

var arr = ['a', 'b', 'c'];
indexOf(arr, 'c'); // 2

このようなパターンをFallbackと言い、ない機能を真似て使用します。しかし、このコードを使うと、indexOfをnativeでサポートしているブラウザでもFallbackを送信するため、パフォーマンス低下の原因となります。そのため他の方法を利用します。

if (typeof Array.prototype.indexOf === 'undefined') {
Array.prototype.indexOf = function() {};
}

 

ArrayというnativeオブジェクトにindexOfメソッドが存在しない場合、先ほど作成したFallbackに置換します。このパターンをPolyfillといいます。これにより、nativeにある場合はnative実装を使用し、ない場合はPolyfillを使用することになります。

BabelはES6コードを「ES5対応ブラウザ」に届くようにする役割をするだけです。ここにIE8は含まれないため、IE8から外れたES5の機能を満たすことができるPolyfillが必要です。Polyfillのみ追加してES5の機能をIE8で利用することもできます。

Babelは正式にはcore-jsファイルをPolyfill「オプション」として提供しています。そのためBabel-Polyfillパッケージをインストールして、コードに含めることで要件を満たすことができます。しかし、IE8でコードを実行するとエラーが発生します。まだ1つ設定が残っています。

Babelサブサポート設定 – Plugins

// ORIGINAL
class UserList {
constructor() {
this.users = new Set();
}
delete(name) {
return this.users.delete(name);
}
}

// TRANSPILED
function _classCallCheck(instance, Constructor) {/ 省略 /}
var UserList = function () {
function UserList() {
_classCallCheck(this, UserList);
this.users = new Set();
}
UserList.prototype.delete = function _delete(name) { // ERROR
return this.users.delete(name); // ERROR
};
return UserList;
}();

usersプロパティをES6のコレクションであるSetに変更しました。そしてdeleteメソッドを使ってuserを削除できるようして、これをBabelからtranspileしました。このコードをIE8で実行すると、Expected identifierエラーが発生します。deleteというキーワードを「アクセス者」として使ったからです。

もちろん、このコードは、ES5対応ブラウザでは有効です。そのため、transpileの中でアクセス者がキーワードのとき、obj[‘delete’](undefined “undefined”);の形で変わるべきです。Pluginを使って解決することができます。member-expression-literals、property-literalsプラグインは、transpileの中で、このようなmember literal表記をカンマで囲みます。よってobj[‘delete’](undefined “undefined”);に変換します。これでIE8でES6文法を使用できようになりました。

下位互換用のPluginだけでなく、ES6以降のスペック用のPluginもあります。async、awaitスペックはES7スペックですが、ES6のgeneratorを応用して変換するPluginもあります。スペック別にPluginが存在するので、必要に応じて設置して使用すればよいでしょう。

使い方

いよいよ実践です。npmを使ってインストールします。

$ npm i --save-dev babel-core babel-preset-es2015 babel-polyfill
babel-plugin-transform-es3-member-expression-literals
babel-plugin-transform-es3-property-literals

各モジュールを説明します。

  • babel-core:Babelの主要モジュール。transpilerが含まれている。
  • babel-preset-es2015:Babelは各Pluginをスペック単位で使用する形態である。es2015つまりES6文法もスペックに対応するPluginが1つ1つが集まったものである。Babelでpresetと呼ぶ。preset-es2015はES6スペックのPluginコレクションと考えるとよい。
  • babel-polyfill:前述したPolyfillである。core-jsとfacebookのregeneratorが含まれている。
  • babel-plugin-transform-es3-member-expression-literals:キーワードをアクセス者で使うときカンマで囲む。
  • babel-plugin-transform-es3-property-literals:キーワードでプロパティ名を使うとき、カンマで囲む。
    プロジェクトのルートに.babelrcを作成し、以下のように設定する。
{
"presets": ["es2015"],
"plugins": [
"transform-es3-property-literals",
"transform-es3-member-expression-literals"
]
}

その後Babelのコマンドと一緒に対象ファイルをglobパターンで作成すると変換された結果が出力されます。詳細は公式ドキュメントを参照してください。

結論と要約

このようにBabel transpilerを用いてES6コードをES5で動作させることができます。このとき、IE8はES5スペックの全体に対応していないため、足りない部分はPolyfillを使用します。さらに、以前のバージョンでは、キーワードをアクセス者として使用するとエラーになるのを防ぐため、Pluginでのキーワードは文字で囲みます。

最後に、別のヒントを紹介します。

キーワードへのアクセスはBabelが作り出すコードにもあるので、IE8に対応するにはPluginの使用が必須となります。
コマンドの実行を応用すれば、自動化ツールとの連動が可能でしょう。
ES6コードの効率性は非常に素晴らしく、簡潔ながらもコードの役割を忠実にコーディングに記述することができます。JSは比較的最近になって登場したプログラミングパラダイムを消化できるように変化しています。

参考 : Babeljs公式サイト:http://babeljs.io

NHN Cloud Meetup 編集部

NHN Cloudの技術ナレッジやお得なイベント情報を発信していきます
pagetop