241
237

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-02-07

追記・「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

241
237
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
241
237

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?