Skip to content

Instantly share code, notes, and snippets.

@mattpowell
Created September 5, 2011 23:59
Show Gist options
  • Save mattpowell/1196209 to your computer and use it in GitHub Desktop.
Save mattpowell/1196209 to your computer and use it in GitHub Desktop.
Compile Soy Templates on the Fly w/ Node

Overview

Small hack to help keep me productive when using Google Closure Templates. This'll set an external filter for .soy files so that they can be compiled and returned as executable javascript. I've done this by modifying SoyToJsSrcCompiler to accept a new cmdline arg: --mattHack. If you pass --mattHack, then --outputPathFormat and passed-in soy filepaths will be ignored. If --mattHack is defined then SoyToJsSrcCompiler will spit stdin to a temporary file and pass that to SoyCompiler, business-as-usual. SoyCompiler will compile the template to a temporary file which SoyToJsSrcCompiler will then write to stdout. To get a compiled template, you can run this:

java -jar SoyToJsSrcCompiler.jar --mattHack<CoolSoyTemplate.soy

Working w/ apache

To get the above working w/ apache we'll setup an external filter to call node (which'll do all the heavy lifting). A filter, as defined by: ExtFilterDefine, will pipe what would've been apache's response to a subprocess AND THEN take the output from the subprocess and send that back to the browser (slash original requestor). So our filter will catch all requests to a .soy file and apply the CompileSoyTemplate filter. The CompileSoyTemplate filter will pass the contents of the .soy file to node which will pass that to SoyToJsSrcCompiler.jar with the --mattHack argument. Node then takes the response from SoyToJsSrcCompiler.jar and sends that back to apache, who then sends it to the browser (or wherever).

You can then reference that template directly from a script tag:

<script src="CoolSoyTemplate.soy"></script>

Or from a goog.require statement (assuming your deps.js is up to date :):

goog.require('Cool.Soy.Template');

Building a new SoyToJsSrcCompiler jar

Oh ya, to apply the SoyToJsSrcCompiler.java diff and build a new custom version SoyToJsSrcCompiler.jar, do the following:

  • svn checkout http://closure-templates.googlecode.com/svn/trunk/ closure-templates-src
  • patch -p0 -i SoyToJsSrcCompiler.java.diff
  • ant -f closure-templates-src/build.xml SoyToJsSrcCompiler
  • file closure-templates-src/build/SoyToJsSrcCompiler.jar

Also, sorry for the shitty java code. I'll update that. One day. Maybe. But probably not :/

var fs=require('fs'),
//rawTemplate=fs.readFileSync('/dev/stdin').toString(),
spawn = require('child_process').spawn,
java=spawn(
'java'
,['-jar','build/SoyToJsSrcCompiler.jar','--mattHack','--shouldProvideRequireSoyNamespaces','--codeStyle','concat']
,{
cwd:"/Users/mpowell/Downloads/closure-templates-src/"
//,customFds:[process.stdin,process.stdout,process.stderr]
}
);
java.stdout.pipe(process.stdout);
java.stderr.pipe(process.stdout);//things worked better by sending java's stderr to the main stdout :/
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.pipe(java.stdin);
process.stdin.on('end', function () {
//console.log('done reading');
});
AddType text/html .soy
#apache needs node in it's $PATH when you start it. Also, you may need to type in the full path to CompileSoyTemplate.js.
ExtFilterDefine CompileSoyTemplate mode=output intype=text/html outtype=text/javascript cmd="node CompileSoyTemplate.js"
AddOutputFilter CompileSoyTemplate soy
Index: java/src/com/google/template/soy/SoyToJsSrcCompiler.java
===================================================================
--- java/src/com/google/template/soy/SoyToJsSrcCompiler.java (revision 19)
+++ java/src/com/google/template/soy/SoyToJsSrcCompiler.java (working copy)
@@ -29,8 +29,7 @@
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
-import java.io.File;
-import java.io.IOException;
+import java.io.*;
import java.util.List;
@@ -57,7 +56,6 @@
private String inputPrefix = "";
@Option(name = "--outputPathFormat",
- required = true,
usage = "[Required] A format string that specifies how to build the path to each" +
" output file. If not generating localized JS, then there will be one output" +
" JS file (UTF-8) for each input Soy file. If generating localized JS, then" +
@@ -167,6 +165,10 @@
usage = "Specifies the full class names of Guice modules for function plugins and" +
" print directive plugins (comma-delimited list).")
private String pluginModules = "";
+
+ @Option(name = "--mattHack",
+ usage = "[Matt's hack. Will take stdin compile it and print to stdout.")
+ private boolean mattHack = false;
/** The remaining arguments after parsing command-line flags. */
@Argument
@@ -189,12 +191,15 @@
private void execMain(String[] args) throws IOException, SoySyntaxException {
-
+
CmdLineParser cmdLineParser = MainClassUtils.parseFlags(this, args, USAGE_PREFIX);
- if (arguments.size() == 0) {
+
+
+
+ if (arguments.size() == 0 && !mattHack) {
MainClassUtils.exitWithError("Must provide list of Soy files.", cmdLineParser, USAGE_PREFIX);
}
- if (outputPathFormat.length() == 0) {
+ if (outputPathFormat.length() == 0 && !mattHack) {
MainClassUtils.exitWithError(
"Must provide the output path format.", cmdLineParser, USAGE_PREFIX);
}
@@ -212,8 +217,33 @@
// Create SoyFileSet.
SoyFileSet.Builder sfsBuilder = injector.getInstance(SoyFileSet.Builder.class);
String inputPrefixStr = inputPrefix;
- for (String arg : arguments) {
- sfsBuilder.add(new File(inputPrefixStr + arg));
+
+
+ File templateOut=null;
+ if (mattHack){
+ try {
+
+ File templateIn=File.createTempFile("closure-template-compiler-in",".tmp");
+ templateOut=File.createTempFile("closure-template-compiler-out",".tmp");
+ templateIn.deleteOnExit();
+ templateOut.deleteOnExit();
+ BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
+ String str="";
+ BufferedWriter out = new BufferedWriter(new FileWriter(templateIn));
+ while ((str = stdin.readLine()) != null) out.write(str + System.getProperty("line.separator"));
+ out.close();
+ sfsBuilder.add(templateIn);
+ outputPathFormat=templateOut.getPath();
+
+
+ } catch (java.io.IOException e) { System.out.println(e); }
+
+ }else {
+
+ for (String arg : arguments) {
+ sfsBuilder.add(new File(inputPrefixStr + arg));
+ }
+
}
String cssHandlingSchemeUc = cssHandlingScheme.toUpperCase();
sfsBuilder.setCssHandlingScheme(
@@ -236,6 +266,15 @@
sfs.compileToJsSrcFiles(
outputPathFormat, inputPrefix, jsSrcOptions, locales, messageFilePathFormat);
}
+
+
+ if (mattHack && templateOut.exists()){
+ BufferedReader compiledTemplate = new BufferedReader(new FileReader(templateOut));
+ String compiledTemplateStr="";
+ while ((compiledTemplateStr = compiledTemplate.readLine()) != null) System.out.println(compiledTemplateStr);
+
+ }
+
}
}
@bentruyman
Copy link

While you call this a hack, there's some serious (and impressive) nerdery going on here. Nice work.

@mattpowell
Copy link
Author

Hey, thanks! Would prefer to parse soy templates in straight javascript, but this'll have to do in the mean time ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment