読者です 読者をやめる 読者になる 読者になる

io.js の v2.0 が出ました。

io.js

https://hexi.pics/resize/w/462/h/462/e03baa40377a8e562cb376199c6e3721.png


さてさて、久しぶりのio.js エントリですが、この度 io.js の v2.0 が出ました。
変更点をかいつまんで説明しましょう。書いてたら長くなってしまったので、サマリだけでいい人は最後のまとめを読むだけでいいと思います。

io.js は変化がものすごく激しく、v1.0 から v2.0 が入るまでに色んな機能が入っているんですが、知らない人も多いかと思います。

今回は v2.0 の単なる変更点だけじゃなくて、 v1.0 から今までで入った機能をサマリつつ伝えていこうかと思います。

Stream Simpler Construction (v1.2.0〜)

Stream の作成が簡単になりました。今までStreamを作るためには、目的のStreamを継承して、TransformStreamであれば _transform のようなメソッドを拡張して実現する必要がありました。

これをより簡単にしたものが through2 に代表されるヘルパライブラリでしたが、簡単にいえばこの through2 がなくても io.js では Stream を作るのが簡単にできるようになりました。

StreamのTransformに対して、transformやflushを取ることができ、これを使うとわざわざ継承をしなくてもTransform Streamを作ることが可能です。下記のようになりました。

before
var Transform = require('stream').Transform;
var util = require('util');

util.inherits(MyTransform, Transform);

function MyTransform(opts){
    Transform.call(this, opts);
}

MyTransform.prototype._transform = function(chunk, encoding, callback){
  // ここで 変換して
  ...
  // pushする
  this.push(chunk);
};

MyTransform.prototype._flush = function(done){
  // 最後に何かしたければここで flush する
};
after
var transform = new stream.Transform({
  transform: function(chunk, encoding, next) {
    // ここで 変換して
    ...
    // pushする
    this.push(chunk);
  },
  flush: function(done) {
    // 最後に何かしたければここで flush する
  }
});

ここでは Transform Stream で例を示しましたが、 Readable や Writable も簡単に作れるようになりました。

詳しくはコチラを見てください。

https://iojs.org/api/stream.html#stream_simplified_constructor_api

LTTNG サポート (v1.2.0〜)

HTTP のリクエストやサーバのレスポンス、GCの実行といった時にカーネルのレベルで何が起きているかを追うための機能が追加されています。今までの Node.js ではこれを行うのに Dtrace を使っていましたが、LinuxのDtrace実装は若く、全てが実装されているわけではありませんでした。結果として、 Joyentが提供している SmartOS等のSolarisをベースにしたUnix環境でしかNode.jsでDtraceを使うのはサポートされていませんでした。

これからは Linux カーネルでも標準でトレース可能な LTTNG も使えるようになりました。
詳しくはここを見てもらえるといいかと。

Node LTTNG

http://nearform.github.io/tracing-node-lttng-nodejsdublin/pictures/demo.gif

Promise の unhandledRejection / rejectionHandled イベント (v1.4.1〜)

Promise の catch を忘れた時に、 キャッチされていない例外として、 unhandledRejection と rejected 済みでもう呼ばれることがない例外を検出するための仕組みとして、 新しくrejectionHandledイベントが追加されました。

こちら、 azuさんの資料が詳しいので抜粋させてもらいます。

var bluebird = require("bluebird");
// unhandledRejection catchされていない例外
process.on("unhandledRejection", function (reason, promise) {
    console.log("unhandledRejection");
});

var resolved = bluebird.resolve();
resolved.then(function () {
    throw new Error("Yay!");
});
var Promise = require("bluebird");
process.on("rejectionHandled", function (promise) {
    console.log("rejectionHandled");
});
var rejected = Promise.reject(new Error("Error Promise"));
setTimeout(function () {
    rejected.catch(function () {
        // rejected済みのpromiseに`catch`する
    });
},100);

Promise Error Handling

Buffer に indexOf メソッドが追加されました。 (v1.5.0 〜)

Buffer に対して indexOf メソッドを使えるようになりました。これまでは 一旦 String にしたりしてから、中身の文字列を検索する必要がありましたが、その必要がなくなりました。

before
var fs = require('fs');

// ./foo.txt => yosuke furukawa
fs.readFile(__dirname + '/foo.txt', function(err, buf){
  var str = buf.toString();
  console.log(str.indexOf('furukawa')); // 7
});
after
var fs = require('fs');

// ./foo.txt => yosuke furukawa
fs.readFile(__dirname + '/foo.txt', function(err, buf){
  console.log(buf.indexOf('furukawa')); // 7
});


詳しくはコチラ。

https://iojs.org/api/buffer.html#buffer_buf_indexof_value_byteoffset

node のコマンドに --require オプションで preload モジュールを渡せるようになりました。 (v1.6.0〜)

$ node --require ./a.js b.js

事前にpreloadしておきたい場合に使えます。

process.nextTick に複数の引数を渡せるようになりました。 (v1.8.0〜)

var obj = {};

process.nextTick(function(a, b) {
  assert.equal(a, 42);
  assert.equal(b, obj);
}, 42, obj);

この変更によって、process.nextTick の callback に任意の引数を渡すことができるようになりました。
APIが setTimeoutやsetIntervalと似るようになりました。

REPL に history save 機能が追加 (v2.0〜)

REPL の magic mode と呼ばれる機能が追加されています。これは、環境引数NODE_REPL_HISTORY_FILEを指定することでREPLを終了させても、次回の起動時に自分が実行したreplの内容を記憶してくれる機能です。いわゆる、 REPL の history save 機能ですね。

$ NODE_REPL_HISTORY_FILE=~/.node_history iojs
> var fs = require('fs');
> fs.readFile;
# Ctrl-D


$ NODE_REPL_HISTORY_FILE=~/.node_history iojs
> # push up button
> fs.readFile;

これは NODE_REPL_HISTORY_FILEを指定しないといけなくて、必ず有効にはなりません。
僕は .zshrc に以下のように書くことで対応しています。

# iojs
alias iojs-repl="NODE_REPL_HISTORY_SIZE=Infinity NODE_REPL_HISTORY_FILE=~/.node_history iojs"

こうするとiojs-replで実行するときにはhistoryが有効になります。

詳しくはコチラ
https://iojs.org/api/repl.html#repl_repl

ES6でデフォルトで使える構文が追加 (v2.0〜)

僕が少し前に作ったiojsの新機能紹介リポジトリにも書きましたが、日本語で説明を加えていきます。

class

クラスがデフォルトで使えるようになりました。

// strict mode needed
'use strict';

class Animal {
  constructor(name) {
    this.name = name
  }

  say() {
    // unimplemented
  }
}

class Cat extends Animal {
  say() {
    console.log(`${this.name} < meow`);
  }
}

var cat = new Cat('Mike');
cat.say(); // Mike < meow

これはかなり重要で、 io.js v2.0 を使っている分には util.inherits のような継承のためのヘルパメソッドは不要になるし、ビルドインクラスを継承するのにも使えます。

enhanced object literals (v2.0〜)

Object のリテラルに拡張が加わりました。 以下のように key と value が一緒の時は省略して記述することができるようになります。

'use strict';
// class
class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
 getInfo() {
    let name = this.name;
    let age = this.age;
    let nextAge = this.age + 1;
    // enhanced object literal
    return {
      name,
      age,
      nextAge
    };
  }
}

var bob = new Person('bob', 15);

console.log(bob.getInfo()); // { name: 'bob', age: 15, nextAge: 16 }

Rest パラメータが --harmony-rest-parameters オプション付きで有効になりました。

harmony option 付きで Rest パラメータが追加されました。

// Rest parameters
function max(...args) {
  // rest parameter is not Array-like object, that is just array.
  console.log(Array.isArray(args))  // true
  console.log(args.length)          // 6

  var max = args.reduce(function(max, n) { 
    return n > max ? n : max;
  });
  return max;
}

var maxNum = max(5, 15, 10, 1, 4, 5);
console.log(maxNum); // 15

実行するには下記のようにします。

$ iojs --harmony-rest-parameters es6/rest_params/rest.js

Computed property names が --harmony-computed-property-names オプション付きで有効になりました。

harmony オプション付きで、Computed property names が使えるようになりました。

var i = 0;
var a = {
  ["foo" + ++i]: i,
  ["foo" + ++i]: i,
  ["foo" + ++i]: i
};

console.log(a.foo1); // 1
console.log(a.foo2); // 2
console.log(a.foo3); // 3

var param = 'size';
var config = {
  [param]: 12,
  ["mobile" + param.charAt(0).toUpperCase() + param.slice(1)]: 4
};

console.log(config);

以下のように実行します。

$ iojs --harmony-computed-property-names es6/computed_property/computedProps.js

Strong mode がサポートされました。 (v2.0〜)

今、 Google の v8 チームは色んな試みをしているのですが、 StrongScriptと呼ばれる試みをしています。
これは、JavaScriptの自由度の高い構文 (var, arguments, ==, delete, for-in 等)をなるべく deprecated にして、ES6 を含めた最新の構文(let, ...args, ===, Map, for-of)に書き換えるための試みです。

この StrongScript は コードの先頭に 'use strong' ディレクティブを付けることで Strong Mode になり、実行されます。
ちょうど 'use strict' で Strict Mode にするのと同様です。

今回のio.js v2.0 からこの Strong Mode が --strong_mode オプション付きで使えるようになりました。

※ ちなみにまだまだ実験的な試みなので、本番環境で使うのは推奨できません。

var => let/const
'use strong';

var a = 'hoge';
$ iojs --strong_mode strong_mode/vars.js

/Users/yosuke/iojs_v2_features/strong_mode/vars.js:3
var a = 'hoge';
^^^
SyntaxError: Please don't use 'var' in strong mode, use 'let' or 'const' instead
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:411:25)
    at Object.Module._extensions..js (module.js:446:10)
    at Module.load (module.js:353:32)
    at Function.Module._load (module.js:308:12)
    at Function.Module.runMain (module.js:469:10)
    at startup (node.js:124:18)
    at node.js:882:3
arguments => ...args
'use strong';

function some() {
  let args = Array.prototype.slice.call(arguments);
}

some();
$ iojs --strong_mode strong_mode/arguments.js
/Users/yosuke/iojs_v2_features/strong_mode/arguments.js:4
  let args = Array.prototype.slice.call(arguments);
                                        ^^^^^^^^^
SyntaxError: Please don't use 'arguments' in strong mode, use '...args' instead
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:411:25)
    at Object.Module._extensions..js (module.js:446:10)
    at Module.load (module.js:353:32)
    at Function.Module._load (module.js:308:12)
    at Function.Module.runMain (module.js:469:10)
    at startup (node.js:124:18)
    at node.js:882:3
eqeq => eqeqeq
'use strong';

if ('a' == 'a') {
}
$ iojs --strong_mode strong_mode/eqeq.js

/Users/yosuke/iojs_v2_features/strong_mode/eqeq.js:3
if ('a' == 'a') {
        ^^
SyntaxError: Please don't use '==' or '!=' in strong mode, use '===' or '!==' instead
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:411:25)
    at Object.Module._extensions..js (module.js:446:10)
    at Module.load (module.js:353:32)
    at Function.Module._load (module.js:308:12)
    at Function.Module.runMain (module.js:469:10)
    at startup (node.js:124:18)
    at node.js:882:3
propertyの削除に対して、 delete を使わず、 Map/Setを使う
'use strong';
let obj = { key: 'value'};
delete obj.key;
console.log(obj);
$ iojs --strong_mode strong_mode/delete.js
/Users/yosuke/iojs_v2_features/strong_mode/delete.js:5
delete obj.key;
           ^^^
SyntaxError: Please don't use 'delete' in strong mode, use maps or sets instead
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:411:25)
    at Object.Module._extensions..js (module.js:446:10)
    at Module.load (module.js:353:32)
    at Function.Module._load (module.js:308:12)
    at Function.Module.runMain (module.js:469:10)
    at startup (node.js:124:18)
    at node.js:882:3
for-in => for-of
'use strong';

for (let k in [1, 2, 3]) {
  console.log(k);
}
$ iojs --strong_mode strong_mode/for.js
/Users/yosuke/iojs_v2_features/strong_mode/for.js:3
for (let k in [1, 2, 3]) {
           ^^
SyntaxError: Please don't use 'for'-'in' loops in strong mode, use 'for'-'of' instead
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:411:25)
    at Object.Module._extensions..js (module.js:446:10)
    at Module.load (module.js:353:32)
    at Function.Module._load (module.js:308:12)
    at Function.Module.runMain (module.js:469:10)
    at startup (node.js:124:18)
    at node.js:882:3

幅広い環境のサポート (v1.4.0, v1.6.0, v1.8.0 )

今回、かなりビルド環境に気合が入っていて、幅広い環境でバイナリが提供されています。 Node.js の時は、 darwin(osx), linux, sunos(solaris), windows のバイナリが提供されるだけでしたが(それでもかなり素晴らしいけど)、 io.js からは ARM v6, v7も含めたかなり多くの環境でバイナリが提供されています。

binaryは提供されていませんが、 Android でも動くためのビルドオプションがあったり、SmartOS、FreeBSDでも動くためのオプションが提供されています。

また色んなOSの仮想環境を構築し、そこの上でテストを実行しています。どこかの環境でエラーがあったらすぐに分かるようになっています。

https://jenkins-iojs.nodesource.com/job/iojs+any-pr+multi/

依存ライブラリ関連 (v2.0.0〜)

v8が 4.2 になった

v8が新しくなったので、JavaScriptの最適化やES6の機能が増えました。これに関しては先ほど紹介しました。
また、v8がアップグレードされたことで、V8のAPI が更新され、 Native Module も変更が必要になりました。
つまり、古いNative moduleのままでは動かなくなってしまいました。

現時点で v2.0 では動かないモジュール一覧に関しては以下のissueをご一読ください。

github.com

nan更新を行うだけでいけるモジュールも多いので、忘れているモジュールに関しては、 今のところ nan を更新して pull request で教えてあげればいいかと思います。
ただ毎回 v8 が更新されるだけで動かなくなるモジュールがたくさんあると困るので、 nan を core に入れる提案もされています。

OpenSSL v1.0.2a になった

OpenSSL が v1.0.2a になり、 crypto の性能が改善されました。
この辺りの記事が詳しいです。*1

github.com

http-parser v2.5.0 になった

http-parserがやっと最新になりました。一旦 http-parser は v2.4になった後、バグが見つかりrevertされましたが、そのバグが修正され、高速化されたhttp-parserが使えるようになりました。

libuv が v1.4.2 になった

libuv が新しくなりました。 windows/linuxでのバグ修正や ARM でも動くようにするための修正などが含まれています。

npm が v2.9.0 になった

npm も新しくなりました。翻訳があるのでそこを見ておくと良いかもしれません。

medium.com


まとめ (v1.0 〜 v2.0の違い)

  • Streamを簡単に作れるようになった
  • LTTNG のサポート
  • Promise の unhandledRejection / rejectionHandled イベント
  • Buffer の indexOf メソッドを追加
  • node --require オプション追加
  • repl に history をsaveするモードが追加
  • v8 が新しくなり、 class, object リテラルの拡張 etcとStrong Mode が追加
  • ARMを初めとする バイナリの提供と幅広い環境のサポート
  • 依存ライブラリの刷新による性能向上、バグ修正など

結び

さて、今回ざざっと v1.0 から今までの変更をお伝えしました。これらの変更は日本語の記事であれば、

blog.iojs.jp

にまとまっています*2

変化の激しい io.js を追うのは大変ですが、この記事を毎週少しでも読んでおいてもらえると助けになると思います。
(毎週週末の時間を削って、翻訳している翻訳チームを助けてもらえるとありがたいです。)

また、コアチームは今は v3.0 に向けて動き出しています。僕もちょっとずつ改善を入れています。この変化を楽しみながら貢献できるといいなと思います。

*1:大津さんが書いてくれました。 大津さん ++

*2:いつも翻訳してくれる @watilde さん、 @jgs さん、 @kosamari さんに感謝です。