追記・「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)
orcoffee(js)
ores6(js)
...
-
-
以下、コードを追っていきます。一緒にコードを見つつ、どうぞ。
真偽値属性 (boolean attribute)
checked
やreadonly
などの属性については、文字列ではなく真偽値として扱います。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ファイルに入っているテキストそのもの。正規表現で、強引にタグの名称 tagName
とhtml
とjs
に分離。
//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
こんなイージーでいいのか...。
- 複数空白をひとつに
- HTMLコメントの除去
- 属性値をinterpolationに統一:
foo={ bar }
をfoo="{ bar }"
に - 真偽値属性の置き換え (前述なので略)
- 表現部分
{ hoge }
をJavaScriptにコンパイル - Voidタグを
<foo></foo>
に統一 (前述なので略) - シングルコートのエスケープ
-
\{
\}
を二重エスケープ - タグ間の空白除去 ※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.mount
とriot.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 ]
- コーテーション付き文字列をスキップ:
"a b"
,'a b'
,'a \'b\''
- オブジェクトのプロパティをスキップ:
.name
- オブジェクトのリテラルをスキップ:
name:
- 予約語をスキップ
- 変数名にマッチ
テンプレート生成と、レンダリング
テンプレートを生成し(キャッシュがある場合は取得し)、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)
obj
にfrom
の持つフィールドを追加
//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要素から、nextSibling
かfirstChild
を順に辿って関数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の生成。
♦
td
かth
だったら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