Edited at

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

More than 3 years have passed since last update.

追記・「Riot.js ソースコード完全解説 v3対応版」を公開しました。(2016/7/26)


2.0.7時点のコードを読みます。

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

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


  • 正規表現を多用する (かなりイージー)

  • DOMのパースはinnerHTML頼り

  • CoffeeScriptやJadeなどのコンパイラは含まない

  • それ以外のライブラリ依存なし

  • セミコロンが嫌いらしい

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

ファイル
機能


compiler.js
tagファイルからriot.tag(html, script)の形式に


tmpl.js
テンプレート生成


view.js
ビューの生成と更新

observable.js
メッセージ通信 (追加機能)

router.js
ルーティング (追加機能)

cli.js
CLIから compiler.js を呼び出すためのラッパー

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

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


  • tagファイル

  • コンパイル riot.tag(html, script)

  • テンプレート生成

  • ビュー生成と更新

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


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



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

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

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) ...





以下、コードを追っていきます。一緒にコードを見つつ、どうぞ。


真偽値属性 (boolean attribute)

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

//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>に統一。

//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実装になります (後述)。

//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自体に含まれるのは、テンプレートの解析部分だけ。それも単純な正規表現によるものなので、「パーサー」というほどでもなし。



正規表現

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

//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ファイルに入っているテキストそのもの。正規表現で、強引にタグの名称tagNamehtmljsに分離。

//L26

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

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

html = html.replace(SCRIPT, function(_, fullType, _type, script) {

if (_type) type = _type.replace('text/', '')
js = script
return ''
})

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

return

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


compileTemplate(lang, html)


  • 引数: lang, html

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

//L144

var parser = HTML_PARSERS[lang]
return parser(html)


compileHTML(html, opts, type)


  • 引数: html, opts, type

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


  1. 複数空白をひとつに

  2. HTMLコメントの除去

  3. 属性値をinterpolationに統一: foo={ bar }foo="{ bar }"

  4. 真偽値属性の置き換え (前述なので略)

  5. 表現部分{ hoge }をJavaScriptにコンパイル

  6. Voidタグを<foo></foo>に統一 (前述なので略)

  7. シングルコートのエスケープ


  8. \{ \} を二重エスケープ

  9. タグ間の空白除去 ※opts.compact指定時のみ

//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関数が使われます。

//L138

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


riotjs(js)


  • 引数: js

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

myFunc () {

return 1+1
}

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

this.myFunc = function() {

return 1+1
}.bind(this);

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

//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から使うケース。

//L181

module.exports = {
html: compileHTML,
compile: compile
}

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

//L283

is_node = !this.top


ブラウザの場合

riot.mountriot.mountToは、view.jsで定義されている関数。プリコンパイルせずに、ブラウザから利用する場合は、mountの際にコンパイラをかぶせている。

//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行


キャッシュと正規表現

//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を渡してレンダリングします。

//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は、{ ... }とそうでない部分が交互に入った配列になります。

//L61

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

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


  • 単独表現: {x}

  • テンプレート: <b>{x}</b>

  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("")' // 連結
)

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

    .replace(/\uFFF0/g, '{')

.replace(/\uFFF1/g, '}')
)
}


expr(s, n)

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

//L113

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

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



  • { show: isOpen(), done: item.done }が指定されていて


  • isOpne()だけ真で


  • item.doneが偽なら

  • 文字列'show'に置き換えられる

  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して返します。

    : wrap(s, n)

}


wrap(s, nonull)

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

//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とかは空列に。

      + (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

var tmpl = riot._tmpl,

all_tags = [],
tag_impl = {},
doc = document


ユーティリティメソッド


each(nodes, fn)

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

//L11

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


extend(obj, from)

objfromの持つフィールドを追加

//L17

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


diff(arr1, arr2)

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

//L24

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


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



walk(dom, fn)

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

//L30

function walk(dom, fn) {
dom = fn(dom) === false ? dom.nextSibling : dom.firstChild

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


mkdom(tmpl)

DOMの生成。


tdthだったらtrとか、アドホックな対応が入っている。このあたり、筋がよくない。

♦ Riot.jsのサイズが小さい一番の理由は、el.innerHTML = tmplの部分。HTMLのパースをブラウザ任せにすることで、いろいろ省いています。


//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イベント周りの、マルチブラウザ対応。

//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()

    if (handler.call(instance, e) !== true) {

e.preventDefault && e.preventDefault()
e.returnValue = false
}

instance.update()
}

}


ビュー周りのメソッド


update(expressions, instance)

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

//L75

function update(expressions, instance) {
instance.trigger('update')

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

  each(expressions, function(expr) {

var tag = expr.tag,
dom = expr.dom

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

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

    if (expr.loop) {

remAttr('each')
return loop(expr, instance)
}

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

    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)

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

    if (typeof value == 'function') {

setEventHandler(attr_name, value, dom, instance)

show hide if の場合。


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


    } else if (/^(show|hide|if)$/.test(attr_name)) {

remAttr(attr_name)
if (attr_name == 'hide') value = !value
dom.style.display = value ? '' : 'none'

上記以外。

    } 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

//L142

function parse(root) {

var named_elements = {},
expressions = []

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

  function addExpr(dom, value, data) {

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

順繰りにDOM要素を解析。nodeTypeで場合分け。

  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属性が指定されているかで判断。ループであれば、新しいループ表現を追加して次の要素へ。

      value = dom.getAttribute('each')

if (value) {
addExpr(dom, value, { loop: 1 })
return false
}

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

      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
}
}
})

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

      if (tag) addExpr(dom, 0, { tag: tag })

}
})
return { expr: expressions, elem: named_elements }
}


createTag(conf)

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

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に格納。

  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に。

  if (!tag.on) {

riot.observable(tag)
delete tag.off // off メソッドは不要
}

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

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

  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指定があれば、要素の前に。そうでなければ子要素として。

  while (dom.firstChild) {

if (conf.before) mountNode.insertBefore(dom.firstChild, conf.before)
else mountNode.appendChild(dom.firstChild)
}

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

  tag.trigger('mount')

all_tags.push(tag)
return tag
}


loop(expr, instance)


TODO:要追記


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

//L290

function loop(expr, instance) {
if (expr.done) return
expr.done = true

  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*/)
}

  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(?)の変更検知

  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
})

}

重複の除去。

    diff(rendered, items).map(function(item) {

var pos = rendered.indexOf(item)
root.removeChild(root.childNodes[startPos() + pos])
rendered.splice(pos, 1)
})

新規追加。

    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にアサイン

    rendered = items

})
}


riotを拡張するメソッド


riot.tag(name, tmpl, fn)

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

//L390

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


riot.mountTo(node, tagName, opts)

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

//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が認識する書き方ならOK。


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


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

//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)を返します。

  return instances

}


riot.update()

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

riot.update = function() {

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


まとめ

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