CSS3 でロールオーバーの transition を非対称に行う

CSS3 の transition を使ってロールオーバーを行うとき、ポインタが載った時と外れた時で非対称にアニメーションしたいとき。

ポイントは次の通り。

  1. :hover 擬似要素のセレクタにポインタが載った時の transition を指定
  2. 擬似要素なしのセレクタにポインタが外れた時の transition を指定

コードはこんな感じ。(vendor prefix とかは各自で良い感じで)

.box {
  width: 100px;
  height: 100px;
  background-color: red;

  transition-property: background-color;

  /* ポインタが外れた時 */
  transition-duration: 1s;
  transition-timing-function: ease-in;
}

.box:hover {
  background-color: yellow;
  
  /* ポインタが載った時 */
  transition-duration: 0.1s;
  transition-timing-function: ease-out;
}

実際のデモ (jsfiddle)

This was posted 4 months ago. It has 6 notes and 0 comments.

jQuery Migrate の Warning を console に出さないようにする

jQuery 1.9 以降で後方互換を保つための jQuery Migrate Plugin の警告をミュートする方法。

jQuery を読み込んだあと、Migrate Plugin を読み込む前に次の行を追加。

$.migrateMute = true;

以上

This was posted 4 months ago. It has 4 notes and 0 comments.

Raspberry Pi で Node v0.8.9 をビルド

メモ

ビルド時間

real    72m9.107s
user    68m15.610s
sys     1m10.440s

uname

$ uname -a
Linux raspberrypi 3.1.9+ #272 PREEMPT Tue Aug 7 22:51:44 BST 2012 armv6l GNU/Linux

/proc/cpuinfo

nulltask@raspberrypi:~$ cat /proc/cpuinfo 
Processor   : ARMv6-compatible processor rev 7 (v6l)
BogoMIPS    : 996.14
Features    : swp half thumb fastmult vfp edsp java tls 
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part    : 0xb76
CPU revision    : 7

Hardware    : BCM2708
Revision    : 1000002
Serial      : 00000000c902d673
This was posted 8 months ago. It has 1 note and 0 comments.

JavaScript 基礎 (Part 1) 資料

公開の勉強会をやったので、その資料を公開。

追記 (2012-06-19 15:40)

syoichi さんからのご指摘を反映

  • Boolean にキャストすると false になるものに 0 を追加
  • 即値関数 → 即時関数

追記 (2012-06-20 16:15)

@poyoshi さんからのご指摘を反映

  • ループの continue で偶数と奇数が逆になっていたのを修正

自己紹介

  • Seiya Konno (@nulltask)
  • プログラマ
  • ユニバ株式会社所属

目的

今回の目的。

  • JavaScript の文法を理解する

今日は特定の処理系 (ブラウザなど) に依存しない JavaScript の言語のみに着目した内容になります。そのため座学っぽくなってしまいますが、お手柔らかに…

次回以降実践を交えながら JavaScript で本格的なオブジェクト指向をやっていく予定です。

対象者

  • 新しいプログラミング言語として JavaScript を覚えたい人

JavaScript とは

  • 作者: Blendan Eich さん (現 Mozilla CTO)
  • 1995 年に Netscape 2.0 とともに登場
  • ブラウザ上で動作するスクリプト言語
    • 現在は Node.js に代表されるサーバサイドの言語としても注目される
  • 動的言語
  • プロトタイプベースのオブジェクト指向言語
    • クラスはないけど立派にオブジェクト指向ができます
  • 名前は似ているが Java とは別物!

構文の特徴

  • C 言語に近い平易なシンタックス
  • 大文字小文字の区別あり
    • getName, getname, GETNAME はそれぞれ別物として扱われる。
  • 連続したホワイトスペース (改行, 空白, タブ) は一つにまとめられる。
  • 文はセミコロン ; で区切る。

変数

値を格納しておくための識別子。一般的な言語の変数と同じ。

宣言と代入

var 変数名;  // 宣言
変数名 = 値;  // 代入

宣言時に代入してもよい。

var 変数名 = 値; // 宣言と代入を同時に

宣言時に値を代入しなかった場合、undefined という特別な値になる。

var score = 10;

カンマで区切って複数宣言することもできる。

var a = 10,
    b = 100,
    c = 1000;
  • 静的型付け言語と違い、型宣言はしない。
  • 型宣言はないが、型はある。

変数名に使える文字

  • 英数時 [0-9a-zA-Z]
  • アンダースコア _
  • ドル記号 $

変数名は数字から始まってはいけない。

var 123abc = 'test'; // NG

コメント

プログラムに注釈を入れることが可能。コメントはインタプリタから無視される。

// 一行コメント
/* 複数行に
   またがる
   コメント */
// 次の行は無視される
// var a = 10;

型とリテラル

JavaScript で扱う「値」は必ず何かの「型」に従属する。

JavaScript の型

  • 数値 (Number)
  • 文字列 (String)
  • 真偽値 (Boolean)
  • オブジェクト (Object)
    • 関数 (Function)
    • 配列 (Array)
    • 日付 (Date)
    • 正規表現 (RegExp)
    • エラー (Error)
  • Null
  • Undefined

数値 (Number)

整数型 (int)、小数型 (float) の分類はなくて、引っ括めて数値型。

var num1 = 10,
    num2 = 10.5,
    num3 = 1034;

0x で始まる場合は 16 進数

var hex = 0xFF; // 255

parseInt(), parseFloat() で整数型へキャストする。

var a = parseInt('32', 10);    // 32
var b = parseFloat('10.5');     // 10.5

文字列 (String)

var greeting1 = 'hello world',
    greeting2 = "hello world";
    
var genre = 'Rock\'n Roll'; // Rock'n Roll
var tag = "<div class=\"cleafix\"></div>";  // <div class="clearfix"></div>

ダブルクオーテーション " かシングルコーテーション ' のいずれかで括る。どちらでも良い。\ でエスケープする。

String() で文字列へキャストする。

var str = String(10);  // "10"

論理値 (Boolean)

var like = true,
    hate = false;

Boolean() で論理値へキャストする。

var success = Boolean(1);  // true

キャストすると false になるオブジェクト

  • 0 (+0, -0)
  • null
  • ''
  • NaN
  • undefined

-1true にキャストされる。

正規表現 (RegExp)

var pattern = /java(script)?/gi;

pattern.test('Java');   // true
pattern.test('JavaScript'); // true
pattern.test('Objective-C');    // false

RegExp() で正規表現にキャストする。

var regexp = RegExp("Go{2,3}gle"); // /Go{2,3}gle/

null

「値が無い」ことを示す。

var foo = null;

undefined

「値が未定義」であることを示す。

実は変数なので上書きできる。 undefined = 100;

undefined = void 0; // 確実に undefined を得る方法

オブジェクト (Object)

var obj = { a: 10, b: 20, hello: 'world' };
var val1 = obj.a; // 10

ここでの a, b, hello はプロパティと呼ばれる。詳細はまた後で。

配列 (Array)

var arr = [10, 20, 30, 'Hello', 'world'];
var val = arr[0]; // 10
var len = arr.length; // 5

arr.push(100);
arr.push(200);

// arr is [10, 20, 30, 'Hello', 'world', 100, 200]

len = arr.length // 7

Arraylength プロパティで要素数を調べられる。push() で末尾に追加。

関数 (Function)

function funcName() {
    // do something...
}

詳細はまた後で。


予約語

JavaScript の予約語。

  • break
  • case
  • catch
  • continue
  • default
  • delete
  • do
  • else
  • false
  • finally
  • for
  • function
  • if
  • in
  • instanceof
  • new
  • null
  • return
  • switch
  • this
  • throw
  • true
  • try
  • typeof
  • var
  • void
  • while
  • with

変数名などに使用できない。


演算子

JavaScript の演算子は C や Java と同様のスタイル。

算術演算子

整数型同士の演算。

足し算 (+)
var a = 10 + 20; // 30
引き算 (-)
var a = 10 - 20; // -10
掛け算 (*)
var a = 10 * 20; // 200
割り算 (/)
var a = 10 / 20; // 0.5

ゼロ 0 で割ると Infinity もしくは -Infinity という特別な値となる。

割った余り (%)
var a = 5 % 3; // 2

ゼロで割った余りは NaN (Not a Number) という特別な値となる。

インクリメント・デクリメント

整数型を 1 ずつ加算 or 減算。

var a = 0;
a++;  // 1
a++;  // 2
var a = 100;
a--;  // 99
a--;  // 98

++a--a とも書ける。

代入演算子

変数に値を代入する。

var a = 10;
a += 100;  // 110 (a = a + 100; と同様)
a -= 50;  // 60 (a = a - 50; と同様)

+=, -= で代入と演算を同時に出来る。

比較演算子

ふたつの式を比較する。比較結果が正しければ true, そうでない場合 false と評価される。

a == b;     // 一致
a === b;    // 完全に一致
a != b;     // 一致しない
a > b;       // a は b より大きい
a >= b;      // a は b より大きいか等しい
a < b;       // a は b 未満
a <= b;      // a は b 以下

===== の違い。

'10' == 10      // true
'10' === 10     // false

== は比較時に型変換を行う。=== は型変換を行わない。

文字列連結子 (+)

文字列同士の連結。

var message = 'hello';
message = message + ', world.'; // 'hello, world.'
message += ' everyone!'; // 'hello, world. everyone!'

論理演算子

var a = true, b = false, c = true, d = false;

a && b;  // false
a && c;  // true

a || b;  // true
b || d;  // false

!(a && b);  // true
!(b || d);  // true

&& は比較するすべての値が true であれば true, || は比較する値が一つでも true であれば true

三項演算子

条件によって代入する値を変更することができる。

var child = (age <= 18) ? "yes" : "no";

演算子の優先順位

var a = 1 + 2 * 3; // 2 * 3 が優先される
var b = (1 + 2) * 3;  // カッコを使って優先順位を変更できる

制御文 : 分岐

JavaScript の分岐は C や Java と同様のスタイル。

if

条件によって処理を分岐する。

if (a > b) {
    // a が b よりも大きいとき
} else {
    // a は b 以下
}

条件を複数書くこともできる。

if (a > b) {
    // a が b よりも大きい時
} else if (a > c) {
    // a が c よりも大きい時
} else {
    // それ以外の時
}

switch

複数が複数存在する場合に有用。

switch (char) {
    case 'a':
        // do something if char is 'a'
        break;
    case 'b':
        // do something if char is 'b'
        break;
    case 'c':
        // do something if char is 'c'
        break;
    case 'd':
        // do something if char is 'd'
        break;
    case 'e':
    case 'f':
        // do something if char is 'e' or 'f'
        break;
        // 何にも一致しなかった時
    default:
        break;
}

break を書かない場合はフォールバックする。

switch (num) {
    case 1:
        // do something if num is 1
        break;
    case 2:
        // do something if num is 2
    case 3:
        // do something if num is 2 or 3
    case 4:
        // do something if num is 2 or 3 or 4
    default:
        // do something if num is not 1
}

制御文 : ループ

JavaScript のループ文は C や Java と同様のスタイル。

for

var sum = 0;

for (var i = 0; i < 10; i++) {
    sum = sum + (i * 10);
}

while

var i = 0,
    sum = 0;

while (i < 10) {
    sum = sum + (i * 10);
    i++;
}

do-while

var i = 0,
    sum = 0;

do {
    sum = sum + (i * 10);
} while (i < 10);

ループ内で break を実行するとループを終了させることができる。

var i = 0;

while (true) {  // 無限ループ
    if (i > 5) {
        break;  // i が 5 より大きくなったらループを中断
    }
}

ループ内で continue を実行するとそのループをスキップし後続のループを処理する。

var i = 0;

for (i = 0; i < 10; i++) {
    if (i % 2) {
        continue;   // 奇数の場合はスキップ
    }
    // 偶数の時だけここに行く
    // do something
}

オブジェクト

JavaScript のオブジェクトは「プロパティ」と「値」のペアで表現される。

var obj = {
    a: 10,
    b: 20,
    c: 30
};

次のように書くこともできる。

var obj = {};
obj.a = 10;     // ドットシンタックス
obj.b = 20;
obj['c'] = 30;    // 連想配列風

ネストすることもできる。

var my = {
    namespace: {
        foo: 'baz'
    }
};

my.namespace.foo;       // 'baz'
my['namespace'].foo;  // 'baz'

JavaScript のオブジェクトはネームスペースの生成や連想配列として使うこともできる。

オブジェクトにプロパティが存在するか調べるには in 演算子を使う。

var obj = {
    a: 10,
    b: 20
};

var res1 = 'a' in obj;    // true
var res2 = 'z' in obj;    // false

for-in ループでプロパティを列挙できる。

var obj = {};

obj.foo = 10;
obj.bar = 20;
obj.baz = 30;

for (var p in obj) {
    obj[p];
}

関数

ここでは一般的な関数の振る舞いについて説明する。

定義

function キーワードを使って関数を定義する。

function funcName() {
    // do something
}

return で戻り値を返すことができる。return した時点で関数の処理は終わる。

function max(a, b) {
    if (a > b) {
        return a;
    }
    return b;
}

呼び出し

関数名に () をつけることで呼び出すことができる。

funcName();

引数を指定することができる。

funcName(a, b);

代入式で戻り値を取得できる。

var result = max(10, 20);

自分自身を呼び出すこともできる。(再帰呼び出し)

function pow(n) {
    if (n == 0) {
        return 1;
    }
    return pow(n - 1) * n;
}

var result = pow(5); // 120 (5!)

JavaScript の関数

JavaScript の関数の面白い特徴。

  • 関数そのものを値として使うことができる
    • C などの関数ポインタ (のようなもの)
  • 名前の無い関数を作ることができる (無名関数)
  • 関数はスコープを生成する
    • 関数の中に関数を定義できる
  • クロージャを作ることができる
  • 関数に new キーワードを使ってオブジェクトを生成することもできる

関数を値として使う例

宣言した関数を変数に代入しちゃう。

function funcName() {
    // do something
}

var fn = funcName;  // 変数に代入
fn();   // 呼び出し

関数を引数として渡せちゃう。

function funcName(callback) {
    // do something...
    callback();
}

function callbackFunc() {
    // run after `funcName`
}

funcName(callbackFunc);

名前の無い関数 (無名関数)

var fn = function() {
    // do something...
};

fn(); // 呼び出し

以下はよくある一例。無名関数をそのまま関数の引数として渡す。

function doSomething(callback) {
    // do something…
    callback();
}

doSomething(function() {
    // run after `doSomething`
});

無名関数をいきなり実行しちゃう。(即時関数)

(function() {
    // run anonymous function
})();

オブジェクトと組み合わせちゃう。

var obj = {
    max: function(a, b) {
        if (a > b) {
            return a;
        }
        return b;
    },
    min: function(a, b) {
        if (a > b) {
            return b;
        }
        return a;
    }
};

obj.max(10, 20);    // 20
obj.min(10, 20);    // 10

関数はスコープを生成する

関数の中の変数には関数の外側からは参照できない。

function foo() {
    var a = 100;
}

var baz = a;    // baz === undefined

関数の中に関数を定義する。

function foo() {
    // outer function
    function bar() {
        // inner function
        function baz() {
            // super inner function
        }
    }
}

外側の関数の値は参照できる。外側からはできない。(レキシカルスコープ)

function outer() {
    var a = 10,
        b = 20;
    function inner() {
        var c = a;  // c == 10
    }
    inner();
    var x = c;  // x === undefined
}

関数外で変数を宣言した場合はグローバルオブジェクトに定義される。(グローバル汚染)

var a = 10, b = 20;    // どこからでも参照できてしまう

function() {
    var c = a,
        d = b;  // c == 10, d == 20
}

即値関数を応用してグローバル汚染を回避する。

(function() {
    var a = 10,
        b = 20;
})();

カプセル化っぽいことも。

function capsule() {
    var himitsu1 = 10,
        himitsu2 = 20;
    
    return function(a, b) {
        return a + b + himitsu1 + himitsu2;
    };
}

var fn = capsule();
var result = fn(100, 200);  // result == 330

変数 himitsu1, himitsu2 の存在が隠蔽される。

クロージャを作る

function closure() {
    var n = 0;
    return function() {
        n++;
        return n;
    };
}

var fn = closure();

var res1 = fn(),    // 1
    res2 = fn(),    // 2
    res3 = fn();    // 3

関数内の n の値が保持されつづける。

キーワード new で関数からオブジェクトを生成

function Person(name, age) {
    this.name = name;
    this.age = age;
    
    this.introduce = function() {
        return 'My name is ' + this.name + '. I\'m ' + age ' years old.';
    };
}

var alice = new Person('Alice Liddel', 10),
    bob = new Person('Bob Dylan', 71);

var introOfAlice = alice.introduce();   // 'My name is Alice Liddel. I'm 10 years old.'
var introOfBob = bob.introduce();   // 'My name is Bob Dylan. I'm 71 years old.'

alice.sayAboutFuture = function(n) {
    return n + ' years after, my age will be ' + (this.age + n) + '.';
};

var future = alice.sayAboutFuture(90);  // '90 years after, my age will be 100.'
  • コンストラクタとして振る舞う関数 Personコンストラクタ関数と呼ぶ
  • コンストラクタ関数は慣例的に大文字からはじめる
  • new を使って関数を呼び出す場合、this は生成されたオブジェクト自身の参照となる
  • アクセス修飾子は存在しない
    • すべて public 扱い (alice.age = 100; ができてしまう!!)
  • あとからプロパティを追加することができる

カプセル化や継承などの踏み込んだ話は次回!


例外処理

Java の例外と同様のスタイル。

捕捉

try {
    // do something.
    // 例外が起きるかもしれないコード
} catch (e) {
    // 例外が起きた時に実行される
    // e にスローされたオブジェクトが渡される
} finally {
    // 例外が起きても起きなくても必ず実行される
}

finally は書かなくても良い。

例外のスロー

try {
    if (Math.random() % 2) {
        throw new Error('なにか起きた');
    }
    // do something...
} catch (e) {
    // e.message でエラーメッセージを取得できる
}

Error オブジェクトにかぎらず、なんでもスローできるが、原則として Error オブジェクトのインスタンスをスローするのが一般的。

This was posted 1 year ago. It has 48 notes and 0 comments.

Xcode 4.3 で rvm install ruby-1.9.2 そして Pow 0.4.0 と RVM

Xcode 4.3.2 世代の CommandLine Tools が入っている状態で rvm install ruby-1.9.2 が失敗する事案が発生。

♪ rvm install 1.9.2-p290
The provided compiler '/usr/bin/gcc' is LLVM based, it is not yet fully supported by ruby and gems, please read `rvm requirements`.

それならば --with-gcc=clang オプションを使って GCC ではなく Clang でコンパイルしてしまおう。

 rvm install 1.9.2-p290 --with-gcc=clang
clang: error: unsupported option '--with-libyaml'
Building 'ruby-1.9.2-p290' using clang - but it's not (fully) supported, expect errors.
Installing Ruby from source to: /Users/nulltask/.rvm/rubies/ruby-1.9.2-p290, this may take a while depending on your cpu(s)...

ruby-1.9.2-p290 - #fetching 
ruby-1.9.2-p290 - #downloading ruby-1.9.2-p290, this may take a while depending on your connection...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 8604k  100 8604k    0     0  1019k      0  0:00:08  0:00:08 --:--:-- 1044k
ruby-1.9.2-p290 - #extracting ruby-1.9.2-p290 to /Users/nulltask/.rvm/src/ruby-1.9.2-p290
ruby-1.9.2-p290 - #extracted to /Users/nulltask/.rvm/src/ruby-1.9.2-p290
ruby-1.9.2-p290 - #configuring 
ruby-1.9.2-p290 - #compiling 
ruby-1.9.2-p290 - #installing 
Removing old Rubygems files...
Installing rubygems-1.8.24 for ruby-1.9.2-p290 ...
Installation of rubygems completed successfully.
ruby-1.9.2-p290 - adjusting #shebangs for (gem irb erb ri rdoc testrb rake).
ruby-1.9.2-p290 - #importing default gemsets (/Users/nulltask/.rvm/gemsets/)
Install of ruby-1.9.2-p290 - #complete 
clang: error: unsupported option '--with-libyaml'
Ruby 'ruby-1.9.2-p290' was build using clang - but it's not (fully) supported, expect errors.

--with-libyaml で警告。でもビルドできた。

で、Pow 0.4.0 にアップデートしたら、”Automatic RVM support is deprecated” と言われる事案が発生。

今のところ動作するけど、次のメジャーバージョンで .rvmrc の自動ロードが取りやめになるので、.powrc にこれ書いといたほうが良さげ。

if [ -f "$rvm_path/scripts/rvm" ] && [ -f ".rvmrc" ]; then
  source "$rvm_path/scripts/rvm"
  source ".rvmrc"
fi

ふう。

This was posted 1 year ago. It has 1 note and 0 comments.

connect-tweek という Conenct/Express 向けのミドルウェアを作った

2011 年の Ars Electronica の Intaractive Art 部門で Golden Nica 賞を受賞した Newstweek のオマージュ的なミドルウェア。

プロキシとして振る舞い、ファイル形式とか URL に応じてデータを弄ったり出来るようなもの。サンプルとして画像へのアクセスをハンドリングして、ベーコンに差し替えてしまうデモを作ってみた。

ベーコン

ベーコンは 35 秒ぐらいから。


このページを選んだのは、ベーコンのアニメーションが面白かったから。実際に動いているものは下の URL から確認できる。

サブドメインのところをいじってやれば別のサイトでベーコンを楽しむことも出来る。

サンプル

動画のコードはこんな感じ。ベーコンの画像は各自用意してほしい。

API は Connect 本体のミドルウェアシステムと Socket.IO__defineGetter__ を使ったフラグシステムに大きな影響を受けている。jsdomcheerio を使えば、スクレイピングもやれる。

ちょっとした使い方を Readme.md に乗っけているのでこっちもよろしく。

DHCP サーバとネームサーバ にちょっとした細工を施しておけば、Newstweek っぽいことも出来るはず。くれぐれも悪用厳禁で。

This was posted 1 year ago. It has 3 notes and 0 comments.

Connect コードリーディング: アプリケーション編 (詠む会向け資料)

これは Connect (2.3.0) を詠む会向けの資料です。読む会の詳細についてはこちらを御覧ください。


前回は初期化のシーケンスと、connect のアプリケーションを生成するところまで読みました。今回は ./lib/proto.js からアプリケーションの実装を読み解いていきましょう。長めになってしまいますが、お付き合いいただければ幸いです。

./lib/proto.js

定義されている関数は次の3つになります。

  • use(): ミドルウェアの登録
  • handle(): HTTP リクエストのハンドリング
  • listen(): HTTP をリッスン

まず、use() から見て行きましょう。

proto.use()

ミドルウェアの登録を実行する関数です。

app.use = function(route, fn){

  /* snip */

  // add the middleware
  debug('use %s %s', route || '/', fn.name || 'anonymous');
  this.stack.push({ route: route, handle: fn });

  return this;
};

説明のため、後半以外を省略しています。最終的に引数でもらった値を、アプリケーションの stackroutehandle をキーに持つオブジェクトとして push しています。途中のコードを見ていきましょう。

まず最初に可変長引数に対応するための処理を実行しています。route が指定されていなければ / で初期化します。

  // default route to '/'
  if ('string' != typeof route) {
    fn = route;
    route = '/';
  }

以下の二行はどちらも同じ意味として解釈されます。

app.use(connect.middleware);
app.use('/' connect.middleware);

次に、fn.handle が関数だった場合。

  // wrap sub-apps
  if ('function' == typeof fn.handle) {
    var server = fn;
    fn.route = route;
    fn = function(req, res, next){
      server.handle(req, res, next);
    };
  }

引数に Connect アプリケーションを受けた場合になります。Connect アプリケーションの handle() メソッドに処理を委譲します。

var app = connect()
  , app1 = connect()
  , app2 = connect();

app
  .use('/app1', app1)
  .use('/app2', app2);

次は、fn が Node.js の標準モジュールが提供する http.Server のインスタンスだった場合。

  // wrap vanilla http.Servers
  if (fn instanceof http.Server) {
    fn = fn.listeners('request')[0];
  }

request イベントの先頭のリスナーをハンドラとして登録します。

var app = connect()
  , httpServer = require('http').createServer();

httpServer.on('request', function(req, res) {
  // do something
});

app.use('/http-server', httpServer);

最後に route の正規化を行なっています。route の最後がスラッシュで終わっている場合は、末尾のスラッシュを取り除きます。

  // strip trailing slash
  if ('/' == route[route.length - 1]) {
    route = route.slice(0, -1);
  }

/foo/bar//foo/bar に切り詰められ、最終的に stackpush されます。最後にメソッドチェイン用の this を返して、use() は終了です。

まとめます。

  • ミドルウェア
  • Connect アプリケーション
  • HTTP サーバ

これらをミドルウェアとして登録できることがわかりました。


proto.handle()

実際のリクエストを処理する関数になります。ここの動作が理解出来れば Connect のほとんどを理解したと考えてもよさそうです。

すべてを引用すると長いので、フローが分かる範囲で省略してしまいます。

app.handle = function(req, res, out) {
  var stack = this.stack
    , index = 0;

  function next(err) {
    var layer;
    
    // next callback
    layer = stack[index++];

    // all done
    if (!layer || res.headerSent) {
      // delegate to parent
      if (out) return out(err);

      // unhandled error
      if (err) {
        // 500
        res.end();
      } else {
        // 404
        res.end();
      }
      return;
    }

    try {
      var arity = layer.handle.length;
      if (err) {
        if (arity === 4) {
          layer.handle(err, req, res, next);
        } else {
          next(err);
        }
      } else if (arity < 4) {
        layer.handle(req, res, next);
      } else {
        next();
      }
    } catch (e) {
      next(e);
    }
  }
  next();
};

handle() 関数のスコープ内に next() という関数を定義して、再帰呼び出しをしています。実際には細かい分岐もありますが next() 関数の処理は以下のとおり。

  1. stack から use() で登録したミドルウェアを取り出す
  2. stack が空、もしくはヘッダが送信されていたら終了処理
  3. そうでなければミドルウェアを実行し、next() を再帰呼び出し
  4. ミドルウェア実行時にエラーがあれば next() にエラーを渡して再帰呼び出し

それでは handle 関数の中身を読んで行きましょう。

まずは変数の初期化処理。

  • stack (this.stack) のエイリアス
  • fqdn: :// が含まれる URL かどうかのフラグ
  • removed: URL 復元用のマウントパスを保存しておく変数
  • slashAdded: 謎です (!!fqdn === true の場合に関連した処理のようです)
  • index: ミドルウェアの stack の添字
app.handle = function(req, res, out) {
  var stack = this.stack
    , fqdn = ~req.url.indexOf('://')
    , removed = ''
    , slashAdded = false
    , index = 0;
  
  /** snip **/
};

次に。スコープ内の next() 関数の定義ですが、読み飛ばします。

app.handle = function(req, res, out) {
  function next(err) {
    /** snip **/
  }
  next();
};

読み飛ばすと、next() (一回目) の呼び出しがあります。というわけで、next() を見ていきます。

  function next(err) {
    var layer, path, status, c;

    if (slashAdded) {
      req.url = req.url.substr(1);
      slashAdded = false;
    }

    req.url = removed + req.url;
    req.originalUrl = req.originalUrl || req.url;
    removed = '';

    // next callback
    layer = stack[index++];

    // all done
    if (!layer || res.headerSent) {
      /** snip **/
      return;
    }

    try {
      /** snip **/
    } catch (e) {
      next(e);
    }
  }

変数の初期化処理です。

  • 引数の err は初回の時点で undefined (ミドルウェア内でエラーが発生しなければ undefined のまま)
  • if (slashAdded) のブロックは初回は slashAdded == false のためスキップ
  • req.url への代入は remove が空文字列のため値に変化なし
  • req.originalUrl への代入は req.originalUrl は初回 undefined なので req.url が代入される
  • removed は初期値が空文字列のため実質変化なし
  • layer に取り出したミドルウェアを代入 (indexstack[0] が評価されてからインクリメント)

初回の next() のため、実質変化がない処理が幾つかあります。ミドルウェアが一つ以上登録されていると仮定して、次は try-catch のブロックを見ていきます。

    try {
      path = utils.parseUrl(req).pathname;
      if (undefined == path) path = '/';

pathname を取り出して path に代入します。 (http://example.org/foo/bar -> /for/bar)

utils.parseUrl() は Node 標準の path モジュールの parse() と同様の結果を返しますが、高速化のため実行結果をキャッシュするようになっています。

      // skip this layer if the route doesn't match.
      if (0 != path.indexOf(layer.route)) return next(err);

      c = path[layer.route.length];
      if (c && '/' != c && '.' != c) return next(err);

route にマッチするかどうかをチェックします。

  • route == '/foo/bar' の場合
    • /foo -> マッチしない
    • /baz -> マッチしない
    • /foo/bar -> マッチする
    • /for/bar/baz -> マッチする
    • /foo/bar.json -> マッチする

マッチしなかった場合は next() にエラーオブジェクトをプロパゲーションして次のミドルウェアの処理にスキップします。

      // Call the layer handler
      // Trim off the part of the url that matches the route
      removed = layer.route;
      req.url = req.url.substr(removed.length);

removedlayer.route を格納しておき、req.url に layer.route 分切り詰めた URL を代入します。この処理により、ミドルウェア内部の処理をマウントのパスに依存しないようにすることが出来ます。

  • route == '/foo/bar' && req.url == '/foo/bar/baz.json' の場合
    • removed/foo/bar を代入
    • req.url/bar.json を代入
      // Ensure leading slash
      if (!fqdn && '/' != req.url[0]) {
        req.url = '/' + req.url;
        slashAdded = true;
      }

ごめんなさい!謎です。 (req.url:// が含まれるケースを当たれていません)

      debug('%s', layer.handle.name || 'anonymous');
      var arity = layer.handle.length;
      if (err) {
        if (arity === 4) {
          layer.handle(err, req, res, next);
        } else {
          next(err);
        }
      } else if (arity < 4) {
        layer.handle(req, res, next);
      } else {
        next();
      }
    }

いよいよミドルウェアの呼び出しです。

  • 前回のミドルウェアでエラーがあった場合
    • 引数を 4 つ取るミドルウェア (=エラーハンドラ) の場合、ミドルウェアに処理を委譲
    • next はミドルウェア内部で実行してもらう (コールバック)
    • そうでない場合、next(err) でエラーオブジェクトをプロパゲーションして次の処理ミドルウェアにスキップ
  • 前回のミドルウェアでエラーが無かった場合
    • 引数が 4 つ未満のミドルウェアの場合、ミドルウェアに処理を委譲
    • next はミドルウェア内部で実行してもらう (コールバック)
    • そうでない場合、next() で次のミドルウェアの処理を実行

場合分けで書き出すと上記のようになります。この try のブロックで、エラーオブジェクトがスローされた場合は、next(e) でエラーオブジェクトをプロパゲーションします。

    try {
      /** snip **/
    } catch (e) {
      next(e);
    }

これによりミドルウェア内で next(new Error()) もしくは throw new Error() することでエラーをプロパゲーションすることが可能になります。

まとめます。

  • ミドルウェアを登録順に実行
  • ミドルウェア内部でエラーが起きた場合、以降エラーハンドラ以外のミドルウェアは実行されない
  • ミドルウェア内でエラーを発生させるには、引数で受けた next 関数にエラーオブジェクトを渡して実行するか throw する
  • すべてのミドルウェアを取り出すか、req.headerSenttrue に変化した場合終了処理

ミドルウェア内で res.writeHeader() でヘッダを出力するか、 res.write()res.end() でコンテンツを送出することで req.headerSenttrue に変化します。つまり、ミドルウェア内でコンテンツを返すと、next() の再帰呼び出しが終了し、後続のミドルウェアの処理がスキップされます。


では、終了処理のブロックを見て行きましょう。

    // all done
    if (!layer || res.headerSent) {
      // delegate to parent
      if (out) return out(err);

proto.handle() の第三引数が関数だった場合、エラーオブジェクトをその関数に渡して実行します。(どういったシチュエーションなのでしょう?)

      // unhandled error
      if (err) {
        // default to 500
        if (res.statusCode < 400) res.statusCode = 500;
        debug('default %s', res.statusCode);

        // respect err.status
        if (err.status) res.statusCode = err.status;

        // production gets a basic error message
        var msg = 'production' == env
          ? http.STATUS_CODES[res.statusCode]
          : err.stack || err.toString();

        // log to stderr in a non-test env
        if ('test' != env) console.error(err.stack || err.toString());
        if (res.headerSent) return req.socket.destroy();
        res.setHeader('Content-Type', 'text/plain');
        res.setHeader('Content-Length', Buffer.byteLength(msg));
        if ('HEAD' == req.method) return res.end();
        res.end(msg);

エラーハンドラが設定されていなかった、またはミドルウェアがエラーをハンドリングしても res.write() などでコンテンツを返さなかった場合の処理です。

  • res.statusCode が 400 未満の場合は 500 に設定
    • エラーオブジェクトに status プロパティがある場合はそちらを優先
  • NODE_ENV が production の場合はステータスコードに対応するメッセージに設定
  • NODE_ENV が production 以外の場合はエラーオブジェクトをメッセージに設定
  • NODE_ENV が test ではない場合、標準エラー出力に設定したメッセージを表示
    • テスティングフレームワーク側でハンドリングするのだと思います
  • HEAD メソッドの時はレスポンスボディを空で送信
  • そうでない場合は環境ごとに設定したメッセージを送信
      } else {
        debug('default 404');
        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/plain');
        if ('HEAD' == req.method) return res.end();
        res.end('Cannot ' + req.method + ' ' + utils.escape(req.originalUrl));
      }

エラーは発生しなかったが、いずれのミドルウェアもレスポンスを返さなかった場合、404 として扱います。

      return;
    }

return で関数を抜けてリクエストのハンドリングを終了します。


proto.listen()

HTTP のリクエストをリッスンするためのメソッドです。

app.listen = function(){
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};

アプリケーション自身を、新たに作成した server に登録し、引数をそのまま渡して処理を委譲します。http.createServer の引数は request イベントのリスナーとして登録されます。以下のコードは Connect の外側から listen() していますがやっていることは一緒です。

var app = connect()
  , server = http.createServer();
server.on('request', app);
server.listen(3000);

アプリケーションそのものがリスナーとして登録される振る舞いが不自然にも見えますが、createServer の実装を思い起こすと、納得できると思います。Connect アプリケーション自身の listen() を使うと HTTP サーバになりますが、HTTPS サーバとして起動したい場合は、以下のようにすれば OK です。

var app = connect()
  , server = require('https').createServer({ key: fs.readFileSync('foo.key'), cert: fs.readFileSync('bar.crt') }, app);
server.listen(443);

以上です。


リーディングの資料はひとまずここまでとなります。参加者の皆様には事前にミドルウェアの実装を読んで頂いて発表していく形式で進めようと考えています。

続き

続きは勉強会にて!

This was posted 1 year ago. It has 1 note and 0 comments.

Connect コードリーディング: 初期化編 (詠む会向け資料)

これは Connect (2.3.0) を詠む会向けの資料です。読む会の詳細についてはこちらを御覧ください。


前回は Connect の主要な機能についておさらいをしました。いよいよ今回はソースコードを読んでいきます。

ファイルツリー

以下のファイルツリーを参考に、各ファイルの説明をします。リーディングに不要と思われるディレクトリ (node_modules など) は省いておりますのでご了承ください。

♪ tree .
.
├── index.js /* エントリポイント */
├── lib /* ライブラリ本体 */
│   ├── cache.js /* 静的ファイルをキャッシュするためのヘルパ */
│   ├── connect.js /* 初期化処理 */
│   ├── index.js /* ドキュメント生成用のダミーファイル */
│   ├── middleware /* ビルトインのミドルウェアを格納 (詳細は後述) */
│   │   ├── basicAuth.js
│   │   ├── bodyParser.js
│   │   ├── compress.js
│   │   ├── cookieParser.js
│   │   ├── cookieSession.js
│   │   ├── csrf.js
│   │   ├── directory.js
│   │   ├── errorHandler.js
│   │   ├── favicon.js
│   │   ├── json.js
│   │   ├── limit.js
│   │   ├── logger.js
│   │   ├── methodOverride.js
│   │   ├── multipart.js
│   │   ├── query.js
│   │   ├── responseTime.js
│   │   ├── session
│   │   │   ├── cookie.js
│   │   │   ├── memory.js
│   │   │   ├── session.js
│   │   │   └── store.js
│   │   ├── session.js
│   │   ├── static.js
│   │   ├── staticCache.js
│   │   ├── urlencoded.js
│   │   └── vhost.js
│   ├── patch.js /* Node.js 本体へのパッチ */
│   ├── proto.js /* Connect のアプリケーション本体 */
│   └── utils.js /* 内部ユーティリティ関数集 */
└── package.json /* パッケージ情報 */

初期化のシーケンス

package.json

Connect にかぎらず、npm などでインストールしたモジュールを呼び出す際 require('module-name'); を実行します。require() 時に実行されるファイル (エントリポイント) は、package.json に記述されています。

21 行目の main プロパティに記述されているファイルがエントリポイントになります。Connect の場合は index.js になります。では index.js を見てみましょう。

module.exports = process.env.CONNECT_COV
  ? require('./lib-cov/connect')
  : require('./lib/connect');

環境変数 CONNECT_COV を調べて require するファイルを切り替えています。通常は ./lib/connect が require されます。./lib-cov/connect は Mocha でコードカバレッジを取得するためのコードのため、今回は割愛します。

./lib/connect.js

connect の本体のコードです。

26 行目、module.exports に createServer を代入しています。require('connect') すると createServer が帰ってくるわけです。

// expose createServer() as the module

exports = module.exports = createServer;

createServer を見る前に、require 時に実行される初期化処理があるので、そちらを見て行きましょう。

/**
 * Support old `.createServer()` method.
 */

createServer.createServer = createServer;

connect() のエイリアスを connect.createServer() に貼っているだけです。Connect 1.x 向けに書かれたアプリケーションのためのコードになります。

次に、ビルトインのミドルウェアを登録する処理になります。

/**
 * Auto-load bundled middleware with getters.
 */

fs.readdirSync(__dirname + '/middleware').forEach(function(filename){
  if (!/\.js$/.test(filename)) return;
  var name = basename(filename, '.js');
  function load(){ return require('./middleware/' + name); }
  exports.middleware.__defineGetter__(name, load);
  exports.__defineGetter__(name, load);
});

./lib/middleware/*.js 下のファイルを connect の getter として登録しています。この処理が実行されると、connect.static などが利用できるようになります。さらにエイリアスをconnect.middlware.static に貼っています。

一点注目したい点として、モジュールの遅延ロードを行なっています。getter へのアクセスがあった時点で require が実行されるようになっています。これで require('connect') 時のコストを最小に抑えられるわけです。

初期化処理はここまでです。それでは createServer を見て行きましょう。

/**
 * Create a new connect server.
 *
 * @return {Function}
 * @api public
 */

function createServer() {
  function app(req, res){ app.handle(req, res); }
  utils.merge(app, proto);
  utils.merge(app, EventEmitter.prototype);
  app.route = '/';
  app.stack = [];
  for (var i = 0; i < arguments.length; ++i) {
    app.use(arguments[i]);
  }
  return app;
};

createServer() のスコープ内で req, res という引数をとる app という関数を作ってリターンしています。appproto (./lib/proto.js) と EventEmitter を mix-in (継承) し、routestack を初期化しています。

引数をとった場合は、引数を app.use() に渡して処理を委譲しています。connect() の引数でもミドルウェアを登録できるようになっています。

var connect = require('connect');
 , app = connect(/* args */);

connect()createServer() を実行し、app がリターンされるところまで行きました。最初は空っぽの関数だった app には以下のプロパティが追加されています。

  • app.handle(req, res)
  • app.route = ''
  • app.stack = []
  • app.use()

次回は ./lib/proto.js を読んで、追加されたプロパティの処理を読んで行きましょう。

続き

This was posted 1 year ago. It has 2 notes and 0 comments.

SCSS (Sass) の親参照セレクタ (&) は後置でも OK

SCSS (Scss) を使っていて便利なのが、親参照セレクタ。

a {
  text-decoration: none;
  &:hover {
    text-decoration: underline;
  }
}

こんな風に解釈される。

a {
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

これはリファレンスを見ると書いてある使い方なので、SCSS ユーザには馴染み深いと思う。今度は & マークの前にセレクタを書いてみようと思う。

a {
  text-decoration: none;
  #box &:hover {
    text-decoration: underline;
  }
}

意図通りに解釈された。

a {
  text-decoration: none;
}
#box a:hover {
  text-decoration: underline;
}

HTML5 Boilerplate のコンディショナルセレクタを使っている場合。

<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
<!--[if IE 7]>    <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]-->
<!--[if IE 8]>    <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->

↑ こういうやつ。

.box {
  opacity: 0;
  &:hover {
    opacity: 1;
  }
  .lt-ie9 & {
    visibility: hidden;
    &:hover {
      visibility: visible;
    }
  }
}

こういうふうに書けとけば、こんなふうに解釈される。

.box {
  opacity: 0;
}
.box:hover {
  opacity: 1;
}
.lt-ie9 .box {
  visibility: hidden;
}
.lt-ie9 .box:hover {
  visibility: visible;
}

Modernizr でも同様。

.box {
  background-color: rgba(0, 0, 0, .7);
  &:hover {
    background-color: rgba(0, 0, 0, 1);
  }
  .no-rgba & {
    background-color: #333;
    &:hover {
      background-color: #000;
    }
  }
}

こんなふうに書ける。フォールバック系のセレクタを本来のセレクタに関連付けて書けるので、コードの見通しが良くなると思う。

This was posted 1 year ago. It has 3 notes and 0 comments.

Connect とは (詠む会向け資料)

これは Connect (2.3.0) を詠む会向けの資料です。読む会の詳細についてはこちらを御覧ください。


ソースコードリーディングに先立って、Connect の機能をおさらいしましょう。

Connect とは

Connect は Node.js 向けの拡張可能な HTTP サーバフレームワークです。ミドルウェアと呼ばれるプラグイン機構をつかって HTTP サーバを拡張することが出来ます。

Connect のソースコードは GitHub にてホスティングされています。

使い方

もっともベーシックな使い方を紹介します。

// connect モジュール (関数) を呼び出し
var connect = require('connect');

// connect 関数を実行すると HTTP サーバが生成されます
// use 関数を使ってミドルウェアを HTTP サーバに登録します
var app = connect()
  .use(connect.favicon())  // /favicon.ico へのリクエストをハンドル
  .use(connect.logger())  // アクセス時にログを標準出力する
  .use(connect.static('public'));  // 静的ファイルへのリクエストをハンドル

// listen 関数で任意のポートで HTTP をリッスンします
app.listen(3000);

これで、favicon.ico へのリクエストを処理しつつ public ディレクトリ下の静的ファイルへのリクエストを処理しつつ、ログを標準出力する HTTP サーバが出来上がりました。

次に、マウントと呼ばれる機能を使用してみます。

var connect = require('connect');

var app = connect()
  .use(connect.logger())  // 1. アクセス時にログを標準出力する
  .use('/admin', connect.basicAuth('admin', 'pass')) // 2. /admin 以下には BASIC 認証を設定する
  .use('/static', connect.static('public'));  // 3. /static 以下へのリクエストは public 以下の静的ファイルをレスポンスする

app.listen(3000);

2, 3 については use() に引数を二つ渡しています。一つ目は URL のエントリポイント、二つ目はミドルウェアになります。

2 は /admin 以下の URL (/admin/foo/bar や /admin.json など) に基本認証を設定しています。3 は /static 以下の URL にリクエストが来た場合、対応する静的ファイル (GET /static/foo.html -> /public/foo.html) をレスポンスします。

ミドルウェアって何者?

ミドルウェアと呼ばれるものを使って Connect に機能を加えていくということがわかりました。さて、このミドルウェアと呼ばれるものの実態は何なのでしょう。

connect.logger() を実行すると何が起きるでしょう。関数が帰ってくるのです。すなわち、ミドルウェアは関数にすぎないのです。

以下の例を見てみましょう。

var connect = require('connect');

var app = connect();

// foo! を標準出力する
app.use(function(req, res, next) {
  console.log('foo!');
  next();
});

// hello とレスポンスする
app.use(function(req, res) {
  res.end('hello');
});

app.listen(3000);

use() に直接関数を書いてみます。アクセスがあるたびに、foo! と標準出力し、hello という文字列をレスポンスする HTTP サーバが出来上がりました。

関数の終わりに引数にもらった next を実行すると、次のミドルウェアが処理されます。レスポンスを行うミドルウェアは next を引数に取りません。

ミドルウェアの種類

勘が鋭い人はもう気がついているかもしれません。ミドルウェアには実際のレスポンスはしないフィルタと呼ばれるものと、レスポンスを行うプロバイダと呼ばれるものが存在します。

connect.logger() などのミドルウェアはフィルタ、connect.statc() などはプロバイダに分類されます。

更にもう一種類、エラーを処理するためのミドルウェアも存在します。

var connect = require('connect');

var app = connect();

app.use(function(req, res) {
  // 二回に一回の確率でエラーオブジェクトをスローする
  if (Math.round(Math.random())) {
    throw new Error('OMG!');
  }
  res.end('Peace!');
});

app.use(function(err, req, res, next) {
  res.statusCode = 500;
  res.end('An error occurred: ' + err.message);
});

エラーが発生したときは An error occurred: OMG! とレスポンスされ、そうでない場合は、Peace! とレスポンスされる HTTP サーバです。引数を 4 つ取るミドルウェアはエラーハンドラと呼ばれ、エラーが発生した時に呼び出されます。

まとめると、ミドルウェアにはフィルタプロバイダエラーハンドラの三つに分類することができます。

おまけのセクション

Connect のトリビアルな使用例を紹介しておきます。

また別のミドルウェアの登録方法

var connect = require('connect');

var app = connect(
    connect.favicon()
  , connect.logger()
  , connect.static('public')
);

app.listen(3000);

connect() の引数からミドルウェアを登録することが出来ます。use() を使った時と効果は同じです。

ミドルウェア以外のものを use() する

use() はミドルウェア以外に、Connect アプリケーションと HTTPServer を登録することが可能です。

var connect = require('connect');

var app = connect();

var hello = connect()
  .use(function(req, res) {
    res.end('hello');
  });

var bye = require('http').createServer();

bye.on('request', function(req, res) {
  res.end('bye');
});

app.use('/hello', hello);  // connect のアプリケーションをマウント
app.use('/bye', bye); // Node 標準の HTTPServer をマウント

app.listen(3000);

/hello 以下にアクセスすると hello, /bye 以下にアクセスすると bye とレスポンスする HTTP サーバが出来上がりました。


次回は、これらの機能の実装について解説していきます。

続き

This was posted 1 year ago. It has 1 note and 0 comments.