メモ: coffeescriptつこうてみた

今作ってるモノで試しにCoffeeScript使ってみたら存外に書きやすく、かつ楽しかったのでメモ。

CoffeeScriptとは

本家
CoffeeScriptRuby,Python,Haskellから影響を受けたプログラミング言語です。
CoffeeScriptの文法にしたがって書いたものをcoffeeコマンドでコンパイルするとJavaScriptになります。

要するに、JavaScriptシンタックスが嫌いとか、JavaScriptゴチャゴチャしがちでなんとかしたいとか、Webブラウザで実行するスクリプトRubyとかPythonとかHaskellとかみたいに書きたいよーとか思ってる人にはおすすめです。

長所と短所

まだ使い始めて1週間くらいなのですが、現時点で感じることは

長所

  • 同じ事をするにもJavaScriptよりコード量が少なくてすむ(Wikipediaによると1/3)
  • 吐き出すのがJavaScriptなので、うまく動かない際にも、何が起こってるのか理解しやすい
  • 書いてて気持ちいい
  • 読みやすい

短所

  • ブラウザのエラー出力は、コンパイル結果のJavaScript上の位置を示すので、そこからCoffeeScript上の間違いを自分で探す必要あり
  • 一部、IenternetExplorer8では実行できないJavaScriptを吐き出す


インストール
インストールにはNode.jsが必要です。Ubuntuなら

$ sudo apt-get install nodejs

です。Macとかでも、多分homebrewとかportsとかにあると思います。たぶん。

Node.jsが入ったら、あとはnpmコマンドでインストールですが、システム全体で共用するなら

$ sudo npm install -g coffee-script

あるいは、なにかしらのプロジェクトを作ってそのプロジェクトディレクトリ内にインストールしたいなら

$ mkdir hoge_project
$ cd hoge_project
$ npm install coffee-script

てやります。

Hello World
とりあえず、最初のCoffeeScriptを作ってみます。
emacsなど好みのエディタで"helloworld.coffee"というファイルを新規作成し、以下のようにかきます。

alert "hello world"

コンパイルしてみます。

$ coffee -c helloworld.coffee

helloworld.jsというファイルができたと思います。中身を見てみます。

// Generated by CoffeeScript 1.4.0
(function() {

  alert("hello world");

}).call(this);

このJavaScriptをブラウザから呼ぶためにHTMLファイルを用意します。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>はじめてのCoffeeScript</title>

    <script src="helloworld.js"></script>
	
  </head>
  <body>
  </body>
</html>

Ajaxとかするわけじゃないので、このままHTMLファイルダブルクリックで開けば実行できます。

ある程度大きなプロジェクトではまとまった機能毎にクラスにまとめるとかするといいと思います。

class ClassSample

  # インスタンス変数
  name: null

  # 他の関数の呼び出しは "@間数名()" で
  start_sample: () ->
    @hello()
    @name = "shin"
    @my_name()
    @map_sample()  

  # Rubyみたいに、引数にデフォルト値を指定できます
  # Rubyみたいに、文字列に#{}で値を埋め込めます
  hello: (str = "world")->
    alert "hello #{str}"

  # インスタンス変数の参照は "@変数名" で
  my_name: () -> 
    alert "myname is #{@name}"

  # 配列内の各要素に処理を施した新たな配列をかえします
  # 無名関数は "(引数) -> 処理" です
  map_sample: () ->
    array = [1,2,3,4,5,6]
    new_array = array.map (e) -> e * e
    console.log new_array

Rubyユーザが気をつけるべきことは、CoffeeScriptではブロックの終わりのendがいらないってことです。
この辺はPythonの影響があるらしく、インデントでブロックレベルを表現します。
たとえば条件分岐などは

a = 10

if a > 11
   alert "10より大きい"
else
   alert "10以下"

てな具合です。classや関数、ループなども同様となります。

コンパイルするとこうなります

$ coffee -c class_sample.coffee
// Generated by CoffeeScript 1.4.0
(function() {
  var ClassSample;

  ClassSample = (function() {

    function ClassSample() {}

    ClassSample.prototype.name = null;

    ClassSample.prototype.start_sample = function() {
      this.hello();
      this.name = "shin";
      this.my_name();
      return this.map_sample();
    };

    ClassSample.prototype.hello = function(str) {
      if (str == null) {
        str = "world";
      }
      return alert("hello " + str);
    };

    ClassSample.prototype.my_name = function() {
      return alert("myname is " + this.name);
    };

    ClassSample.prototype.map_sample = function() {
      var array, new_array;
      array = [1, 2, 3, 4, 5, 6];
      new_array = array.map(function(e) {
        return e * e;
      });
      return console.log(new_array);
    };

    return ClassSample;

  })();

}).call(this);

ただ、これだと外部から呼び出せません。
外部から呼び出せるようにするには、コンパイルオプションに-b(--bare)を付加します。

$ coffee -bc class_sample.coffee

できあがったものはこうなります。

// Generated by CoffeeScript 1.4.0
var ClassSample;

ClassSample = (function() {

  function ClassSample() {}

  ClassSample.prototype.name = null;

  ClassSample.prototype.start_sample = function() {
    this.hello();
    this.name = "shin";
    this.my_name();
    return this.map_sample();
  };

  ClassSample.prototype.hello = function(str) {
    if (str == null) {
      str = "world";
    }
    return alert("hello " + str);
  };

  ClassSample.prototype.my_name = function() {
    return alert("myname is " + this.name);
  };

  ClassSample.prototype.map_sample = function() {
    var array, new_array;
    array = [1, 2, 3, 4, 5, 6];
    new_array = array.map(function(e) {
      return e * e;
    });
    return console.log(new_array);
  };

  return ClassSample;

})();

これをよびだすHTMLファイルを用意します。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>はじめてのCoffeeScript</title>

    <script src="class_sample.js"></script>
    <script>
      var sample = new ClassSample();
      sample.start_sample();
    </script>

  </head>
  <body>
  </body>
</html>

HTMLダブルクリックで実行できます。

自動コンパイルとか

ここで、ファイルを編集する度にコンパイルするの面倒くさいですよね?
coffeeコマンドにはファイルを監視してて、変更されたらそのファイルを自動的にコンパイルしてくれるオプションがあります。
"-w"をつければ自動コンパイルです。

$ coffee -wc class_sample.coffee

さっきの-bと合わせると

$ coffee -wbc class_sample.coffee

また、ソースファイルの場所とJavaScriptの吐き出し先を指定するとこんな感じです。

$ coffee -wbc -o js/build src/coffee/*.coffee

CoffeeScriptにはcakeというコマンドがあってCakefileを作っておくとタスクが自動化できます。

sys  = require "sys"
fs   = require "fs"
exec = require("child_process").exec
util = require "util"

COFFEE = "coffee"
OPTION = "-cb"
WATCH_OPTION = "-wbc"
SRCDIR = "./"
OUTDIR = "js/build"

#
# @desc 与えられたオプションでコンパイルします
# 
compile = (option) ->
  util.log "compiling coffee..."
  command = "#{COFFEE} #{option} -o #{OUTDIR} #{SRCDIR}/*.coffee"

  exec command, (error, stdout, stderror) ->
    util.log error if error
    util.log stdout if stdout
    util.log stderror if stderror

    if error
      util.log "failure"
    else
      util.log("ok")
  

task "build", "compile coffee to java script", ->
  compile(OPTION)

task "watch", "compile coffee to java script", ->
  compile(WATCH_OPTION)

上記だと、"cake build"とかやるとコンパイルできます。
ただ、これ、コンパイルエラーが出力されないんですよね... たぶん僕が何か間違ってるんでしょうが。。

まあこれくらいの内容なら普通にMakefileでも書けます。

COFFEE=coffee

src_dir=.
out_dir=js/build

all: coffee

coffee:
	@$(COFFEE) -bc  -o ${out_dir} ${src_dir}/*.coffee

coffee-auto:
	@$(COFFEE) -wbc -o ${out_dir} ${src_dir}/*.coffee

clean:
	rm js/build/*.js

ビルドするコマンドが make coffee なんてちょっとオシャレですね(ぉ

今回はここまで。