Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Rhino based compilation of Coffeescript for Coffeescript filter #6

Merged
merged 1 commit into from

2 participants

@daggerrz
Collaborator

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
Collaborator

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
Owner
@jstrachan
Owner

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

@daggerrz
Collaborator

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
This page is out of date. Refresh to see the latest.
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
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.