diff --git a/index.js b/index.js index 93cad2074..1d212af72 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ const Template = require('./src/parser'); const Expression = require('./src/expression.js'); const tags = require('./tags'); const filters = require('./filters'); +const Promise = require("bluebird"); var _engine = { init: function(tag, filter, options) { @@ -43,8 +44,14 @@ var _engine = { }, renderFile: function(filepath, ctx) { try{ - var tpl = this.handleCache(filepath); - return this.render(tpl, ctx); + return this.handleCache(filepath) + .then((templates) => { + return this.render(templates, ctx); + }) + .catch((e) => { + e.file = filepath; + throw e; + }); } catch(e){ e.file = filepath; @@ -67,9 +74,16 @@ var _engine = { if (path.extname(filepath) === '') { filepath += this.options.extname; } - var tpl = this.options.cache && this.cache[filepath] || - this.parse(fs.readFileSync(filepath, 'utf8')); - return this.options.cache ? (this.cache[filepath] = tpl) : tpl; + + return this.getTemplate(filepath) + .then((html) => { + var tpl = this.options.cache && this.cache[filepath] || this.parse(html); + return this.options.cache ? (this.cache[filepath] = tpl) : tpl; + }); + }, + getTemplate: function(filepath) { + var html = fs.readFileSync(filepath, 'utf8'); + return Promise.resolve(html); }, express: function() { return (filePath, options, callback) => { diff --git a/package.json b/package.json index 03d65f9f0..5a970d0bc 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,13 @@ }, "homepage": "https://github.com/harttle/shopify-liquid#readme", "dependencies": { + "bluebird": "^3.4.3", "lodash": "^4.13.1", "strftime": "^0.9.2" }, "devDependencies": { "chai": "^3.5.0", + "chai-as-promised": "^5.3.0", "coveralls": "^2.11.9", "express": "^4.14.0", "istanbul": "^0.4.3", diff --git a/src/render.js b/src/render.js index d10ba8ee5..8249d6273 100644 --- a/src/render.js +++ b/src/render.js @@ -1,30 +1,38 @@ const error = require('./error.js'); const Exp = require('./expression.js'); const assert = require('assert'); +const Promise = require("bluebird"); var render = { renderTemplates: function(templates, scope) { assert(scope, 'unable to evalTemplates: scope undefined'); - var html = '', - partial; - templates.some(template => { + var htmlBlocks = [], + partial, + promises = []; + templates.some((template, index) => { if (scope.get('forloop.skip')) return true; switch (template.type) { case 'tag': - partial = this.renderTag(template, scope, this.register); - if (partial === undefined) return true; - html += partial; + promises.push(this.renderTag(template, scope, this.register) + .then((partial) => { + if (partial === undefined) return true; + return htmlBlocks[index] = partial; + })); break; case 'html': - html += template.value; + promises.push(Promise.resolve(htmlBlocks[index] = template.value)); break; case 'output': var val = this.evalOutput(template, scope); - html += val === undefined ? '' : stringify(val); + htmlBlocks[index] = val === undefined ? '' : stringify(val); + promises.push(Promise.resolve(htmlBlocks[index])); } }); - return html; + return Promise.all(promises) + .then((results) => { + return htmlBlocks.join(''); + }); }, renderTag: function(template, scope, register) { diff --git a/tags/include.js b/tags/include.js index 6644175ae..a136a9b32 100644 --- a/tags/include.js +++ b/tags/include.js @@ -1,4 +1,5 @@ var Liquid = require('..'); +var Promise = require("bluebird"); var lexical = Liquid.lexical; var withRE = new RegExp(`with\\s+(${lexical.value.source})`); @@ -20,11 +21,16 @@ module.exports = function(liquid) { if(this.with){ hash[filepath] = Liquid.evalValue(this.with, scope); } - var tpl = liquid.handleCache(filepath); - scope.push(hash); - var html = liquid.renderer.renderTemplates(tpl, scope); - scope.pop(); - return html; + return liquid.handleCache(filepath) + .then((templates) => { + scope.push(hash); + return liquid.renderer.renderTemplates(templates, scope); + }) + .then((html) => { + scope.pop(); + return html; + }); + } }); diff --git a/test/tags.js b/test/tags.js index fc6b69ff5..aef0c960e 100644 --- a/test/tags.js +++ b/test/tags.js @@ -1,7 +1,9 @@ const chai = require("chai"); +const should = chai.should(); const expect = chai.expect; const Liquid = require('..'); const mock = require('mock-fs'); +chai.use(require("chai-as-promised")); var liquid = Liquid({ root: '/', extname: '.html' @@ -28,7 +30,14 @@ describe('tags', function() { foo: 'bar', arr: [-2, 'a'], alpha: ['a', 'b', 'c'], - emptyArray: [] + emptyArray: [], + person: { + firstName: 'Joe', + lastName: 'Shmoe', + address: { + city: 'Dallas' + } + } }; mock({ '/default-layout.html': 'foo{% block %}Default{% endblock %}foo', @@ -42,7 +51,10 @@ describe('tags', function() { '/color.html': 'color:{{color}}, shape:{{shape}}', '/with.html': '{% include "color" with "red", shape: "rect" %}', '/scope.html': '{% assign shape="triangle" %}{% assign color="yellow" %}{% include "color.html" %}', - '/hash.html': '{% assign name="harttle" %}{% include "user.html", role: "admin", alias: name %}' + '/hash.html': '{% assign name="harttle" %}{% include "user.html", role: "admin", alias: name %}', + '/personInfo.html': 'This is a person {% include "card.html" %}', + '/card.html': '

{{person.firstName}} {{person.lastName}}
{% include "address" %}

', + '/address.html': 'City: {{person.address.city}}' }); }); afterEach(function() { @@ -266,6 +278,12 @@ describe('tags', function() { var dst = 'color:red, shape:rect'; expect(liquid.renderFile(filepath, ctx)).to.equal(dst); }); + + it.only('should support nested includes', function() { + //expect(liquid.renderFile('personInfo.html', ctx)).to.equal('This is a person

Joe Shmoe
City: Dallas

'); + return liquid.renderFile('personInfo.html', ctx).should.eventually.equal('This is a person

Joe Shmoe
City: Dallas

') + }); + it('should throw when block not closed', function() { src = '{% layout "default-layout" %}{%block%}bar'; testThrow(src, /tag {%block%} not closed/);