Search code examples
javascripttemplatesjsrenderjurassicprecompiled-templates

Using Jurassic to precompile a JsRender template server-side


I'm attempting to precompile JsRender templates from a class library written in C#, using the Jurassic script engine to execute JsRender.

Here is my code:

var engine = new Jurassic.ScriptEngine();
engine.Execute(JsRenderContents);    
var precompiledTemplate = engine.CallGlobalFunction<string>(String.Concat("$.templates(\"", template, "\");"));

I've taken the JavaScript function call, $.templates(), from this page which states that

$.templates(markupOrSelector) returns: Compiled template object

And my sample HTML template is simply

<li>{{:Name}}</li>

However, my code produces the exception:

'$.templates("<li>{{:Name}}</li>");' is not a function.

Now, I'm not 100% clear whether I can use the $ operator without jQuery being present. The author includes jQuery in several of his examples, but also states that jQuery is not required.

So what's going wrong? Is the documentation out of date for the version of JsRender taken from GitHub on the same day that I posted this question? (I'm aware that JsRender is still in beta.) Or maybe I'm misusing Jurassic?

EDIT:

I believe this is actually more a Jurassic question than a JsRender question. Specifically, I think this relates to Jurassic's global object, as JsRender is wrapped in an Immediately Invoked Function which passes this, and I'm not certain than Jurassic provides this.

It appears that I'm not the first to face this question. I've taken the advice from the last post on this page and changed my code to the following:

var engine = new Jurassic.ScriptEngine();
engine.Execute(JsRenderContents);
engine.Global["window"] = engine.Global;
var precompiledTemplate = engine.CallGlobalFunction<string>(String.Concat("window.jsviews.templates(\"", template, "\");"));

which didn't work - probably because JsRender's IIF still passes this instead of window, and I don't want to modify the script.

Can anyone help push this forward? How can I call any JsRender function from Jurassic, given that Jurassic... I don't know... perhaps there's some notional difference in the way that Jurassic implements the global object.


Solution

  • I'm using jsRender + Jurassic to precompile my templates and generate js-files in T4. I spent a lot of time solving this problem and didn't find the answer, but read some articles, that helped.

    See my code. It's working in my case. I'm sure I can help you to solve the issue, if this will not help:

    var engine = new Jurassic.ScriptEngine();
    
    var jsRenderPath = "/pathToDir/jsrender.js";
    var jsUnevalPath = "/pathToDir/jsRenderUtils.js";
    engine.ExecuteFile(jsRenderPath);
    engine.ExecuteFile(jsUnevalPath);
    
    engine.Evaluate("function renderTemplate(name, markup) { var tmpl = this.jsviews.templates(name, markup); return uneval(tmpl); }");
    
    var compiledTemplateString = engine.CallGlobalFunction<string>("renderTemplate", templateName, templateString);
    
    var result = "$.templates['" + templateName + "'] = " + compiledTemplateString + ";";
    

    jsRenderUtils.js contents (uneval function)

    function uneval(obj, known) {
        var root = (known === undefined), result;
        known = known || [];
    
        // some values fail eval() if not wrapped in a ( ) parenthesises
        var wrapRoot = function (result) {
            return root ? ("(" + result + ")") : result;
        };
    
        // special objects
        if (obj === null)
            return "null";
        if (obj === undefined)
            return "undefined";
        if (obj !== obj) // isNaN does type coercion, so can't use that.
            return "NaN";
        if (obj === Infinity)
            return "Infinity";
        if (obj === -Infinity)
            return "-Infinity";
    
        // atoms
        switch (typeof obj) {
            case 'function':
                return wrapRoot(obj.toString());
            case 'number':
            case 'boolean':
                return obj.toString();
            case 'string':
                return "\"" + obj.toString() + "\"";
        }
    
        // circular reference check for non-atoms
        if (known.indexOf(obj) !== -1)
            return "null";//throw new Error("Circular references detected while unevaling.");
    
        known.push(obj);
    
        // specialized types
        if (obj instanceof Array)
            return "[" + obj.map(function (o) { return uneval(o, known); }).join(",") + "]";
    
        if (obj instanceof Date)
            return wrapRoot("new Date('" + obj.toString() + "')");
    
        // hashes
        var key, pairs = [];
        for (key in obj) {
            var val;
            switch (key) {
                case "render":
                    val = "$.fn.render";
                    break;
                case "markup":
                    val = "null";
                    break;
                default:
                    val = uneval(obj[key], known);
            }
            pairs.push("\r\n" + key + " : " + val);
        }
    
        return wrapRoot("{" + pairs.join(",") + "}");
    
    };
    

    UPDATED: If you will render templates without jquery, you should add this:

    $ = window.jsviews;
    $.fn = {
        render: function (data, view, j, u) {
            return this.fn(data, view, j, u);
        }
    };