All Downloads are FREE. Search and download functionalities are using the official Maven repository.

skinny.assets.ReactJSXCompiler.scala Maven / Gradle / Ivy

There is a newer version: 2.5.2
Show newest version
package skinny.assets

import org.mozilla.javascript._
import java.io.{ ByteArrayInputStream, IOException, InputStreamReader }
import skinny.util.LoanPattern._
import skinny.ClassPathResourceLoader

import scala.sys.process._
import org.slf4j.LoggerFactory
import skinny.exception.AssetsPrecompileFailureException
import org.mozilla.javascript.ScriptableObject
import org.mozilla.javascript.Context
import org.mozilla.javascript.JavaScriptException

/**
 * React JSX template compiler.
 *
 * @see https://github.com/facebook/react-rails
 */
class ReactJSXCompiler {

  private[this] val log = LoggerFactory.getLogger(classOf[ReactJSXCompiler])

  private[this] lazy val globalScope: ScriptableObject = {
    val context = Context.enter
    context.setOptimizationLevel(-1)
    val globalScope = context.initStandardObjects

    context.evaluateString(globalScope, "var global = global || this;", "jsx", 0, null)
    ClassPathResourceLoader.getClassPathResource("META-INF/skinny-assets/JSXTransformer.js").map { jsx =>
      using(jsx.stream) { stream =>
        using(new InputStreamReader(stream)) { input =>
          context.evaluateReader(globalScope, input, "jsx", 0, null)
        }
      }
    }

    globalScope
  }

  private[this] def isWindows = System.getProperty("os.name").toLowerCase().indexOf("win") >= 0

  private[this] def jsxCommand = if (isWindows) "jsx.bat" else "jsx"

  private[this] def nativeCompilerDescription = Seq(jsxCommand, "--version").lines.mkString

  private[this] def nativeCompilerCommand = Seq(jsxCommand)

  private[this] def nativeCompilerExists: Boolean = {
    try !Seq(jsxCommand, "--version").lines.mkString.isEmpty
    catch { case e: IOException => false }
  }

  /**
   * Compiles React JSX Transformer source code to JavaScript source code.
   *
   * @param jsxCode jsx code
   * @return js code
   */
  def compile(path: String, jsxCode: String): String = {
    if (nativeCompilerExists) {
      // Native compiler (npm install -g react-tools)
      log.debug(s"Native React compiler is found. (version: ${nativeCompilerDescription})")

      val (out, err) = (new StringBuilder, new StringBuilder)
      val processLogger = ProcessLogger(
        (o: String) => out ++= o ++= "\n",
        (e: String) => err ++= e ++= "\n"
      )
      using(new ByteArrayInputStream(jsxCode.getBytes)) { stdin =>
        val exitCode = (nativeCompilerCommand #< stdin) ! processLogger
        if (exitCode == 0) out.toString
        else {
          val message = s"Failed to compile React jsx script code! (exit code: ${exitCode})\n\n${err.toString}"
          log.error(message)
          throw new AssetsPrecompileFailureException(message)
        }
      }
    } else {
      // Compiler on the Rhino JS engine
      val context = Context.enter
      val compileScope = context.newObject(globalScope)
      compileScope.setParentScope(globalScope)
      compileScope.put("source", compileScope, jsxCode)
      val compilerScript = s"global.JSXTransformer.transform(source).code;"

      try context.evaluateString(compileScope, compilerScript, "skinny.assets.ReactCompiler", 0, null).toString
      catch {
        case e: JavaScriptException =>
          val message = s"Failed to compile jsx template code! (${e.getMessage})"
          log.error(message)
          throw new AssetsPrecompileFailureException(message)
      }
    }
  }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy