fix include scope.blocks bleeding

This commit is contained in:
harttle
2016-11-06 14:57:48 +08:00
parent c100a2e25e
commit 258f4a2073
9 changed files with 95 additions and 52 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ var engine = Liquid({
}); });
app.engine('liquid', engine.express()); // register liquid engine app.engine('liquid', engine.express()); // register liquid engine
app.set('views', __dirname); // specify the views directory app.set('views', ['./partials', './views']); // specify the views directory
app.set('view engine', 'liquid'); // set to default app.set('view engine', 'liquid'); // set to default
app.get('/', function (req, res) { app.get('/', function (req, res) {
+1
View File
@@ -4,6 +4,7 @@
"description": "Express Demo Using Shopify-Liquid", "description": "Express Demo Using Shopify-Liquid",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "harttle", "author": "harttle",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "shopify-liquid", "name": "shopify-liquid",
"version": "1.3.0", "version": "1.3.1",
"description": "Liquid template engine for JavaScript, Node.js and Browser", "description": "Liquid template engine for JavaScript, Node.js and Browser",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
+1
View File
@@ -3,6 +3,7 @@ const Promise = require('any-promise');
const mapSeries = require('./util/promise.js').mapSeries; const mapSeries = require('./util/promise.js').mapSeries;
const RenderBreak = require('./util/error.js').RenderBreak; const RenderBreak = require('./util/error.js').RenderBreak;
const assert = require('./util/assert.js'); const assert = require('./util/assert.js');
const _ = require('./util/underscore.js');
var render = { var render = {
+7 -3
View File
@@ -147,13 +147,17 @@ function matchRightBracket(str, begin) {
return -1; return -1;
} }
exports.factory = function(_ctx, opts) { exports.factory = function(ctx, opts) {
opts = _.assign({ opts = _.assign({
strict: false strict: false,
blocks: {}
}, opts); }, opts);
ctx = _.assign(ctx, {
liquid: opts
});
var scope = Object.create(Scope); var scope = Object.create(Scope);
scope.opts = opts; scope.opts = opts;
scope.scopes = [_ctx || {}]; scope.scopes = [ctx];
return scope; return scope;
}; };
+8
View File
@@ -37,7 +37,15 @@ function isArray(value) {
return value instanceof Array; return value instanceof Array;
} }
function echo(prefix){
return v => {
console.log('[' + prefix + ']', v);
return v;
};
}
exports.isString = isString; exports.isString = isString;
exports.isArray = isArray; exports.isArray = isArray;
exports.forOwn = forOwn; exports.forOwn = forOwn;
exports.assign = assign; exports.assign = assign;
exports.echo = echo;
+6 -1
View File
@@ -17,8 +17,12 @@ module.exports = function(liquid) {
} }
}, },
render: function(scope, hash, register) { render: function(scope, hash, register) {
console.log('include', register.root);
var filepath = Liquid.evalValue(this.value, scope); var filepath = Liquid.evalValue(this.value, scope);
var reg = scope.get('liquid');
var originBlocks = reg.blocks;
reg.blocks = {};
if(this.with){ if(this.with){
hash[filepath] = Liquid.evalValue(this.with, scope); hash[filepath] = Liquid.evalValue(this.with, scope);
} }
@@ -29,6 +33,7 @@ module.exports = function(liquid) {
}) })
.then((html) => { .then((html) => {
scope.pop(); scope.pop();
reg.blocks = originBlocks;
return html; return html;
}); });
} }
+19 -22
View File
@@ -13,24 +13,20 @@ module.exports = function(liquid) {
this.layout = match[0]; this.layout = match[0];
this.tpls = liquid.parser.parse(remainTokens); this.tpls = liquid.parser.parse(remainTokens);
}, },
render: function(scope) { render: function(scope, hash, register) {
var layout = Liquid.evalValue(this.layout, scope); var layout = Liquid.evalValue(this.layout, scope);
var reg = scope.get('liquid');
var html = ''; // render the remaining tokens immediately
scope.push({});
// not sure if this first one is needed, since the results are ignored
return liquid.renderer.renderTemplates(this.tpls, scope) return liquid.renderer.renderTemplates(this.tpls, scope)
.then((partial) => { // now register.blocks contains rendered blocks
html += partial; .then(() => liquid.getTemplate(layout, register.root))
return liquid.getTemplate(layout); // push the hash
}) .then(templates => (scope.push(hash), templates))
.then((templates) => { // render the parent
return liquid.renderer.renderTemplates(templates, scope); .then(templates => liquid.renderer.renderTemplates(templates, scope))
}) // pop the hash
.then((partial) => { .then(partial => (scope.pop(), partial));
scope.pop();
return partial;
});
} }
}); });
@@ -49,20 +45,21 @@ module.exports = function(liquid) {
stream.start(); stream.start();
}, },
render: function(scope){ render: function(scope){
var html = scope.get(`_liquid.blocks.${this.block}`); var register = scope.get('liquid');
var promise = Promise.resolve(''); var html = register.blocks[this.block];
// if not defined yet
if (html === undefined) { if (html === undefined) {
promise = liquid.renderer.renderTemplates(this.tpls, scope) return liquid.renderer.renderTemplates(this.tpls, scope)
.then((partial) => { .then((partial) => {
scope.set(`_liquid.blocks.${this.block}`, partial); register.blocks[this.block] = partial;
return partial; return partial;
}); });
} }
// if already defined by desendents
else { else {
scope.set(`_liquid.blocks.${this.block}`, html); register.blocks[this.block] = html;
promise = Promise.resolve(html); return Promise.resolve(html);
} }
return promise;
} }
}); });
+51 -24
View File
@@ -12,43 +12,70 @@ describe('tags/layout', function() {
extname: '.html' extname: '.html'
}); });
}); });
beforeEach(function() {
mock({
'/default-layout.html': 'foo{% block %}Default{% endblock %}foo',
'/multi-blocks-layout.html': 'foo{% block "a"%}{% endblock %}{% block b%}{%endblock%}foo',
'/multi-blocks.html': '{% layout "multi-blocks-layout" %}{%block a%}aaa{%endblock%},{%block b%};{%block c%}ccc{%endblock%};{%endblock%}',
});
});
afterEach(function() { afterEach(function() {
mock.restore(); mock.restore();
}); });
it('should throw when block not closed', function() { it('should throw when block not closed', function() {
src = '{% layout "default-layout" %}{%block%}bar'; mock({
'/parent.html': 'parent',
});
src = '{% layout "parent" %}{%block%}A';
return expect(liquid.parseAndRender(src)).to return expect(liquid.parseAndRender(src)).to
.be.rejectedWith(/tag {%block%} not closed/); .be.rejectedWith(/tag {%block%} not closed/);
}); });
it('should support layout', function() { it('should handle anonymous block', function() {
src = '{% layout "default-layout" %}{%block%}bar{%endblock%}'; mock({
'/parent.html': 'X{%block%}{%endblock%}Y',
});
src = '{% layout "parent.html" %}{%block%}A{%endblock%}';
return expect(liquid.parseAndRender(src)).to return expect(liquid.parseAndRender(src)).to
.eventually.equal('foobarfoo'); .eventually.equal('XAY');
}); });
it('should support layout: multiple blocks', function() { it('should handle named blocks', function() {
src = '{% layout "multi-blocks-layout" %}' + mock({
'{%block a%}bara{%endblock%}' + '/parent.html': 'X{% block "a"%}{% endblock %}Y{% block b%}{%endblock%}Z',
'{%block b%}barb{%endblock%}'; });
src = '{% layout "parent.html" %}' +
'{%block a%}A{%endblock%}' +
'{%block b%}B{%endblock%}';
return expect(liquid.parseAndRender(src)).to return expect(liquid.parseAndRender(src)).to
.eventually.equal('foobarabarbfoo'); .eventually.equal('XAYBZ');
}); });
it('should support layout: nested 1', function() { it('should support default block content', function() {
src = '{% layout "multi-blocks" %}{% block a%}A{%endblock%}{%block c%}C{%endblock%}'; mock({
'/parent.html': 'X{% block "a"%}A{% endblock %}Y{% block b%}B{%endblock%}Z',
});
src = '{% layout "parent.html" %}{%block a%}a{%endblock%}';
return expect(liquid.parseAndRender(src)).to return expect(liquid.parseAndRender(src)).to
.eventually.equal('fooA;C;foo'); .eventually.equal('XaYBZ');
}); });
it('should support layout: nested 2', function() { it('should handle nested block', function() {
src = '{% layout "multi-blocks" %}{%block c%}C{%endblock%}'; mock({
return expect(liquid.parseAndRender(src)).to '/grand.html': 'X{%block a%}G{%endblock%}Y',
.eventually.equal('fooaaa;C;foo'); '/parent.html': '{%layout "grand" %}{%block a%}P{%endblock%}',
'/main.html': '{%layout "parent"%}{%block a%}A{%endblock%}'
})
return expect(liquid.renderFile('/main.html')).to
.eventually.equal('XAY');
});
it('should not bleed scope into included layout', function() {
mock({
'/parent.html': 'X{%block a%}{%endblock%}Y{%block b%}{%endblock%}Z',
'/main.html': '{%layout "parent"%}'+
'{%block a%}A{%endblock%}' +
'{%block b%}I{%include "included"%}J{%endblock%}',
'/included.html': '{%layout "parent"%}{%block a%}a{%endblock%}'
})
return expect(liquid.renderFile('main')).to
.eventually.equal('XAYIXaYZJZ');
});
it('should support hash list', function() {
mock({
'/parent.html': '{{color}}{%block%}{%endblock%}',
'/main.html': '{% layout "parent.html" color:"black"%}{%block%}A{%endblock%}'
});
return expect(liquid.renderFile('/main.html')).to.
eventually.equal('blackA');
}); });
}); });