⌈⌋ ⎇ branch:  Bitrhythm


Artifact Content

Artifact a9cac12593347c2df2af1b2c71dbac794785bc07bd68479ad4812eb2b4025f1a:


import observable from 'riot-observable'
import compiler from 'riot-compiler'
import { T_STRING } from './../common/global-variables'

import isArray from './../common/util/checks/is-array'
import isObject from './../common/util/checks/is-object'
import isFunction from './../common/util/checks/is-function'

import extend from './../common/util/misc/extend'

import $$ from './../common/util/dom/$$'
import getAttribute from './../common/util/dom/get-attribute'
import makeElement from './../common/util/dom/make-element'

var
  promise,    // emits the 'ready' event and runs the first callback
  ready       // all the scripts were compiled?

// gets the source of an external tag with an async call
function GET (url, fn, opts) {
  var req = new XMLHttpRequest()

  req.onreadystatechange = function () {
    if (req.readyState === 4) {
      if (req.status === 200 || !req.status && req.responseText.length) {
        fn(req.responseText, opts, url)
      } else {
        compile.error(`"${ url }" not found`)
      }
    }
  }

  req.onerror = e => compile.error(e)

  req.open('GET', url, true)
  req.send('')
}

// evaluates a compiled tag within the global context
function globalEval (js, url) {
  if (typeof js === T_STRING) {
    var
      node = makeElement('script'),
      root = document.documentElement

    // make the source available in the "(no domain)" tab
    // of Chrome DevTools, with a .js extension
    if (url) js += '\n//# sourceURL=' + url + '.js'

    node.text = js
    root.appendChild(node)
    root.removeChild(node)
  }
}

// compiles all the internal and external tags on the page
function compileScripts (fn, xopt) {
  var
    scripts = $$('script[type="riot/tag"]'),
    scriptsAmount = scripts.length

  function done() {
    promise.trigger('ready')
    ready = true
    if (fn) fn()
  }

  function compileTag (src, opts, url) {
    var code = compiler.compile(src, opts, url)

    globalEval(code, url)
    if (!--scriptsAmount) done()
  }

  if (!scriptsAmount) done()
  else {
    for (var i = 0; i < scripts.length; ++i) {
      var
        script = scripts[i],
        opts = extend({template: getAttribute(script, 'template')}, xopt),
        url = getAttribute(script, 'src') || getAttribute(script, 'data-src')

      url ? GET(url, compileTag, opts) : compileTag(script.innerHTML, opts)
    }
  }
}

export const parsers = compiler.parsers

/*
  Compilation for the browser
*/
export function compile (arg, fn, opts) {

  if (typeof arg === T_STRING) {

    // 2nd parameter is optional, but can be null
    if (isObject(fn)) {
      opts = fn
      fn = false
    }

    // `riot.compile(tag [, callback | true][, options])`
    if (/^\s*</m.test(arg)) {
      var js = compiler.compile(arg, opts)
      if (fn !== true) globalEval(js)
      if (isFunction(fn)) fn(js, arg, opts)
      return js
    }

    // `riot.compile(url [, callback][, options])`
    GET(arg, function (str, opts, url) {
      var js = compiler.compile(str, opts, url)
      globalEval(js, url)
      if (fn) fn(js, str, opts)
    }, opts)

  } else if (isArray(arg)) {
    var i = arg.length
    // `riot.compile([urlsList] [, callback][, options])`
    arg.forEach(function(str) {
      GET(str, function (str, opts, url) {
        var js = compiler.compile(str, opts, url)
        globalEval(js, url)
        i --
        if (!i && fn) fn(js, str, opts)
      }, opts)
    })
  } else {

    // `riot.compile([callback][, options])`
    if (isFunction(arg)) {
      opts = fn
      fn = arg
    } else {
      opts = arg
      fn = undefined
    }

    if (ready) {
      return fn && fn()
    }

    if (promise) {
      if (fn) promise.on('ready', fn)

    } else {
      promise = observable()
      compileScripts(fn, opts)
    }
  }
}

// it can be rewritten by the user to handle all the compiler errors
compile.error = (e) => {
  throw new Error(e)
}