JavaScriptでヒアドキュメントや簡易テンプレート

JavaScriptでもヒアドキュメントやテンプレートが使えれば便利だし、それらを組み合わせてコードの塊(スニペット)にできれば便利だよね。
ってことで、既に同様のアイデアはゴロゴロしてそうなんですが、自分用にざざっと叩き台を書いてみました。

DEMO

http://pigs.sourceforge.jp/blog/20100505/test/demo.snippet/uu.snippet.htm

<!doctype html><html><head><meta charset="utf-8" />
<title></title>
<script src="src/uupaa.js"></script>
<script src="src/uu.snippet.js"></script>
<script>
function xboot(uu) {
    var arg = {
        PHPStyleArgs: "PHPStyleArgs",
        RubyStyleArgs: "RubyStyleArgs",
        DualBraceStyle: 'style="color: green; background-color: #ccc"',
        key: "implement template.key",
        value: "implement template.value",
        list: {
            length: 3,
            key: ["key1", "key2", "key3", "key4"],
            value: ["value1", "value2", "value3", "value4"]
        }
    };

    alert( uu.snippet("snippet1", arg) );
    alert( uu.snippet("snippet2", arg) );
    uu.body( uu.snippet("snippet3", arg) );

    uu.body( uu.snippet("snippet4", arg) );
}
</script>
</head><body></body></html>

<script id="snippet1" type="text/html">  // --- PHP style text here document ---
    return <<<EOS
        "The PHP" style here document.
        text only; <div> does't work.
        implement {$arg.PHPStyleArgs} args
EOS;
</script>

<script id="snippet2" type="text/html"> // --- Ruby style text here document ---
    return <<EOS
        'The Ruby' text here document.
        text only; <div> does't work.
        implement #{arg.RubyStyleArgs} args
EOS
</script>

<script id="snippet3" type="text/html"> // --- text here document ---
    return <text>
        text here document.
        text only; <div> does't work.
        implement {{arg.DualBraceStyleArgs}} args
</text>
</script>

<script id="snippet4" type="text/html"> // --- complex here document ---
    return <>
    <div {{arg.DualBraceStyle}}>
        <ul>
            <each arg.list>
                <li {{arg.ListStyle}}>{{key}}</li>
                <li>{{value}}</li>
            </each>
        </ul>
    </div>
</>
</script>

できること

  • uu.snippet(id, arg) を実行すると <script id="スニペットID" type="text/html">JavaScriptExpression</script> を読み込み、引数argを与えてコードを評価します。
  • 引数には Hash や Array など好きな値を渡せます。スニペット内では、引数は "arg" という名前で参照できます。
  • PHPスタイル, Rubyスタイル, <text>...</text> スタイルでヒアドキュメントを定義できます。
  • <>...</> で、DOMノードを生成できます
    • <each ident> ... </each> で囲んだ範囲は、ident.length の回数だけ繰り返し評価/置換します。

uu.snippet.js

// === snippet ===
//{{{!depend uu
//}}}!depend

uu.snippet || (function(uu) {

uu.snippet = uusnippet;

// uu.snippet - evaluate snippet
function uusnippet(id,    // @param String: snippet id. <script id="...">
                   arg) { // @param Mix(= void): arg
                          // @return String/Mix:
    var ld = uusnippet, js = ld.stock[id] || "", node;

    if (!js) {
        node = uu.id(id);

        if (!node) {
            return "";
        }
        js = node.text.replace(ld.toNewLine, "\n").
            replace(ld.PHPStyleHereDocument, function(all, eos, match) {
                return '"' + normalize(match).replace(ld.DualBrace, implant).
                             replace(ld.PHPBrace, implant) + '"';
            }).replace(ld.RubyStyleHereDocument, function(all, eos, match) {
                return '"' + normalize(match).replace(ld.DualBrace, implant).
                             replace(ld.RubyBrace, implant) + '"';
            }).replace(ld.TextHereDocument, function(all, match) {
                return '"' + normalize(match).replace(ld.DualBrace, implant)
                           + '"';
            }).replace(ld.ComplexHereDocument, function(all, match) {
                match = normalize(match, 1).
                        replace(ld.eachBlock, function(all, hash, block) {
                    return '" + uu.snippet.each(' + hash + ',"' +
                                block.replace(uusnippet.DualBrace, toEachHash) +
                                '") + "';
                }).replace(ld.DualBrace, implant);
                return 'uu.node.bulk("' + match + '");';
            }).replace(ld.trimNewLine, "");

        uusnippet.stock[id] = js;
    }
    return js ? (new Function("arg", js))(arg) : "";
}
uusnippet.stock = {}; // { id: JavaScriptExpression, ... }
uusnippet.toNewLine = /\r\n|\r|\n/g;
uusnippet.PHPStyleHereDocument = /<<<(\w+)\n([\s\S]*?)^\1;$/gm;
uusnippet.RubyStyleHereDocument = /<<(\w+)\n([\s\S]*?)^\1$/gm;
uusnippet.TextHereDocument = /<text>\n([\s\S]*?)^<\/text>$/gm;
uusnippet.ComplexHereDocument = /<>\n([\s\S]*?)^<\/>$/gm; // {{{
uusnippet.PHPBrace = /\{\$([^\}]+)\}/g;
uusnippet.RubyBrace = /#\{([^\}]+)\}/g;
uusnippet.DualBrace = /\{\{([^\}]+)\}\}/g;
uusnippet.EachBrace = /\{\(([^\)]+)\)\}/g;
uusnippet.trimNewLine = /^\s*\n\s*|\s*\n\s*$/g;
uusnippet.trimMultiLine = /^\s+|\s+$/gm;
uusnippet.trim = /^\s+|\s+$/g;
uusnippet.eachBlock = /<each ([^>]+)>([\s\S]*?)<\/each>/;
uusnippet.escapeQuote = /("|')/g;
uusnippet.escapeNewLine = /\n/g;
uusnippet.arg = /^arg\./;
uusnippet.each = each;

// uu.snippet.each
function each(hash,       // @param Hash: { length: 3, prop1: [value, ...], prop2: [value, ...], ... }
              fragment) { // @param String: "<li>{(prop1)}</li><li>{(prop2)}</li>"
                          // @return String:
    var i = 0, iz = hash.length, block = [];

    for (; i < iz; ++i) {
        block.push(fragment.replace(uusnippet.EachBrace, function(all, ident) {
            return hash[ident][i];
        }));
    }
    return block.join("");
}

// inner -
function implant(all, ident) {
    return '"+'  + ident + '+"';
}

// inner - "{{ident}}" -> "{(ident)}"
function toEachHash(all, ident) {
    if (uusnippet.arg.test(ident)) {
        return '"+'  + ident + '+"';
    }
    return '{(' + ident + ')}';
}

// inner -
function normalize(str, multiLine) {
    return str.replace(multiLine ? uusnippet.trimMultiLine
                                 : uusnippet.trim, "").
               replace(uusnippet.escapeQuote, "\\$1").
               replace(uusnippet.escapeNewLine, "\\n");
}

})(uu);

もっとお手軽に

もっとお手軽な方法が欲しい方は、各行末にバックスラッシュ(\)を書けば、ヒアドキュメントの代用になります。

<!doctype html><html><head><title></title><script>

var here = "text here document.\
        text only;\
        <div> does't work.";

alert(here);

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