Using Jurassic to precompile a JsRender template server-side

999 views Asked by At

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.

2

There are 2 answers

8
Marat Gallyamov On BEST ANSWER

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);
    }
};
1
BorisMoore On

JsRender can be used without jQuery.

The way it works is that for calls like var compiledTemplate = $.templates(...);, if jQuery is not loaded then you should set $ = window.jsviews (or equivalent, such as this.jsviews, when on the server).

So really you are calling:

compiledTemplate = this.jsviews.templates(...);

There is an example here: http://borismoore.github.io/jsrender/demos/step-by-step/20_without-jquery.html (code here https://github.com/BorisMoore/jsrender/blob/master/demos/step-by-step/20_without-jquery.html).

There are also a lot of unit tests here: http://borismoore.github.io/jsrender/test/unit-tests-jsrender-no-jquery.html (code: https://github.com/BorisMoore/jsrender/blob/master/test/unit-tests-jsrender-no-jquery.html).

More documentation/samples for this scenario will be added later on http://www.jsviews.com.