プログラマブログ

by wacul

menu

2014.07.18Jadeのmixinでこころぴょんぴょん

Jadeにはmixinという再利用可能なオレオレ要素を作る機能があります。 mixinの機能についての解説はすでにやられてる方がいるので(かずぽんブログ • Jadeで便利なmixinの機能)ここではmixinがどのようにコンパイルされてるのかを解説しようかと思います。ちなみにjade-lang.com/demo/上でリアルタイムにコンパイル結果を確認することができます。


さっそく本題に入ります。以下のJadeのコード例を見て下さい。なんだかごてごてしているように見えますが、ここでは基本的なmixinの機能を全て使っています(もし記法がわからなければ上記ブログを参照することをおすすめします)。

1
2
3
4
5
6
mixin foo(arg)
  div foooooo #{arg}!
  div(class=attributes.class id=attributes.id)
    block

+foo("こんにちは").hoge#fuga(attr1="js") hello

このファイルをコンパイルすると以下のようなコードが生成されます(幅の都合上折り返しています)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function template(locals) {
    var buf = [];
    var jade_mixins = {};
    jade_mixins["foo"] = function(arg) {
        var block = this && this.block,
            attributes = this && this.attributes || {};
        buf.push(
            '<div>foooooo ' +
            jade.escape((jade.interp = arg) == null ? "" : jade.interp) +
            '!</div><div' +
            jade.attr("id", attributes.id, true, false) +
            jade.cls([ attributes.class ], [ true ]) +
            ">"
        );
        block && block();
        buf.push("</div>");
    };
    jade_mixins["foo"].call({
        block: function() {
            buf.push("hello");
        },
        attributes: {
            id: "fuga",
            attr1: "js",
            "class": "hoge"
        }
    }, "こんにちは");
    return buf.join("");
}

HTML出力は以下のようになります。

1
2
3
<div>foooooo こんにちは!</div>
<div id="fuga" class="hoge">hello
</div>

コンパイルされたコードについて分けながら説明します。

まずはtemplate関数全体から。冒頭のlocalsはテンプレートに流し込むデータです(ここでは使っていませんが)。bufにはhtmlタグなどをテキストとして格納しておき、連結して最終的な結果として返します。 mixinはjade_mixinsに関数として格納されます。

1
2
3
4
5
6
function template(locals) {
    var buf = [];
    var jade_mixins = {};
    /* ... 中身 ... */
    return buf.join("");
}

foo mixinの呼び出し部分です(定義は後回しにします)。+foo("こんにちは").hoge#fuga(attr1="js") helloの 最初の"こんにちは"が引数、classとidとattr1がattributesに格納されています。foo mixinの中身であるhelloは block関数の中でbuf.push("hello")することで所定の位置にhelloの文字列が表示されることになります。 blockとattributesはthisコンテキストとして関数に渡されます。

1
2
3
4
5
6
7
8
9
10
jade_mixins["foo"].call({
    block: function() {
        buf.push("hello");
    },
    attributes: {
        id: "fuga",
        attr1: "js",
        "class": "hoge"
    }
}, "こんにちは");

foo mixinの実際の定義部分です。blockとattributesはこの関数の中でローカル変数として展開されます。 不気味なattributesはこんな感じで定義されてたわけです。div "foooooo #{arg}!"div(class=attributes.class id=attributes.id) の部分は特に問題無いですね。jadeでblockを置いていた行はblock && block();になっています。単純にblock関数があれば実行されるだけで、 ここではbufに"hello"を追加していることになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
jade_mixins["foo"] = function(arg) {
    var block = this && this.block,
        attributes = this && this.attributes || {};
    buf.push(
        '<div>foooooo ' +
        jade.escape((jade.interp = arg) == null ? "" : jade.interp) +
        '!</div><div' +
        jade.attr("id", attributes.id, true, false) +
        jade.cls([ attributes.class ], [ true ]) +
        ">"
    );
    block && block();
    buf.push("</div>");
};

まとめ

mixinがどのようにコンパイルされるかを解説しました。 attributesがどこから現れたのかとか、blockが実際に何をやっているかを知ることで mixinを書くときに迷うことが少なくなるかと思います。 その他、mixin内ではJavaScriptを書ける上にbufにアクセスしてごにょごにょできるのですが、 そういうdirty hackはgithubで検索するとわんさか出てくるので興味ある人は調べてみてください。

参考

この記事を書いた人kato

加藤です。JavaScriptに興味があります。

waculでは、プログラマを募集しています。

現在はプロダクトとして、課題発見から改善提案まで自動で行うWeb改善プラットフォーム「AIアナリスト」を開発中です。

waculの採用情報へ

ページトップへ