Created
April 24, 2012 21:07
-
-
Save maffoo/2483787 to your computer and use it in GitHub Desktop.
Ember template precompiler for play
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sbt._ | |
import Keys._ | |
import PlayProject._ | |
object ApplicationBuild extends Build { | |
val emberOptions = SettingKey[Seq[String]]("ember-options") | |
val emberEntryPoints = SettingKey[PathFinder]("ember-entry-points") | |
def EmberCompiler(ember: String) = { | |
val compiler = new com.netwallet.ember.EmberCompiler(ember) | |
AssetsCompiler("ember-handlebars", | |
(_ ** "*.handlebars"), | |
emberEntryPoints, | |
{ (name, min) => "javascripts/" + name + ".pre" + (if (min) ".min.js" else ".js") }, | |
{ (handlebarsFile, options) => | |
val (jsSource, dependencies) = compiler.compileDir(handlebarsFile, options) | |
// Any error here would be because of Handlebars, not the developer; | |
// so we don't want compilation to fail. | |
import scala.util.control.Exception._ | |
val minified = catching(classOf[CompilationException]) | |
.opt(play.core.jscompile.JavascriptCompiler.minify(jsSource, Some(handlebarsFile.getName()))) | |
(jsSource, minified, dependencies) | |
}, | |
emberOptions | |
) | |
} | |
val appName = "NetWallet" | |
val appVersion = "1.0-SNAPSHOT" | |
val appDependencies = Seq( | |
// Add your project dependencies here, | |
) | |
val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings( | |
// Add your own project settings here | |
emberEntryPoints <<= (sourceDirectory in Compile)(base => base / "assets" / "templates"), | |
emberOptions := Seq.empty[String], | |
resourceGenerators in Compile <+= EmberCompiler(ember = "ember-0.9.6.js") | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.netwallet.ember | |
import java.io._ | |
import play.api._ | |
class EmberCompiler(ember: String) { | |
import org.mozilla.javascript._ | |
import org.mozilla.javascript.tools.shell._ | |
import scala.collection.JavaConverters._ | |
import scalax.file._ | |
/** | |
* find a file with the given name in the current directory or any subdirectory | |
*/ | |
private def findFile(name: String): Option[File] = { | |
def findIn(dir: File): Option[File] = { | |
for (file <- dir.listFiles) { | |
if (file.isDirectory) { | |
findIn(file) match { | |
case Some(file) => return Some(file) | |
case None => // keep trying | |
} | |
} else if (file.getName == name) { | |
return Some(file) | |
} | |
} | |
None | |
} | |
findIn(new File(".")) | |
} | |
private lazy val compiler = { | |
val ctx = Context.enter; ctx.setOptimizationLevel(-1) | |
val global = new Global; global.init(ctx) | |
val scope = ctx.initStandardObjects(global) | |
// set up global objects that emulate a browser context | |
ctx.evaluateString(scope, | |
""" | |
// make window an alias for the global object | |
var window = this, | |
document = { | |
createElement: function(type) { | |
return { | |
firstChild: {} | |
}; | |
}, | |
getElementById: function(id) { | |
return []; | |
}, | |
getElementsByTagName: function(tagName) { | |
return []; | |
} | |
}, | |
location = { | |
protocol: 'file:', | |
hostname: 'localhost', | |
href: 'http://localhost:80', | |
port: '80' | |
}, | |
console = { | |
log: function() {}, | |
info: function() {}, | |
warn: function() {}, | |
error: function() {} | |
} | |
// make a dummy jquery object just to make ember happy | |
var jQuery = function() { return jQuery; }; | |
jQuery.ready = function() { return jQuery; }; | |
jQuery.inArray = function() { return jQuery; }; | |
jQuery.jquery = "1.7.1"; | |
var $ = jQuery; | |
// our precompile function uses Ember to do the precompilation, | |
// then converts the compiled function to its string representation | |
function precompile(string) { | |
return Ember.Handlebars.precompile(string).toString(); | |
} | |
""", | |
"browser.js", | |
1, null) | |
// load ember | |
val emberFile = findFile(ember).getOrElse(throw new Exception("ember: could not find " + ember)) | |
ctx.evaluateString(scope, Path(emberFile).slurpString, ember, 1, null) | |
val precompileFunction = scope.get("precompile", scope).asInstanceOf[Function] | |
Context.exit | |
(source: File) => { | |
val handlebarsCode = Path(source).slurpString.replace("\r", "") | |
Context.call(null, precompileFunction, scope, scope, Array(handlebarsCode)).asInstanceOf[String] | |
} | |
} | |
def compileDir(root: File, options: Seq[String]): (String, Seq[File]) = { | |
val dependencies = Seq.newBuilder[File] | |
val output = new StringBuilder | |
output ++= "(function() {\n" + | |
"var template = Ember.Handlebars.template,\n" + | |
" templates = Ember.TEMPLATES = Ember.TEMPLATES || {};\n\n" | |
def addTemplateDir(dir: File, path: String) { | |
for { | |
file <- dir.listFiles.toSeq.sortBy(_.getName) | |
name = file.getName | |
} { | |
if (file.isDirectory) { | |
addTemplateDir(file, path + name + "/") | |
} | |
else if (file.isFile && name.endsWith(".handlebars")) { | |
val templateName = path + name.replace(".handlebars", "") | |
println("ember: processing template %s".format(templateName)) | |
val jsSource = compile(file, options) | |
dependencies += file | |
output ++= "templates['%s'] = template(%s);\n\n".format(templateName, jsSource) | |
} | |
} | |
} | |
addTemplateDir(root, "") | |
output ++= "})();\n" | |
(output.toString, dependencies.result) | |
} | |
private def compile(source: File, options: Seq[String]): String = { | |
try { | |
compiler(source) | |
} catch { | |
case e: JavaScriptException => | |
val line = """.*on line ([0-9]+).*""".r | |
val error = e.getValue.asInstanceOf[Scriptable] | |
throw ScriptableObject.getProperty(error, "message").asInstanceOf[String] match { | |
case msg @ line(l) => CompilationException( | |
msg, | |
source, | |
Some(Integer.parseInt(l))) | |
case msg => CompilationException( | |
msg, | |
source, | |
None) | |
} | |
case e => | |
throw CompilationException( | |
"unexpected exception during Ember compilation (file=%s, options=%s, ember=%s): %s".format( | |
source, options, ember, e | |
), | |
source, | |
None) | |
} | |
} | |
} | |
case class CompilationException(message: String, file: File, atLine: Option[Int]) extends PlayException( | |
"Compilation error", message) with PlayException.ExceptionSource { | |
def line = atLine | |
def position = None | |
def input = Some(scalax.file.Path(file)) | |
def sourceName = Some(file.getAbsolutePath) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment