Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

nunjucks function arguments arrive undefined

I have been using nunjucks for several months, and have found it to be a great templating engine. However, this morning I ran into an issue that seems simple, but I simply cannot figure it out. I am hoping that another set of eyes can help point to the solution.

The problem: If I pass a function to a template, any arguments passed to that function are undefined inside the function body.

Values and objects can be passed to templates without a problem, and if I pass a function I can log to the console from within the function (so I know the function itself is there), but the arguments are all undefined.

This initially seemed like something that could be solved with a closure, but 1) I dont see closures in any examples I can find, and 2) when I tried a closure I found that they too receive undefined arguments.

Through the course of the day I have pared my code back to just about the simplest possible case and still cant figure this out:

The template:

<div>

<p>{{ value }}</p>

<p>{{ object|pretty }}</p>

<p>{{ func(4) }}</p>

<p>{{ args(4)|pretty }}</p>

<p>{{ local(4) }}</p>

</div>

The code that renders the template (this is inside of a requirejs define, not shown):

    var nunjucks = require('lib/nunjucks-slim.min'), // v1.3.4
        env = new nunjucks.Environment(null), // global templates
        $tgt = $('#test'),
        local;

    env.addGlobal('value', 3);

    env.addGlobal('object', {
        a: 2
    });

    env.addFilter('pretty', function (obj) {
        return JSON.stringify(obj, null, 2);
    });

    env.addGlobal('func', function (val) {
        return 'func: ' + val;
    });

    env.addGlobal('args', function () {
        return arguments;
    });

    local = function (val) {
        return 'local: ' + val;
    };

    $tgt.html(env.render('test.nj', {
        'local': local
    }));

and the rendered HTML looks like this:

3                  // value

{ "a": 2 }         // object|pretty

func: undefined    // func

{}                 // args

local: undefined   // local

So, a value is fine. The object works as well, and even looks nice after passing it through the "pretty" filter.

However, a value passed to "func" becomes undefined in the function body, and using the arguments variable inside the function doesnt help. Furthermore, passing a function directly into the template context (local) doesnt work either.

Filters (which are basically just functions) work fine, but regular functions dont, whether they are passed as part of the template context or as a global.

A few notes that might help:

  • I am using requirejs although (except for importing nunjucks itself) I have tried to eliminate it from the equation.

  • This example uses nunjucks v1.3.4. Through the course of debugging I tried to verify this behavior in v2.1 but it appears that v2.x may have broken requirejs compatibility, and I didnt want to tackle two problems in parallel.

  • I am precompiling my templates using grunt-nunjucks, then uglifying the result. The relevant portions of my Gruntfile are as follows:

    module.exports = function (grunt) {
    'use strict';
    
    grunt.initConfig({
    
    pkg: grunt.file.readJSON('package.json'),
    
    nunjucks: {
        common: {
            baseDir: 'site/',
            src: [
                'site/core/**/*.nj'
            ],
            dest: 'application/static/js/common.js'
        },
    },
    
    uglify: {
        templates: {
            options: {
                preserveComments: false,
                mangle: true,
                compress: {
                    dead_code: true,
                    loops: true,
                    conditionals: true,
                    booleans: true,
                    unused: true,
                    if_return: true,
                    join_vars: true,
                    drop_console: true
                }
            },
            files: {
                'application/static/js/common.min.js': ['application/static/js/common.js'],
            }
        }
    },
    });
    
    grunt.loadNpmTasks('grunt-nunjucks');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    };
    

The test template (shown above) is one of several that are compiled into common.js, then uglified into common.min.js using the settings above. Note that I have tried compiling the templates without uglifying, but get the same result.

This is pretty basic functionality so If this were a bug I would expect to see lots of references on SE and in the issue log, but I could hardly find any. I assume I am missing something really obvious, but just cant seem to find it.

Any thoughts?

like image 649
gary Avatar asked Oct 02 '15 23:10

gary


1 Answers

After continuing debug I eventually took uglification and requirejs out of the equation with the following test setup:

index.html:

<html>
<head>
    <script src="nunjucks-slim.js"></script>
    <script src="common.js"></script>
</head>

<body>

    <script src="test.js"></script>
</body>

</html>

my template, which is precompiled into common.js:

<div>

<p>{{ value }}</p>

<p>{{ object|pretty }}</p>

<p>{{ func(4) }}</p>

<p>{{ func(x) }}</p>

<p>{{ args(4)|pretty }}</p>

<p>{{ local(4) }}</p>

<p>{{ local(x) }}</p>

</div>

test.js is the code which exercises the template:

var env = new nunjucks.Environment(null),
    local;

env.addGlobal('value', 3);

env.addGlobal('object', {
    a: 2
});

env.addFilter('pretty', function (obj) {
    return JSON.stringify(obj, null, 2);
});

env.addGlobal('func', function (val) {
    return 'func: ' + val;
});

env.addGlobal('args', function () {
    return arguments;
});

local = function (val) {
    return 'local: ' + val;
};

console.log(env.render('core/root/test.nj', {
    'local': local,
    'x': 4
}));

Finally, the output which demonstrates the problem (with added comments):

<div>

<p>3</p>    // value

<p>{
  "a": 2    // object|pretty
}</p>

<p>func: undefined</p>    // func(4)

<p>func: undefined</p>    // func(x)

<p>{}</p>    // args(4)|pretty

<p>local: undefined</p>    // local(4)

<p>local: undefined</p>    // local(x)

</div>

I spent several hours stepping through the compiled template code to try to find the issue (which was very informative, by the way), then face-palmed - since I had eliminated requirejs I could try once more with nunjucks v2.1.0.

A few clicks later, I had working test code:

<div>

<p>3</p>

<p>{
  a: 2
}</p>

<p>func: 4</p>

<p>func: 4</p>

<p>{
  0: 4
}</p>

<p>local: 4</p>

<p>local: 4</p>

</div>

So, while it would have been nice to figure out and solve the problem (with v1.3.4), the answer seems to be "upgrade to nunjucks v2.1.0".

like image 98
gary Avatar answered Oct 24 '22 03:10

gary