Riot.js ソースコード完全解説

追記・「[Riot.js ソースコード完全解説 v3対応版](http://qiita.com/cognitom/items/df0b759f244f8b50060e)」を公開しました。(2016/7/26)

-----

[2.0.7時点のコード](https://github.com/muut/riotjs/tree/c1697feab1f0e1b72e5a87a30f44e817ffe6111b)を読みます。

Qiitaの記事としては、ボリューム感に溢れてますが、ひとつのライブラリとしては驚異的に短いです。各所で指摘されているように、結構サボった実装になっています。ただ、なんだかそれを指摘するのすら野暮という感じの、単純なロジックなので、優しい気持ちでぜひ。

読み解くにあたり、いくつか特徴を挙げておきます。

- 正規表現を多用する (かなりイージー)
- DOMのパースは`innerHTML`頼り
- CoffeeScriptやJadeなどのコンパイラは含まない
- それ以外のライブラリ依存なし
- セミコロンが嫌いらしい

Riot.jsは6つのスクリプトに分かれていますが、★印の3つが基本的な部分です。この記事でも、この3つのみを扱います。

 | ファイル | 機能
:-- | :-- | :--
★ | [compiler.js](https://github.com/muut/riotjs/blob/master/lib/compiler.js) | tagファイルから`riot.tag(html, script)`の形式に
★ | [tmpl.js](https://github.com/muut/riotjs/blob/master/lib/tmpl.js) | テンプレート生成
★ | [view.js](https://github.com/muut/riotjs/blob/master/lib/view.js) | ビューの生成と更新
 | [observable.js](https://github.com/muut/riotjs/blob/master/lib/observable.js) | メッセージ通信 (追加機能)
 | [router.js](https://github.com/muut/riotjs/blob/master/lib/router.js) | ルーティング (追加機能)
 | [cli.js](https://github.com/muut/riotjs/blob/master/lib/cli.js) | CLIから compiler.js を呼び出すためのラッパー

Riot.jsでは「tagファイル」というほぼHTMLなテンプレートとスクリプトが一体化したものを扱います。まずは、[このあたり](https://muut.com/riotjs/guide/)をじっくり眺めて、tagファイルに慣れておいてください。

処理の流れとしては、次のようになるので、説明もこの順番で進めようと思います。

- tagファイル
- コンパイル `riot.tag(html, script)`
- テンプレート生成
- ビュー生成と更新

コードは、ほぼ全文を載せていますが、説明のために端折ったり、コメントを抜いている部分があります。元コードの全体を一度眺めた後に、並べつつ読み進めると理解が早いかもしれません。

> ♦ 最後、眠気に負けて、一部、ビュー周りの説明がしきれていません。あと、図表も足したいところ。後日追記します。(2015/2/7)

## コンパイラ: compiler.js 全284行

このファイルが、Node.jsなどから使う場合の`main`になります。パブリックなメソッドは、`compile()`と`compileHtml()`のふたつだけ。

```javascript
riot = require('riot')

riot.compile(...);// compile() メソッドにマップ
riot.html(...);// compileHtml() メソッドにマップ
```

呼び出しの順序を追うと...

- `compile(riot_tag, opts)`
    - `compileTemplate(lang, html)`
        - `jade(html)`
    - `compileJS(js, opts, type)`
        - `riotjs(js)` or `coffee(js)` or `es6(js)` ...


以下、コードを追っていきます。一緒に[コード](https://github.com/muut/riotjs/blob/c1697feab1f0e1b72e5a87a30f44e817ffe6111b/lib/compiler.js)を見つつ、どうぞ。

### 真偽値属性 (boolean attribute)

`checked`や`readonly`などの属性については、文字列ではなく真偽値として扱います。IE8対応のため、`__`をプリフィックス。

```javascript
//L5
var BOOL_ATTR = ('allowfullscreen,async,autofocus,autoplay,checked,...<略>').split(',')

//L46
html = html.replace(/([\w\-]+)=["'](\{[^\}]+\})["']/g, function(full, name, expr) {
  if (BOOL_ATTR.indexOf(name.toLowerCase()) >= 0) name = '__' + name
  return name + '="' + expr + '"'
})
```

### Voidタグ (自己終了タグ)

内部的には、`<tag />`も`<tag></tag>`に統一。

```javascript
//L11
var VOID_TAGS = 'area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr'.split(',')

//L63
if (VOID_TAGS.indexOf(name.toLowerCase()) == -1) tag += '</' + name + '>'
```

### パーサー

現在対応するパーサーは、HTMLについてはJade, JavaScriptについては、CoffeeScript、プレーンなJS、ES6、TypeScript、riotjs(デフォルト)の5種。riotjsは軽量な「なんちゃって」6to5実装になります (後述)。

```javascript
//L13
var HTML_PARSERS = {
  jade: jade
}
//L17
var JS_PARSERS = {
  coffeescript: coffee,
  none: plainjs,
  cs: coffee,
  es6: es6,
  typescript: typescript
}
```

パーサーは汎用のものをrequireしているだけ。ほかのAltJSを足したければ、このあたりに追加してコミットすると良さげ。

```
//L97: Jadeなら
require('jade').render(html, {pretty: true})

//L82: Coffeeなら
require('coffee-script').compile(js, { bare: true })
//L85: ES6なら
require('6to5').transform(js).code
//L89: TypeScriptなら
require('typescript-simple')(js)
```

> ♦ Riot.js自体に含まれるのは、テンプレートの解析部分だけ。それも単純な正規表現によるものなので、「パーサー」というほどでもなし。

### 正規表現

タグを抽出するための各種正規表現。

```javascript
//L26
var CUSTOM_TAG = /^<([\w\-]+)>([^\x00]*[\w\/]>$)?([^\x00]*?)^<\/\1>/gim,
    SCRIPT = /<script(\s+type=['"]?([^>'"]+)['"]?)?>([^\x00]*?)<\/script>/gm,
    HTML_COMMENT = /<!--.*?-->/g,
    CLOSED_TAG = /<([\w\-]+)([^\/]*)\/\s*>/g,
    LINE_COMMENT = /^\s*\/\/.*$/gm,
    JS_COMMENT = /\/\*[^\x00]*?\*\//gm
```

カスタムタグの名称としては、英数とハイフンがOK。

> ♦ `[\w\-]+`だと、最初がハイフンなやつとかもマッチするけど、まあいいや。
> HTMLは木構造だからパーサー書かないと気持ち悪い(?)けど、なんとかなるならネイティブ動作の正規表現の方が速いのは確か。

### メソッド

#### compile(riot_tag, opts)

- 引数: `riot_tag`, `opts`

tagファイルに書かれた内容を、riot.jsのビュー(view.js)が解釈可能なコードに変換する関数。

第一引数は、tagファイルに入っているテキストそのもの。正規表現で、強引にタグの名称` tagName`と`html`と`js`に分離。

```javascript
//L26
CUSTOM_TAG = /^<([\w\-]+)>([^\x00]*[\w\/]>$)?([^\x00]*?)^<\/\1>/gim
//L155
return riot_tag.replace(CUSTOM_TAG, function(_, tagName, html, js) { <略> }
```

`<script>`タグ内にコードを書いている場合は、`html`からコードを抽出。

```javascript
html = html.replace(SCRIPT, function(_, fullType, _type, script) {
  if (_type) type = _type.replace('text/', '')
  js = script
  return ''
})
```

HTML部分、スクリプト部分をそれぞれコンパイル。全体として、ひとつのテキストにして戻す。

```javascript
return
  'riot.tag(\'' +tagName+ '\', \''
  + compileHTML(html, opts, type)
  + '\', function(opts) {'
    + compileJS(js, opts, type)
  + '\n});'
```

#### compileTemplate(lang, html)

- 引数: `lang`, `html`

HTML部分のコンパイル。今のところ、対応はJadeのみ。

```javascript
//L144
var parser = HTML_PARSERS[lang]
return parser(html)
```

#### compileHTML(html, opts, type)

- 引数: `html`, `opts`, `type`

こんなイージーでいいのか...。

0. 複数空白をひとつに
0. HTMLコメントの除去
0. 属性値をinterpolationに統一: `foo={ bar }`を`foo="{ bar }"`に
0. 真偽値属性の置き換え (前述なので略)
0. 表現部分`{ hoge }`をJavaScriptにコンパイル
0. Voidタグを`<foo></foo>`に統一 (前述なので略)
0. シングルコートのエスケープ
0. `\{` `\}` を二重エスケープ
0. タグ間の空白除去 ※opts.compact指定時のみ

```javascript
//L37
html = html.replace(/\s+/g, ' ')
//L40
html = html.trim().replace(HTML_COMMENT, '')
//L43
html = html.replace(/=(\{[^\}]+\})([\s\>])/g, '="$1"$2')
//L53
html = html.replace(/\{\s*([^\}]+)\s*\}/g, function(_, expr) {
   return '{' + compileJS(expr, opts, type).trim() + '}'
})
//L68
html = html.replace(/'/g, "\\'")
//L72
html = html.replace(/\\[{}]/g, '\\$&')
//L75
if (opts.compact) html = html.replace(/> </g, '><')
```

#### compileJS(js, opts, type)

- 引数: `js`, `opts`, `type`

スクリプト部分のコンパイル。対応AltJSについては前述。未指定の場合は、`riotjs`関数が使われます。

```javascript
//L138
var parser = opts.parser || (type ? JS_PARSERS[type] : riotjs)
return parser(js, opts)
```

#### riotjs(js)

- 引数: `js`

軽量な「なんちゃって」6to5実装。スクリプト用のパーサが未指定の時に使用。

```javascript
myFunc () {
  return 1+1
}
```

は次のコードに変換されます。

```javascript
this.myFunc = function() {
  return 1+1
}.bind(this);
```

該当箇所のコードは以下。

```javascript
//L115
var m = /(\s+)([\w]+)\s*\(([\w,\s]*)\)\s*\{/.exec(line)
//L118
lines[i] = m[1] + 'this.' + m[2] + ' = function(' + m[3] + ') {'
//L126
lines[i] += '.bind(this);'
```

### エクスポート

#### Node.jsの場合

CommonJS形式で。Browserifyや、コンパイラをnodeから使うケース。

```javascript
//L181
module.exports = {
  html: compileHTML,
  compile: compile
}
```

ちなみに、Node.jsかどうかの判定はこれ...

```javascript
//L283
is_node = !this.top
```

#### ブラウザの場合

`riot.mount`と`riot.mountTo`は、[view.js](https://github.com/muut/riotjs/blob/c1697feab1f0e1b72e5a87a30f44e817ffe6111b/lib/view.js)で定義されている関数。プリコンパイルせずに、ブラウザから利用する場合は、`mount`の際にコンパイラをかぶせている。

```javascript
//L271
riot.mount = function(a, b) {
  browserCompile(function() { mount(a, b) })
}
riot.mountTo = function(a, b, c) {
  browserCompile(function() { mountTo(a, b, c) })
}
```


## テンプレート: tmpl.js 全159行

### キャッシュと正規表現

```javascript
//L41
var cache = {},
    re_vars = /("|').+?[^\\]\1|\.\w*|\w*:|\b(?:this|true|false|null|...)\b|([a-z_]\w*)/gi
            // [ 1            ][ 2  ][ 3 ][ 4                             ][ 5       ]
```

1. コーテーション付き文字列をスキップ: `"a b"`, `'a b'`, `'a \'b\''`
2. オブジェクトのプロパティをスキップ: `.name`
3. オブジェクトのリテラルをスキップ: `name:`
4. 予約語をスキップ
5. 変数名にマッチ

### テンプレート生成と、レンダリング

テンプレートを生成し(キャッシュがある場合は取得し)、`data`を渡してレンダリングします。

```javascript
//L54
return function(str, data) {
  return str && (cache[str] = cache[str] || tmpl(str))(data)
}
```

### tmpl(s, p)

テンプレートのインスタンス生成。

> ♦ 作成したテンプレートは`new Function(arg, 'return ...')`の形で、関数化されるので、Riot.js の好き嫌いを大きく分けそうなポイント。`eval()`よりはマシにしても、`Function`の使用はできれば避けたいところ。ここも含めてプリコンパイルする方法があっても良さそう。

まず、一時的に`\{`と`\}`を関係ない文字コードで置き換え。 `{ ... }`でチャンクに分けます。つまり、変数`p`は、`{ ... }`とそうでない部分が交互に入った配列になります。

```javascript
//L61
function tmpl(s, p) {
  p = (s || '{}')
    .replace(/\\{/g, '\uFFF0')
    .replace(/\\}/g, '\uFFF1')
    .split(/({[\s\S]*?})/)
```

単独表現(expression)か、テンプレートかを見分けます。`{ ... }`で分割しているので、配列の0番目と2番目の要素が空なら単独表現と見なせます。前者ならそのまま評価し、そうでなければ全ての偶数番目の要素を評価します。

- 単独表現: `{x}`
- テンプレート: `<b>{x}</b>`

```javascript
  return new Function('d', 'return ' + (
    !p[0] && !p[2]
      ? expr(p[1])
      : '[' + p.map(function(s, i) {
          return i % 2
            ? expr(s, 1)
            : '"' + s
                .replace(/\n/g, '\\n') // 改行記号の保持
                .replace(/"/g, '\\"') // クオートのエスケープ
              + '"'
        }).join(',') + '].join("")' // 連結
    )
```

置き換えていた文字を`{`と`}`に戻す。

```javascript
    .replace(/\uFFF0/g, '{')
    .replace(/\uFFF1/g, '}')
  )
}
```

### expr(s, n)

`{ ... }`の部分のパース。

```javascript
//L113
function expr(s, n) {
  s = s
    .replace(/\n/g, ' ') // 改行を空白に
    .replace(/^[{ ]+|[ }]+$|\/\*.+?\*\//g, '') // 空白と`{` `}`のトリム。コメント除去
```

ハッシュ(オブジェクトリテラル)なら、真になるものだけを空白で連結して返す。class属性の設定で使うやつ。例えば次のような処理になります。

- `{ show: isOpen(), done: item.done }`が指定されていて
- `isOpne()`だけ真で
- `item.done`が偽なら
- 文字列'show'に置き換えられる

```javascript
  return /^\s*[\w-"']+ *:/.test(s)
    ? '[' + s.replace(/\W*([\w-]+)\W*:([^,]+)/g, function(_, k, v) {
        return v.replace(/\w[^,|& ]*/g, function(v) { return wrap(v, n) }) + '?"' + k + '":"",'
      }) + '].join(" ")'
```

ハッシュでなければ、単に`wrap`して返します。
   
```javascript
    : wrap(s, n)
}
```

### wrap(s, nonull)

JavaScriptのコードを`try` `finally`でラップ。`undefined`エラーの抑止。

```javascript
//L142
function wrap(s, nonull) {
  return
    '(function(v){try{v='
      + (s.replace(re_vars, function(s, _, v) { return v ? 'd.' + v : s }) || 'x')
    + '}finally{return '
```

ゼロ以外の`null`,`false`,`undefined`とかは空列に。

```javascript
      + (nonull ? '!v&&v!==0?"":v' : 'v')
    + '}}).call(d)'
}
```


## ビュー: view.js 全429行

処理の流れは、こんな感じ。

- `riot.tag(name, tmpl, fn)`: コンポーネントの登録
- `riot.mount(selector, opts)`: コンポーネントのマウント
    - `riot.mountTo(node, tagName, opts)`
        - `createTag(conf)`
            - `parse(root)`
                - `mkdom(tmpl)`

更新(update)の流れ。

- `riot.update()`: 全コンポーネントの更新
    - `tag.update(data, _system)`: 個別のコンポーネントの更新
        - `update(expressions, instance)`
            - `loop(expr, instance)`

### 共通に参照する変数

`tmpl`は前述のtmpl.jsのコード参照。`all_tags`は

```javascript
var tmpl = riot._tmpl,
    all_tags = [],
    tag_impl = {},
    doc = document
```

### ユーティリティメソッド

#### each(nodes, fn)

各オブジェクトに関数fnを適用する。`false`を返した場合は再適用。

```javascript
//L11
function each(nodes, fn) {
  for (var i = 0; i < (nodes || []).length; i++) {
    if (fn(nodes[i], i) === false) i--
  }
}
```

#### extend(obj, from)

`obj`に`from`の持つフィールドを追加

```javascript
//L17
function extend(obj, from) {
  from && Object.keys(from).map(function(key) {
    obj[key] = from[key]
  })
  return obj
}
```

#### diff(arr1, arr2)

2つの配列を比較して、`arr1`の要素が`arr2`に存在しなかったら、その部分を配列として返す。

```javascript
//L24
function diff(arr1, arr2) {
  return arr1.filter(function(el) {
    return arr2.indexOf(el) < 0
  })
}
```

> ♦ 順序気にしないのか...

#### walk(dom, fn)

指定したDOM要素から、`nextSibling`か`firstChild`を順に辿って関数`fn`を適用。

```javascript
//L30
function walk(dom, fn) {
  dom = fn(dom) === false ? dom.nextSibling : dom.firstChild

  while (dom) {
    walk(dom, fn)
    dom = dom.nextSibling
  }
}
```

#### mkdom(tmpl)

DOMの生成。

> ♦ `td`か`th`だったら`tr`とか、アドホックな対応が入っている。このあたり、筋がよくない。
> ♦ Riot.jsのサイズが小さい一番の理由は、`el.innerHTML = tmpl`の部分。HTMLのパースをブラウザ任せにすることで、いろいろ省いています。

```javascript
//L40
function mkdom(tmpl) {
  var tag_name = tmpl.trim().slice(1, 3).toLowerCase(),
      root_tag = /td|th/.test(tag_name) ? 'tr' : tag_name == 'tr' ? 'tbody' : 'div'
      el = doc.createElement(root_tag)

  el.innerHTML = tmpl
  return el
}
```

#### setEventHandler(name, handler, dom, instance)

DOMイベント周りの、マルチブラウザ対応。

```javascript
//L50
function setEventHandler(name, handler, dom, instance) {

  dom[name] = function(e) {

    e = e || window.event
    e.which = e.which || e.charCode || e.keyCode
    e.target = e.target || e.srcElement
    e.currentTarget = dom
```

preventDefaultが自動で呼ばれる。イベントチェーンに繋ぎたい場合は、明示的にイベントハンドラで`true`を返す必要あり。

ハンドラを処理後、インスタンスの`update()`

```javascript
    if (handler.call(instance, e) !== true) {
      e.preventDefault && e.preventDefault()
      e.returnValue = false
    }

    instance.update()
  }

}
```

### ビュー周りのメソッド

#### update(expressions, instance)

処理前に`update`イベントをトリガー。

```javascript
//L75
function update(expressions, instance) {
  instance.trigger('update')
```

`parse()`結果の、表現の配列`expressions`を順に処理。`remAttr()`は、`dom.removeAttribute()`のショートハンド。

```javascript
  each(expressions, function(expr) {
    var tag = expr.tag,
        dom = expr.dom

    function remAttr(name) {
      dom.removeAttribute(name)
    }
```

ループであれば、`loop`に渡してその結果を返す。

```javascript
    if (expr.loop) {
      remAttr('each')
      return loop(expr, instance)
    }
```

タグのアップデート。変更がなければ、途中でリターン。

```javascript
    if (tag) return tag.update ? tag.update() :
      expr.tag = createTag({ tmpl: tag[0], fn: tag[1], root: dom, parent: instance })

    var attr_name = expr.attr,
        value = tmpl(expr.expr, instance)

    if (value == null) value = ''

    // 変更がなければリターン
    if (expr.value === value) return
    expr.value = value

    // テキストノード
    if (!attr_name) return dom.nodeValue = value

    // 属性
    if (!value && expr.bool || /obj|func/.test(typeof value)) remAttr(attr_name)
```

関数の場合は、イベントハンドラとして登録

```javascript
    if (typeof value == 'function') {
      setEventHandler(attr_name, value, dom, instance)
```

`show` `hide` `if` の場合。

> ♦ `style.display`で隠すあたりが、少々危うい感じ。理解してテンプレートを組む必要がある。

```javascript
    } else if (/^(show|hide|if)$/.test(attr_name)) {
      remAttr(attr_name)
      if (attr_name == 'hide') value = !value
      dom.style.display = value ? '' : 'none'
```

上記以外。

```javascript
    } else {
      if (expr.bool) {
        dom[attr_name] = value
        if (!value) return
        value = attr_name
      }
      dom.setAttribute(attr_name, value)
    }
  })

  instance.trigger('updated')

}
```

#### parse(root)

ここで渡される`root`引数は、`mkdom()`関数で作られた(マウント前の)DOMです。

`named_elements`には、`id`属性か`name`属性が指定された「名前付き要素」が入ります。[cf. Named elements](https://muut.com/riotjs/guide/#named-elements)

```javascript
//L142
function parse(root) {

  var named_elements = {},
      expressions = []
```

`expressions`に表現を追加していくための局所関数`addExpr()`。

```javascript
  function addExpr(dom, value, data) {
    if (value ? value.indexOf('{') >= 0 : data) {
      var expr = { dom: dom, expr: value }
      expressions.push(extend(expr, data || {}))
    }
  }
```

順繰りにDOM要素を解析。[nodeType](https://developer.mozilla.org/en-US/docs/Web/API/Node.nodeType)で場合分け。

```javascript
  walk(root, function(dom) {

    var type = dom.nodeType,
        value = dom.nodeValue

    // テキストノード (nodeType:3)
    if (type == 3 && dom.parentNode.tagName != 'STYLE') {
      addExpr(dom, value)

    // エレメントノード (nodeType:1)
    } else if (type == 1) {
```

ループかどうかを、`each`属性が指定されているかで判断。ループであれば、新しいループ表現を追加して次の要素へ。

```javascript
      value = dom.getAttribute('each')
      if (value) {
        addExpr(dom, value, { loop: 1 })
        return false
      }
```

独自タグかどうかを、`tag_impl[<タグ名>]`が存在するかで判断。要素の属性ごとに、ひとつひとつパース。

```javascript
      var tag = tag_impl[dom.tagName.toLowerCase()]

      each(dom.attributes, function(attr) {
        var name = attr.name,
            value = attr.value

        // 名前付き要素
        if (/^(name|id)$/.test(name)) named_elements[value] = dom

        // 表現
        if (!tag) {
          var bool = name.split('__')[1] // 接頭辞__が付いていたら真偽値属性
          addExpr(dom, value, { attr: bool || name, bool: bool })
          if (bool) {
            dom.removeAttribute(name)
            return false
          }
        }
      })
```

独自タグなら、表現を追加。全てのノードの走査が完了したら、最後にパース結果を返す。

```javascript
      if (tag) addExpr(dom, 0, { tag: tag })
    }
  })
  return { expr: expressions, elem: named_elements }
}
```

#### createTag(conf)

独自タグ(コンポーネント)のインスタンスを作成。

```javascript
function createTag(conf) {

  var opts = conf.opts || {},
      dom = mkdom(conf.tmpl),
      mountNode = conf.root,
      parent = conf.parent,
      ast = parse(dom),
      tag = { root: mountNode, opts: opts, parent: parent, __item: conf.item },
      attributes = {}

  // 名前付き要素を`tag`に付加
  extend(tag, ast.elem)
```

マウントする要素の属性を、`attributes`にコピー。
データをテンプレートに通した結果を、`opts`に格納。

```javascript
  each(mountNode.attributes, function(attr) {
    attributes[attr.name] = attr.value
  })

  function updateOpts() {
    Object.keys(attributes).map(function(name) {
      var val = opts[name] = tmpl(attributes[name], parent || tag)
      if (typeof val == 'object') mountNode.removeAttribute(name)
    })
  }
  updateOpts()
```

タグを`observable`に。

```javascript
  if (!tag.on) {
    riot.observable(tag)
    delete tag.off // off メソッドは不要
  }

  if (conf.fn) conf.fn.call(tag, opts)
```

タグに`update()`メソッドを定義。コンポーネントのルート要素が繰り返しになっている場合は、仮に`div`要素が`mkdom()`によって渡されている。

```javascript
  tag.update = function(data, _system) {

    if (parent && dom && !dom.firstChild) {
      mountNode = parent.root
      dom = null
    }

    if (_system || doc.body.contains(mountNode)) {
      extend(tag, data)
      extend(tag, tag.__item)
      updateOpts()
      update(ast.expr, tag)

      // update parent
      !_system && tag.__item && parent.update()
      return true

    } else {
      tag.trigger('unmount')
    }
  }
  tag.update(0, true)
```

domを実際にマウント。`before`指定があれば、要素の前に。そうでなければ子要素として。

```javascript
  while (dom.firstChild) {
    if (conf.before) mountNode.insertBefore(dom.firstChild, conf.before)
    else mountNode.appendChild(dom.firstChild)
  }
```

タグの生成が完了したので、`mount`イベントをトリガー。`all_tags`に要素を登録し、`tag`を返す。

```javascript
  tag.trigger('mount')
  all_tags.push(tag)
  return tag
}
```


#### loop(expr, instance)

> TODO:要追記

ループの初期化は一度だけ

```javascript
//L290
function loop(expr, instance) {
  if (expr.done) return
  expr.done = true
```



```javascript
  var dom = expr.dom,
      prev = dom.previousSibling,
      root = dom.parentNode,
      template = dom.outerHTML,
      val = expr.expr,
      els = val.split(/\s+in\s+/),
      rendered = [],
      checksum,
      keys

  if (els[1]) {
    val = '{ ' + els[1]
    keys = els[0].slice(1).trim().split(/,\s*/)
  }
```



```javascript
  instance.one('mount', function() {
    var p = dom.parentNode
    if (p) {
      root = p
      root.removeChild(dom)
    }
  })

  function startPos() {
    return Array.prototype.indexOf.call(root.childNodes, prev) + 1
  }
```

仮想DOM(?)の変更検知

```javascript
  instance.on('updated', function() {

    var items = tmpl(val, instance),
        is_array = Array.isArray(items)

    if (is_array) items = items.slice(0)

    else {

      if (!items) return // some IE8 issue

      // オブジェクトの変更検知
      var testsum = JSON.stringify(items)
      if (testsum == checksum) return
      checksum = testsum

      items = Object.keys(items).map(function(key, i) {
        var item = {}
        item[keys[0]] = key
        item[keys[1]] = items[key]
        return item
      })

    }
```

重複の除去。

```javascript
    diff(rendered, items).map(function(item) {
      var pos = rendered.indexOf(item)
      root.removeChild(root.childNodes[startPos() + pos])
      rendered.splice(pos, 1)
    })
```

新規追加。

```javascript
    diff(items, rendered).map(function(item, i) {
      var pos = items.indexOf(item)

      if (keys && !checksum) {
        var obj = {}
        obj[keys[0]] = item
        obj[keys[1]] = pos
        item = obj
      }

      var tag = createTag({
        before: root.childNodes[startPos() + pos],
        parent: instance,
        tmpl: template,
        item: item,
        root: root
      })

      instance.on('update', function() {
        tag.update(0, true)
      })
    })
```

最後に`rendered`にアサイン

```javascript
    rendered = items
  })
}
```

### riotを拡張するメソッド

#### riot.tag(name, tmpl, fn)

riotに独自タグ(コンポーネント)を登録する関数。`[テンプレート, 関数]`のセットで登録されます。コンパイルされたテンプレートが呼んでいる関数がこれ。

```javascript
//L390
riot.tag = function(name, tmpl, fn) {
  fn = fn || noop,
  tag_impl[name] = [tmpl, fn]
}
```

#### riot.mountTo(node, tagName, opts)

特定のDOM要素に、コンポーネントを「マウント」する関数。`createTag`を呼んでいるだけ。

```javascript
//L395
riot.mountTo = function(node, tagName, opts) {
  var tag = tag_impl[tagName]
  return tag && createTag({ tmpl: tag[0], fn: tag[1], root: node, opts: opts })
}
```

#### riot.mount(selector, opts)

基本的な使い方としては、HTMLから使う唯一の関数。セレクタを指定して「マウント」します。タグ名でも、IDでも、ブラウザの[querySelector](http://caniuse.com/#feat=queryselector)が認識する書き方ならOK。

> ♦ riotがIE7以下に対応しないのは、主に`querySelector`が実装されてないから。

セレクタとして`*`を渡しても、HTMLの全要素を走査するわけではなく、登録されたコンポーネントの名称のみを検索するので、パフォーマンスを気にする必要はなさそう。

```javascript
//L400
riot.mount = function(selector, opts) {
  if (selector == '*') selector = Object.keys(tag_impl).join(', ')

  var instances = []

  each(doc.querySelectorAll(selector), function(node) {
    if (node.riot) return

    var tagName = node.tagName.toLowerCase(),
        instance = riot.mountTo(node, tagName, opts)

    if (instance) {
      instances.push(instance)
      node.riot = 1
    }
  })
```

ドキュメントには明記されていませんが、生成したインスタンス(仮想DOM)を返します。

```javascript
  return instances
}
```

#### riot.update()

すべてのコンポーネントをアップデート。

```javascript
riot.update = function() {
  return all_tags = all_tags.filter(function(tag) {
    return !!tag.update()
  })
}
```


## まとめ

おつかれさまでした m(_ _)m