0

coffee-scriptからES6に移行する理由

coffeeからES6(babel)に少しずつ書き換える
の続き。

そもそも書き直す必要があるのか


正直シンタックスはcoffeeの方が好き。無駄にカッコつけなくていいし、インデントでpipeやpromiseをつないで書いていくのも見た目が良い。
coffeeは書きやすい。
まあすでに動いている物を書き直す必要は無いと思う。


移行するとしたら、ネガティブな動機が1つ、ポジティブな動機は2つある。

coffeeの開発の停滞

詳しくcoffeeの開発状況をウォッチしてないからよくわからないけど、詳しい人が「開発が停滞している」と言っているのでそうなんだと思う。
さよなら CoffeeScript – mizchi's blog

coffeeはここ1年ぐらいバージョンが上がった時にdiffだけは見ているのだが、毎回git diffで差分を見て一瞬で理解できるぐらいの更新しかされてない。まあようするに停滞しているのは間違いない。


HTML5の新しいAPIを使いたい

Web Bluetooth APIやWeb MIDI APIを使ってみると、型付き配列、Promise、IteratorなどのES6で増えた機能が前提で作られている。

この中で特にIteratorはES6のfor-of文で書くと綺麗に書けるんだけど、for-ofを使わないとしたらJavaみたいな感じでnext()を呼んだりdoneをチェックしたりとかしなくてはならなくて、すごく面倒くさい。

一方coffeeではfor-of文が対象Objectのkeyとvalueの2つを引数に取ってループを回す構文として実装されている。RubyのHash#eachみたいなやつ。
これはこれで便利なのだが、つまりcoffeeでES6のIteratorを扱うとつらい。
既にfor-ofが予約されてしまっている以上、もしcoffeeにIterator回し構文を追加するとしたら別の名前になる・・のかな?

たぶん、USB MIDIコントローラー250個とか接続できるから非同期でデバイス情報が返ってくるようになっているんだと思う。今後ハードウェアを扱うAPIは増えるだろうから、そういう場合はES6で書いたほうがいい。スマホWebアプリとかでそういう機会が増えると思う。


Map, Set, Promise, Buffer, 型付きArrayなど新しい組み込み型はブラウザならネイティブに実装されているのでcoffeeでも問題ない。というかbabelでも変換していない(ES5に翻訳しようがない)

ESLintが良い

少なくともcoffee-lintより気が利いている、というか勉強になる事をサジェストしてくれる気がする。具体的には忘れたけど。
プロジェクト毎のカスタムも容易でよくできている。

ES6で新しい組み込み型が増えているから、そういうのに合わせて細かくlintしてくれるようになるかもだし。


まとめ

別にふつうのアプリケーションを書くなら今はcoffeeでいいと思う。既に動いているのを書き直す必要もない。

いちはやく新しい文法で書いてみたいとかで無ければ、ES6(というかbabelが)変化激しいし、coffeeでいいかな・・・

ただ、いくつかの新しいHTML5 APIを使う場合はES6で書いたほうが明らかに書きやすくはなる。
ESLint良いよね。

0

coffeeからES6(babel)に少しずつ書き換える

coffee-scriptで書いていたwebアプリをES6(babel)に書きなおした。

全部を一気に書き直してハイ動いたーとやるのは無理なので、coffeeとES6のファイルが混在しても動かせるようにして、少しずつ書き直した。


書き直したのはこれ
React+Fluxxor+socket.ioでfluxなチャットを作った
https://github.com/shokai/node-flux-boilerplate

インストール

% npm install babel babelify browserify watchify -save-dev
とやっていたのだが、数日前にbabelが5から6にアップデートされて、babel-coreやbabel-preset-*など色々分割されて、大分色々変わってしまった。周辺ツールも対応の過渡期だったのでbabel5系を使うようにバージョンを指定した。

  "devDependencies": {
"babel": "~5",
"babelify": "~6",
"browserify": "~11",
"watchify": "~3.5"
}


クライアント側


coffeeで書いたReactのプログラムをbrowserifyでjsに変換して1ファイルに固めていたので、単純にtransformを複数指定してやったらES6とcoffeeを混在しても大丈夫になった。

% browserify --debug --extension=.cjsx --extension=.coffee --extension=.es6 -t babelify -t coffee-reactify client/app.jsx -o public/js/bundle.js

coffeeもES6もまとめてES5になってbundle.jsができる
もちろんwatchifyでも同様にES6/coffee混在可能。

babel6だったらたぶんこれで混在できる。browserifyやbabelifyも最新に上げてから
% browserify --debug --extension=.cjsx --extension=.coffee --extension=.es6 -t [ babelify --presets [ react es2015 ] ] -t coffee-reactify client/app.jsx -o public/js/bundle.js


サーバー側

サーバー側はregisterを使う。registerはrequire関数をhookして実行前に翻訳してくれるしくみなので、例えばbabel/registerを使うとcoffeeからES6をrequireして実行できる。

Require Hook · Babel
register.coffee

最初に起動するファイルがserver.coffeeの場合

こうやってアプリを起動している場合
% coffee server.coffee # 起動

babel/registerで動的にES6を実行できるようにする。server.coffeeのなるべく上の方で

require "babel/register"

を書いておくと、これで以後coffee内からES6をrequireするとbabelが逐次変換してくれるので、混ぜて書ける。
require "models/user" # user.coffeeが読み込まれる
require "controller/main.es6" # main.es6が読み込まれる

babel 6.x系だと別のnpmに切り出されて、babel-core/registerに変わった。


最初に起動するファイルがserver.es6の場合


最初に呼び出すファイルをES6で書きなおしたら、
% babel-node server.es6 # 起動

coffee-script/registerで動的にcoffeeを実行する。server.es6のなるべく上の方で
require("coffee-script/register");

以後ES6なスクリプトからcoffeeをimport/requireすると逐次変換して実行される。

同じファイル名の.coffeeと.es6があったらどうするか

拡張子まで書けば指定できる。
require("foo.es6");

ES6に統一し終わったら.coffeeファイルを消して、requireの拡張子も消せばok
require("foo");


npmの場合

coffeeで書いてES5にトランスパイルしてnpmjs.orgにリリースしているプロジェクトを部分的にES6で書き直す場合は、試してないけどたぶんビルドプロセスを
coffeeの変換の後にbabelの変換、という順に実行してbabel側のファイルで上書きしてやるようにすればいいのではないかと思う。

0

ES6の勉強をした

Web DB Press vol.87のES6の特集を読みながら、ES6を勉強した。とてもわかりやすかった。
紹介されている機能をちょこちょこと試す細かいスクリプトを色々書いたりメモを取ったりしたのでまとめておく。色々な機能があった。

https://github.com/shokai/es6-study

最近Web MIDI APIを使ってたんだけど、そのインタフェースがES6のPromiseとfor-ofで回す新しいIteratorとTypedArrayで実装されていたので、今後ブラウザに増える新しいAPIがES6前提で作られるなら普段からES6で書くようにしたほうがいいかなと思った。

個人的にはcoffeeの方がES6よりちょっと好き。でもfor-of文がES6で新しく増えたMapやIteratorを使う時に重要なんだけど、coffeeのfor-of文は別の意味があるので、coffeeからES6のAPIを使うのは辛そうな気がしている。一応IteratorはforEachで回せるけど


ES6の機能メモ

(coffeeとの比較みたいな感想入り)

Promise

非同期処理の新しいインタフェース。ScalaのFutureみたいなやつ。

変数の初期化

var foo = "zanmai";
var bar = "kazusuke";
var obj = {foo, bar};
// オブジェクト初期化省略記法
// {foo: "zanmai", bar: "kazusuke"} というオブジェクトができる

分割代入

var [foo, bar] = ["zanmai", "kazusuke"]; // 配列を配列で受けるとそれぞれ変数に代入できる
[a, b] = [b, a]; // 入れ替えもできる

class構文がある

extendsとかもできる。superで呼び出す
ほぼcoffeeのclassと同じ感じ
static/setter/getterを設定する修飾子がある
 class User extends EventEmitter2{
constructor(name){
super({delimiter: ":", wildcard: true}); // superで継承元を呼び出す
this.name = name; // プロパティを持てる
}
get website(){ // getter設定
return "http://github.com/"+this.name;
}
}
組み込みクラス(Array等)も継承で拡張できる
これまでArrayなどにメソッドを生やしたりすると全体に影響が及んでいたのが解決できる

Arrow Function

 var sum = (a, b) => {
return a + b;
}
functionを=>で書ける
外側のthisが引き継がれる
coffeeの=>と同じ
thisを引っ張ってきたくなければ今まで通りfunctionを使う


Generatorがある

function()*{ yield foo; }
値を複数回返せる機構
returnではなくyieldで返す
Rubyのblock+yieldと同じ

requireのかわりにexport/import

requireの挙動が複雑だったので仕様を直したもの
babelではrequireに変換される
export function foo(){ ~~~~ };
// module.exports.foo = function(){ ~~~ }; と同じ

export default function(){ ~~~ };
// defaultを付けると、module.exports = function(){ ~~~ }; と同じ

import foo from "foo";
// var foo = require("foo"); と同じ

import {EventEmitter2} from "eventemitter2";
// 部分的にrequireできる。var EventEmitter2 = require("eventemitter2").EventEmitter2; と同じ
// coffeeでrequireの左辺に{ }付けた時と同じ挙動

import {aaa, bbb} from "foo";
// 複数の子をまとめてrequireできる
// var aaa = require('foo').aaa;
// var bbb = require('foo').bbb; と同じ
classを1つだけ提供する時は
export default class ClassName { ~~ } で提供し
import className from “ClassName”; で読み込む

ユーティリティ関数を複数提供する時は、exportとimport {name}で部分的に読み込めるようにすると良さそう?

色々と関数が追加されている

String.repeat(num); // 文字列をくり返す
Array.from(obj); // 新規Arrayインスタンス作成
Object.assign(target, source1, source2...); // targetにsourceのプロパティをmix-inする

テンプレート文字列がある

`my name is ${this.name}`
バッククオートで囲う
${}の中が評価される

タグ付きテンプレート文字列

例えば
gyazz`gyazz記法を[[展開]]できる`
と書くと、 gyazz記法を<a href=”./展開”>展開</a>できる
function gyazz(templates, …values){ 処理して文字列を返す }
のような関数を宣言しておけば使える
`の前に関数名を書いて変換器を指定する
function gyazz(templates, ...values){
console.log("---gyazz markup start---");
var reg = /\[{2}([^\[\]]+)\]{2}/g;
var str = "";
for(var i = 0; i < templates.length; i++){
str += templates[i] || "";
str += values[i] || "";
}
if(!reg.test(str)){
return str;
}
return str.replace(reg, "<a href=\"./$1\">$1</a>");
}

2/8/16進数

0b1010
0xFA
0o70
接頭辞で書ける

デフォルトパラメータ

 var foo = function(name="shokai"){ console.log(name); };
foo("hoge"); // => hoge
foo(); // => shokai
引数なしの時の初期値を設定できる

可変長引数

var foo = function(...bar){ (略) }
barをArrayとして受け取れる
変数名の前に...を付ける
es5では特殊変数argumentsを見なければできなかった
関数宣言時に書けるのでぱっと見わかりやすい
静的解析ツールの支援も受けやすそう

スプレッドオペレータ

[1,2, ...[5,6,7]] // [1,2,5,6,7] になる

// Underscoreのrangeと組み合わせると強い
[..._.range(1,5), 11, 12, ..._.range(20,23)] // [1,2,3,4,5,11,12,20,21,22,23] になる

var [a,b,...c] = [1,2,3,4,5,6,7]; // 分割代入と組み合わせ
// a => 1
// b => 2
// c => [3,4,5,6,7]

定数

 const NAME = "shokai";
NAME = "zanmai"; // 代入できない
console.log(NAME); // => shokai

ブロックスコープ変数

letで宣言する
 if(true){
let name = "shokai";
console.log(name); //=> shokai
}
console.log(name); // name is not defined
for(let i = 0; i < 10; i++){ // varで宣言すると全部10になる
setTimeout(function(){
console.log(i); // 1,2,3,4,5,6,7,8,9,10
});
}
letがあるので、coffeeのdo (args) -> が必要なくなる


イテレータ

for(let i of arr){ console.log(i); }
// MapやSetはkey, valueで回せる
for(let [k,v] of map){ }

TypedArray

型付き配列
var arr = new Uint8Array(8); // 8bit型
arr[0] = 256; // これは8bit超えてるので代入されない。例外は発生せず代入処理だけが実行されない
arr[1] = 255; // 代入できる
WebGLやWebMIDIなど大量のデータを処理する時に速い
Nodeのbuffer由来
length等いくつかのプロパティが無い
Arrayに変換すればok
var arr2 = Array.prototype.slice.call(arr);

Proxy

Rubyのmethod missingと同等の事ができる
現状Firefoxでしか動かない?


実行環境・ツール


ES6をそのまま実行できる環境


ローカルで実行するちょっとしたスクリプトとかだと、いちいちコンパイルせずにES6のまま実行できると良い

Chrome
だいたいそのままes6が動く
Arrow Function以外
Chrome拡張とか作るならいきなりES6で書いても良さげ

Firefox
だいたいそのままes6が動く
Proxyもたしか動くはず
classが無い

Node.js
0.12なら--harmonyオプションを付けるとes6が実行できる
rest parameterやspread operatorが使えない

iojs
class, Generatorなど多くの機能をサポートしてる
--harmonyオプションを付ければもっと色々使える
--harmony_rest_parametersや--harmony_arrow_functionsなども必要に応じてONにできる
サーバーで実行するならiojsで良さそう。herokuでもengine指定すれば使える

babel/register
babelに含まれているpolyfill
ブラウザでもサーバーでも動くのでわりと良さそう
プログラムの頭で
require("babel/register")();
しておくと、以後ES6コードをrequireするとふつうに実行できる
Nodeのrequire関数がフックされて拡張されるらしい
最初のregisterするファイルにはes6を書けない、syntax errorになる
Node0.10でもfor-of文が動かせる

babel-node
babel付属の実行コマンド
es6を直接実行できる
Node0.10でもfor-of文が動かせる

osascript
MacのAppleScript実行コマンドだったが、YosemiteでJavaScriptでも書けるようになった。ES6が一部サポートされている。
分割代入とspread operatorとMapとSetが動く。
Promiseはオブジェクトはあるけど呼び出すとエラーが返る

ES6をふつうのJavaScript(ES5)に変換するツール


色々なブラウザ環境で実行させるとしたらES5に変換するのが現実的

babel
es6をes5に変換するトランスパイラ
元6to5
node 0.12なら問題なし、node 0.10でもiteraotr系以外動くJSが作れる
ES6でライブラリを書いてnpmに公開するとかにオススメ
新し目のブラウザでの実行もbabelで良さそう

traceur
es6をes5に変換
Google製

Browserify+babelify
es6からブラウザjsに変換、requireを辿って1ファイルに固めてくれるのでnode_modulesも読み込める

lint

ESLint+babel-eslint
es6をlintできる


ES6を実際使う

ES6は各ブラウザでもサポートしている機能としてない機能がバラバラなのでbabelでES5に変換してやるのが良いようだ。
ただ、for-ofのiteratorはbabelで変換すると変換後のコードにSymbol.iteratorが含まれているのでNode 0.10だと動かない。Node 0.12だと動く。

あとProxyはFirefoxでしか動かないっぽい。

色々試した感じ、例えばES6で書いてbabelで変換してからNodeのライブラリとしてnpmで配布する時はfor-ofだけ使わないように意識すれば事故は起こらないはず。
(MapやTypedArrayみたいな新しい組み込み型は使わないとして)


ES6と変換後のES5の動作をブラウザでも手軽に確かめたかったので、ちょっとしたツールを作った。
こんなかんじでgithubに置いてあるES6/ES5が実行できる

Herokuでも動いてて、サーバーはES6で書いてbabel-nodeで実行してる
https://shokai-es6-study.herokuapp.com/

0

ブラウザ/NodeでKORG nanoKONTROL1/2を使えるライブラリをES6で作った

作った。

https://www.npmjs.com/package/korg-nano-kontrol

% npm i korg-nano-kontrol -save

ブラウザならWeb MIDI APIで、Nodeならmidi npmでMIDIコントローラと通信するようになっているのでrequireすればあとは適当に動く。
あと、最初coffeeで書いて全部完成したんだけどbabel(ES6)で書き直してみた。というのもES6は前々から使ってみたかったんだけど、1から機能を1つずつ勉強するのは苦手なので、既に慣れてるcoffeeの機能をフルに使ってある程度の大きさの物を実装してからES6で書きなおす事で色々な機能を一気に使わざるを得ない状況を作って勉強してみた。

KORG nanoKONTROLとは

EdisonとMIDIコントローラでHueを調光する
とか
Node.jsとMIDIコントローラでHueを調光する
で使っている便利なMIDIコントローラー。ツマミとスライダーがたくさん付いている。

もともとkorg-nanoというライブラリがある

んだけど、nanoKONTROL2に対応していなかったのと、修正しようにも2つのデバイスに対応するなら1から作ったほうが早そうな設計だったのでついでにWeb MIDI APIにも対応させて作ってみた。

使いやすかったのでAPIはほぼそのままにしてある。

使う


connect()するとPromiseが返ってくる
var nanoKONTROL = require('korg-nano-kontrol');

nanoKONTROL.connect()
.then(function(device){
console.log('connected!' + device.name);
// do something
})
.catch(function(err){
console.error(err);
});

で、

device.on('slider:0', function(value){ // スライダー0番を取得
console.log("slider:0 >>> "+value);
});
とか
device.on('knob:*', function(value){ // 全ノブ取得
console.log(this.event+' => '+value);
});
とかすると取れる。



Web MIDI API

ブラウザでもBrowserifyで固めれば同様に使える



EventEmitter2

EventEmitter2にはワイルドカードでイベントを受け取れる機能がある。上でdevice.on(“knob:*”, func)とかやってるのがそれ。

ES6ではclass nanoKONTROL2 extends EventEmitter2して、コンストラクタの中でsuper({wildcard: true, delimiter: “:”})したら有効になった。


coffeeからbabel(ES6)に書き直した

簡単に感想を列挙すると

coffee-scriptよりは機能が少ない。
coffeeのfor k,v of objでkey/valueを回せるのと、do (arg) ->でクロージャ即時実行と、添字付きfor文と後置if文が使いたい。
でもfor ofはES6組み込みクラスのMapをイテレータでfor [k,v] of Mapすればいいし、doはletがあれば必要ない。

unlessがほしい。ifに!つけるのつらい。
関数宣言する時にexport付ければexportできるのは便利。
テンプレート文字列もよい。
[3…8]みたいなのはlodashの_.range(3,8)で代用できる。
関数のデフォルトパラメータ指定便利。
classは違和感なく書ける。getter/setterやstaticの定義も便利。
for(let i of array)を使うとnode 0.12やChromeでは動くけど0.10やSafariでは動かないJSができる。Symbol is not definedエラーが出る。

coffeeで書くのに比べてコード量は1.3倍ぐらいになる。
でもまあ、node 0.12以上で動かすならどっちで書いてもいいかなという感じがした。
古い環境をターゲットにするならcoffeeの方が安心感がある。

ESLint

lintツールにESLintを使ってみた。coffee-lintより色々しっかり見てくれてる気がする。

.eslintrcを置くと全ルールがカスタムできてよい。

parserにbabel-eslintを指定するとES6がlintできる。


gruntやめてnpm runにした

参考:Grunt/Gulpで憔悴したおっさんの話 – MOL

grunt-babelが複数ファイルの変換設定がかっこよく書けないのと、gulpがやはり理解不能なのでコンパイルはbabel –watchでいいやと思った。

parallelshellで複数コマンド同時実行できるので、esw(eslint-watch)も一緒に実行してる。

あと、npm2.0からnpm run コマンド名 — 追加の引数という感じに–を挟むと引数を追加して実行できるので、babelのビルド設定をそのままに–watchを追加して実行できてよい。

watchという続けて書いたコマンドをファイル更新監視して実行してくれるnpmもあるんだけど、なんか反応が遅いのでeslint-watchとbabel –watchで良いやとなった。これだとEdison上でもサクサク動いて良い。
package.json
  "scripts": {
"test": "eslint src/*.es6 src/*/*.es6",
"build": "babel src/ --out-dir lib/ --source-maps inline",
"watch": "parallelshell 'npm run build -- --watch' 'esw src/ --watch'",
"buildSample": "browserify --debug samples/webmidi/src/main.js -o samples/webmidi/dist/bundle.js"
},