twitter / hogan.js

A compiler for the Mustache templating language
http://twitter.github.io/hogan.js
Apache License 2.0
5.14k stars 432 forks source link

Caching bug with template inheritance #206

Open David-Mulder opened 9 years ago

David-Mulder commented 9 years ago

Ok, so I am not sure of all terminology and everything (pretty new to mustache still), but I was able to boil it down to a pretty small test case that shows the functionality

Express

router.get('/a', function(req, res) {
    res.render("A");
});
router.get('/b', function(req, res) {
    res.render("B");
});

A.mustache

{{<C}}
     {{$C}}
          A
     {{/C}}
{{/C}}

B.mustache

{{<C}}
     {{$C}}
          B
     {{/C}}
{{/C}}

C.mustache

{{>D}}
C: {{$C}}{{/C}}

D.mustache

D: {{$C}}{{/C}}

Now, load up /a and then /b, the first will output D: AA C: AA as expected, the second D: AA C: BB, now reload node.js and load up /b first and then /a and you get the exact opposite (D: BB gets 'stuck'). I have tried figuring this one out myself, but I got lost in the source code entirely.

TenaciousDarius commented 9 years ago

+1 We've been seeing this as well. The issue arises when you cache partials after compiling them and have an inheritance hierarchy similar to the following:

Grandparent ^ Parent ^ Child1 Child2

When Child1 is rendered, Parent has Child1 set as one of it's partials. Then when Child2 is rendered, Parent isn't reloaded and it's the content of Child1 which we see. The opposite is true if we load Child2 first. If the partials are loaded from the FS on every page load the problem goes away.

We've created a snippet which illustrates the problem. The second time the tests are run, the output matches the first test rather than the second.

var Hogan = Hogan || require('./lib/hogan');

var tests = [{
    template: "{{<parent}}{{$a}}c{{/a}}{{/parent}}",
    partials: {
        parent: "{{<grandParent}}{{$a}}p{{/a}}{{/grandParent}}",
        grandParent: "{{$a}}g{{/a}}"
    },
    expected: "c"
}, {
    template: "{{<parent}}{{/parent}}",
    partials:{
        parent: "{{<grandParent}}{{$a}}p{{/a}}{{/grandParent}}",
        grandParent: "{{$a}}g{{/a}}"
    },
    expected: "p"
}],
    cache={},
    getResults=function(test){
        var output = cache[test.template].template.render({},   cache[test.template].partials);
        console.log('---------------');
        console.log(test.template);
        console.log("output:"+output);
        console.log("expected:"+test.expected);
    };

tests.forEach(function(test) {
    var partials = {};

    for (var i in test.partials) {
        partials[i] = Hogan.compile(test.partials[i]);
    }
    cache[test.template]={
        template: Hogan.compile(test.template),
        partials:partials
    };
    getResults(test);

});

/* renders reference previous paritals */
tests.forEach(function(test) {
    getResults(test);
});

Has anyone else seen this issue or found a solution please? We're at the point of avoiding inheritance and using composition instead but would be useful to know if there's a solution. Thanks for any help in advance.

David-Mulder commented 9 years ago

No such luck here at least@finding a solution. I somehow worked myself around it, but it's an ugly bug indeed.

TenaciousDarius commented 9 years ago

I see, sad panda. We'll work around it by using composition rather than inheritance I guess. Thanks very much for responding. I might take a closer looks at how hogan works over the weekend to see if a fix presents itself.

paulwib commented 8 years ago

+1

Same thing here, here's another set of steps to reproduce:

var hogan = require('hogan.js');

var layouts = {
  outerLayout: hogan.compile('OUTER: {{$inner}}Inner stuff goes here{{/inner}}'),
  innerLayout: hogan.compile('{{<outerLayout}}{{$inner}}INNER: {{$content}}Content goes here{{/content}}{{/inner}}{{/outerLayout}}')
};
var page1 = hogan.compile('{{<innerLayout}}{{$content}}Page 1{{/content}}{{/innerLayout}}');
var page2 = hogan.compile('{{<innerLayout}}{{$content}}Page 2{{/content}}{{/innerLayout}}');

console.log(page1.render({}, layouts));
console.log(page2.render({}, layouts));
$ node index.js
OUTER: INNER: Page 1
OUTER: INNER: Page 1

If you switch the rendering order this will change to "Page 2".

setthase commented 8 years ago

Any news about this?