Skip to content

Loading…

Rhino based compilation of Coffeescript for Coffeescript filter #6

Merged
merged 1 commit into from

2 participants

@daggerrz

Quick win for server based compilation (based on https://github.com/daggerrz/scala-coffee), but I have a few open questions that need to be answered before merging:

  • How to get access to line number information to give user a more informative error message on Coffee compilation error
  • Is there any reason to keep the browser based compilation at all (even when in dev-mode)?
  • How to handle the Rhino dep. Move to separate module?
@daggerrz

Open questions:

  • How to get access to line number information to give user a more informative error message on Coffee compilation error
  • Is there any reason to keep the browser based compilation at all (even when in dev-mode)?
  • How to handle the Rhino dep. Move to separate module?
@jstrachan jstrachan merged commit 1c73f37 into scalate:master
@jstrachan
@jstrachan

An optional dependency on rhino sounds fine to me; added an enable/disable flag too. Still pondering the line number issue...

@daggerrz

Sounds good, most will probably opt to use the compiler once it's there and for those who don't want the extra dep, a flag should solve their problems. Line numbers would be really useful, but it looks like that will require some refactoring of the Scalate pipeline. I'm not familiar enough with the source to make any suggestions, though...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
View
8 scalate-core/pom.xml
@@ -92,6 +92,14 @@
<optional>true</optional>
</dependency>
+ <!-- Coffescript compiler -->
+ <dependency>
+ <groupId>rhino</groupId>
+ <artifactId>js</artifactId>
+ <version>1.7R2</version>
+ <optional>true</optional>
+ </dependency>
+
<!-- testing -->
<dependency>
<groupId>org.scalatest</groupId>
View
8 scalate-core/src/main/resources/org/fusesource/scalate/filter/coffee-script.js
8 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
79 scalate-core/src/main/scala/org/fusesource/scalate/filter/CoffeeScriptFilter.scala
@@ -19,6 +19,8 @@ package org.fusesource.scalate
package filter
import org.fusesource.scalate.support.RenderHelper
+import org.mozilla.javascript._
+import java.io.InputStreamReader
/**
* Surrounds the filtered text with &lt;script&gt; and CDATA tags.
@@ -31,16 +33,71 @@ object CoffeeScriptFilter extends Filter {
def filter(context: RenderContext, content: String) = {
- // TODO: 'if( !context.engine.isDevelopmentMode )' then
- // we should try to compile the coffescript on the server
- // side and cache it.
+ if( !context.engine.isDevelopmentMode )
+ Compiler.compile(content, Some(context.currentTemplate)).fold({ error =>
+ throw new CompilerException(error.message, Nil)
+ }, { coffee =>
+ """<script type='text/javascript'>
+ | #<![CDATA[
+ | """.stripMargin+RenderHelper.indent(" ", coffee)+"""
+ | #]]>
+ |</script>""".stripMargin
+ })
+ else {
+ context.attributes("REQUIRES_COFFEE_SCRIPT_JS") = "true"
+ """<script type='text/coffeescript'>
+ | #<![CDATA[
+ | """.stripMargin+RenderHelper.indent(" ", content)+"""
+ | #]]>
+ |</script>""".stripMargin
+ }
+ }
+
+ /**
+ * A Scala / Rhino Coffeescript compiler.
+ */
+ object Compiler {
+
+ /**
+ * Compiles a string of Coffeescript code to Javascript.
+ *
+ * @param code the Coffeescript code
+ * @param sourceName a descriptive name for the code unit under compilation (e.g a filename)
+ * @param bare if true, no function wrapper will be generated
+ * @return the compiled Javascript code
+ */
+ def compile(code: String, sourceName: Option[String] = None, bare : Boolean = false)
+ : Either[CompilationError, String] = withContext { ctx =>
+ val scope = ctx.initStandardObjects()
+ ctx.evaluateReader(
+ scope,
+ new InputStreamReader(getClass.getResourceAsStream("coffee-script.js"), "UTF-8"),
+ "coffee-script.js", 1, null
+ )
+
+ val coffee = scope.get("CoffeeScript", scope).asInstanceOf[NativeObject]
+ val compileFunc = coffee.get("compile", scope).asInstanceOf[Function]
+ val opts = ctx.evaluateString(scope, "({bare: %b});".format(bare), null, 1, null)
- context.attributes("REQUIRES_COFFEE_SCRIPT_JS") = "true"
- """<script type='text/coffeescript'>
- | #<![CDATA[
- | """.stripMargin+RenderHelper.indent(" ", content)+"""
- | #]]>
- |</script>""".stripMargin
+ try {
+ Right(compileFunc.call(ctx, scope, coffee, Array(code, opts)).asInstanceOf[String])
+ } catch {
+ case e : JavaScriptException =>
+ Left(CompilationError(sourceName, e.getValue.toString))
+ }
+ }
+
+ def withContext[T](f: Context => T): T = {
+ val ctx = Context.enter()
+ try {
+ ctx.setOptimizationLevel(-1) // Do not compile to byte code (max 64kb methods)
+ f(ctx)
+ } finally {
+ Context.exit()
+ }
+ }
}
-
-}
+
+ case class CompilationError(sourceName: Option[String], message: String)
+}
+
View
2 scalate-core/src/test/resources/org/fusesource/scalate/scaml/coffee.scaml
@@ -0,0 +1,2 @@
+:coffeescript
+ alert "Hello, Coffee!"
View
18 scalate-core/src/test/scala/org/fusesource/scalate/filter/CoffeeScriptFilterTest.scala
@@ -0,0 +1,18 @@
+package org.fusesource.scalate.filter
+
+import org.fusesource.scalate.TemplateTestSupport
+
+class CoffeeScriptFilterTest extends TemplateTestSupport {
+ test("coffeescript filter") {
+ assertUriOutputContains("/org/fusesource/scalate/scaml/coffee.scaml", """<script type='text/javascript'>
+ #<![CDATA[
+ (function() {
+ alert("Hello, Coffee!");
+ }).call(this);
+
+ #]]>
+</script>
+""")
+ }
+
+}
Something went wrong with that request. Please try again.