io.js の v2.0 が出ました。
さてさて、久しぶりの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 も使えるようになりました。
詳しくはここを見てもらえるといいかと。
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);
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が有効になります。
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の仮想環境を構築し、そこの上でテストを実行しています。どこかの環境でエラーがあったらすぐに分かるようになっています。
依存ライブラリ関連 (v2.0.0〜)
v8が 4.2 になった
v8が新しくなったので、JavaScriptの最適化やES6の機能が増えました。これに関しては先ほど紹介しました。
また、v8がアップグレードされたことで、V8のAPI が更新され、 Native Module も変更が必要になりました。
つまり、古いNative moduleのままでは動かなくなってしまいました。
現時点で v2.0 では動かないモジュール一覧に関しては以下のissueをご一読ください。
nan の更新を行うだけでいけるモジュールも多いので、忘れているモジュールに関しては、 今のところ nan を更新して pull request で教えてあげればいいかと思います。
ただ毎回 v8 が更新されるだけで動かなくなるモジュールがたくさんあると困るので、 nan を core に入れる提案もされています。
http-parser v2.5.0 になった
http-parserがやっと最新になりました。一旦 http-parser は v2.4になった後、バグが見つかりrevertされましたが、そのバグが修正され、高速化されたhttp-parserが使えるようになりました。
まとめ (v1.0 〜 v2.0の違い)
結び
さて、今回ざざっと v1.0 から今までの変更をお伝えしました。これらの変更は日本語の記事であれば、
にまとまっています*2。
変化の激しい io.js を追うのは大変ですが、この記事を毎週少しでも読んでおいてもらえると助けになると思います。
(毎週週末の時間を削って、翻訳している翻訳チームを助けてもらえるとありがたいです。)
また、コアチームは今は v3.0 に向けて動き出しています。僕もちょっとずつ改善を入れています。この変化を楽しみながら貢献できるといいなと思います。