Rendering issue: a param from a previous render call gets injected
korya opened this issue · comments
I have a basic setup: a layout and 2 views built on top of the layout. When I render the first view everything is OK, but when I render second view unless
helper behaves in a weird way: it uses context param value of title
from a previous call to render.
'use strict';
var templates = require('templates');
var app = templates();
/**
* Defin the engine to use
*/
app.engine('txt', require('engine-handlebars'));
app.option('engine', 'txt');
/**
* Create view collections
*/
app.create('pages');
app.create('layouts', {viewType: 'layout'});
app.create('headings', {viewType: 'partial'});
/**
* Add some views
*/
// layout
app.layout('base', {
data: {},
content: '<div>{{#unless title}}{{ title }}{{/unless}}<span>{% body %}</span>{{ title }}</div>',
});
app.option('layout', 'base');
// pages
app.page('one', {
data: {title: 'title-A'},
content: 'ONE',
});
app.page('two', {
data: {},
content: 'TWO',
});
/**
* Render views
*/
app.render('one', {}, function(err, res) {
if (err) return console.log(err.stack);
console.log(res.content);
});
app.render('two', {}, function(err, res) {
if (err) return console.log(err.stack);
console.log(res.content);
});
Expected output:
<div><span>ONE</span>title-A</div>
<div><span>TWO</span></div>
Actual output:
<div><span>ONE</span>title-A</div>
<div>title-A<span>TWO</span></div>
.render
is async, do this instead:
app.render('one', {}, function(err, res) {
if (err) return console.log(err.stack);
console.log(res.content);
app.render('two', {}, function(err, res) {
if (err) return console.log(err.stack);
console.log(res.content);
});
});
Thanks.
One more clarification. As far as I can see, the "bad" package having a race condition is handlebars-engine
. Is it right? and can it be easily fixed? or is this "global-state" problem inherent and it comes from Handlebars module?
I have provided a minimal example of the problem occurring in my setup, and indeed your solution solves the problem in that example. However my real setup is a bit more complicated. I am running two instances of Templates in the same process, and that causes the problem. Below is an updated example demonstrating the problem:
'use strict';
const { promisify } = require('util');
const Templates = require('templates');
const getTemplateApp = () => {
const app = new Templates();
app.engine('txt', require('engine-handlebars'));
app.option('engine', 'txt');
app.create('pages');
app.create('layouts', {viewType: 'layout'});
app.create('headings', {viewType: 'partial'});
app.layout('base', {
data: {},
content: '<div>{{#unless hideTitle}}{{ title }}{{/unless}}<span>({% body %})</span>{{ hideTitle }}</div>',
});
app.option('layout', 'base');
return app;
};
const app1 = getTemplateApp();
const app2 = getTemplateApp();
app1.page('zero', {
data: {},
content: 'ZERO',
});
app2.page('one', {
data: {title: 'title-A', hideTitle: true},
content: 'ONE',
});
app2.page('two', {
data: {},
content: 'TWO',
});
/**
* Render views
*/
app1.render('zero', {}, function(err, res) {
if (err) return console.log(err.stack);
console.log(res.content);
app2.render('one', {}, function(err, res) {
if (err) return console.log(err.stack);
console.log(res.content);
app2.render('two', {}, function(err, res) {
if (err) return console.log(err.stack);
console.log(res.content);
});
});
});
Expected output:
<div><span>(ZERO)</span></div>
<div><span>(ONE)</span>true</div>
<div><span>(TWO)</span></div>
Actual output:
<div><span>(ZERO)</span></div>
<div><span>(ONE)</span>true</div>
<div>title-A<span>(TWO)</span></div>
Is it a bug? or is it a desired feature and templates
was not designed to be used like that? Is there any workaround?
Can you please just let me know if there is a quick workaround for the issue?
The solution I found is to patch the engine package to support multiple instances per process. I've done by exporting a create()
function similar to what Handlebars package is doing. The actual commit is available at PlanitarInc/engine-handlebars@d9e621f .
@korya thanks for posting back here. I had looked into this and forgot to respond earlier.
I'm glad you found a fix. I haven't tried it yet, but your fix makes me think that using Object.assign
in the code above would work too:
app.engine('txt', Object.assign({}, require('engine-handlebars')));
When I get a chance, I'll try that out. Otherwise, we might want to look into your changes.
Closing based on age, and this was a support request more than a bug report. Also, we'll be handling engines differently in the pending 2.0 release.