diff --git a/runtime/helpers.js b/runtime/helpers.js index c8a9f51d7..2560d5b6a 100644 --- a/runtime/helpers.js +++ b/runtime/helpers.js @@ -22,6 +22,26 @@ function notEmpty(o) { return true; } +function createLazyRenderer(handler) { + var lazyRenderer = function(input, out) { + lazyRenderer.renderer(input, out); + }; + + // This is the initial function that will do the rendering. We replace + // the renderer with the actual renderer func on the first render + lazyRenderer.renderer = function(input, out) { + var rendererFunc = handler.renderer || handler.render; + if (typeof renderFunc !== 'function') { + throw new Error('Invalid tag handler: ' + handler); + } + // Use the actual renderer from now on + lazyRenderer.renderer = rendererFunc; + rendererFunc(input, out); + }; + + return lazyRenderer; +} + var WARNED_INVOKE_BODY = 0; module.exports = { @@ -116,8 +136,12 @@ module.exports = { r: function(handler) { var renderFunc = handler.renderer || handler.render || handler; + // If the user code has a circular function then the renderer function + // may not be available on the module. Since we can't get a reference + // to the actual renderer(input, out) function right now we lazily + // try to get access to it later. if (typeof renderFunc !== 'function') { - throw new Error('Invalid tag handler: ' + handler); + return createLazyRenderer(handler); } return renderFunc; diff --git a/runtime/marko-runtime.js b/runtime/marko-runtime.js index b1d8c0d59..d223222da 100644 --- a/runtime/marko-runtime.js +++ b/runtime/marko-runtime.js @@ -53,8 +53,8 @@ if (streamPath) { stream = require(streamPath); } -function Template(renderFunc, options) { - this._ = renderFunc; +function Template( options) { + this._ = null; this.buffer = !options || options.buffer !== false; } @@ -204,20 +204,35 @@ function load(templatePath, options) { if (typeof templatePath === 'string') { template = cache[templatePath]; if (!template) { - // The template has not been loaded, load the template to get - // access to the factory function that is used to produce + // The template has not been loaded + + // Cache the Template instance before actually loading and initializing + // the compiled template. This allows circular dependencies since the + // partially loaded Template instance will be found in the cache. + template = cache[templatePath] = new Template(options); + + // Now load the template to get access to the factory function that is used to produce // the actual compiled template function. We pass the helpers // as the first argument to the factory function to produce - // the compiled template function - template = cache[templatePath] = new Template( - loader(templatePath).create(helpers), // Load the template factory and invoke it - options); + // the template rendering function + template._ = loader(templatePath).create(helpers); // Load the template factory and invoke it } } else { // Instead of a path, assume we got a compiled template module // We store the loaded template with the factory function that was // used to get access to the compiled template function - template = templatePath._ || (templatePath._ = new Template(templatePath.create(helpers), options)); + template = templatePath._; + if (!template) { + // First put the partially loaded Template instance on the + // the compiled template module before actually loading and + // initializing the compiled template. This allows for circular + // dependencies during template loading. + template = templatePath._ = new Template(options); + + // Now fully initialize the template by adding the needed render + // function. + template._ = templatePath.create(helpers); + } } return template; diff --git a/test/fixtures/marko-taglib.json b/test/fixtures/marko-taglib.json index e980348f2..a833f77c6 100644 --- a/test/fixtures/marko-taglib.json +++ b/test/fixtures/marko-taglib.json @@ -102,6 +102,18 @@ "attributes": { "name": "string" } + }, + "test-circular-renderer-a": { + "renderer": "./taglib/test-circular-renderer-a/renderer" + }, + "test-circular-renderer-b": { + "renderer": "./taglib/test-circular-renderer-b/renderer" + }, + "test-circular-template-a": { + "template": "./taglib/test-circular-template-a/template.marko" + }, + "test-circular-template-b": { + "template": "./taglib/test-circular-template-b/template.marko" } }, "tags-dir": "./taglib/scanned-tags", diff --git a/test/fixtures/taglib/test-circular-renderer-a/renderer.js b/test/fixtures/taglib/test-circular-renderer-a/renderer.js new file mode 100644 index 000000000..a3f5fa902 --- /dev/null +++ b/test/fixtures/taglib/test-circular-renderer-a/renderer.js @@ -0,0 +1,6 @@ +var marko = require('../../../../'); +var template = marko.load(require.resolve('./template.marko')); + +exports.renderer = function(input, out) { + template.render({}, out); +}; \ No newline at end of file diff --git a/test/fixtures/taglib/test-circular-renderer-a/template.marko b/test/fixtures/taglib/test-circular-renderer-a/template.marko new file mode 100644 index 000000000..bb7f23745 --- /dev/null +++ b/test/fixtures/taglib/test-circular-renderer-a/template.marko @@ -0,0 +1,2 @@ +test-circular-renderer-a + \ No newline at end of file diff --git a/test/fixtures/taglib/test-circular-renderer-b/renderer.js b/test/fixtures/taglib/test-circular-renderer-b/renderer.js new file mode 100644 index 000000000..871a65c80 --- /dev/null +++ b/test/fixtures/taglib/test-circular-renderer-b/renderer.js @@ -0,0 +1,6 @@ +var marko = require('../../../../'); +marko.load(require.resolve('./template.marko')); + +exports.renderer = function(input, out) { + out.write('test-circular-renderer-b'); +}; \ No newline at end of file diff --git a/test/fixtures/taglib/test-circular-renderer-b/template.marko b/test/fixtures/taglib/test-circular-renderer-b/template.marko new file mode 100644 index 000000000..e6820b206 --- /dev/null +++ b/test/fixtures/taglib/test-circular-renderer-b/template.marko @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/fixtures/taglib/test-circular-template-a/template.marko b/test/fixtures/taglib/test-circular-template-a/template.marko new file mode 100644 index 000000000..d364b2bd9 --- /dev/null +++ b/test/fixtures/taglib/test-circular-template-a/template.marko @@ -0,0 +1,2 @@ +test-circular-template-a + \ No newline at end of file diff --git a/test/fixtures/taglib/test-circular-template-b/template.marko b/test/fixtures/taglib/test-circular-template-b/template.marko new file mode 100644 index 000000000..436327d0d --- /dev/null +++ b/test/fixtures/taglib/test-circular-template-b/template.marko @@ -0,0 +1 @@ +test-circular-template-b \ No newline at end of file diff --git a/test/fixtures/templates/circular-renderer/expected.html b/test/fixtures/templates/circular-renderer/expected.html new file mode 100644 index 000000000..54d975a00 --- /dev/null +++ b/test/fixtures/templates/circular-renderer/expected.html @@ -0,0 +1 @@ +test-circular-renderer-a test-circular-renderer-b \ No newline at end of file diff --git a/test/fixtures/templates/circular-renderer/template.marko b/test/fixtures/templates/circular-renderer/template.marko new file mode 100644 index 000000000..e6820b206 --- /dev/null +++ b/test/fixtures/templates/circular-renderer/template.marko @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/fixtures/templates/circular-renderer/test.js b/test/fixtures/templates/circular-renderer/test.js new file mode 100644 index 000000000..c4013b344 --- /dev/null +++ b/test/fixtures/templates/circular-renderer/test.js @@ -0,0 +1 @@ +exports.templateData = {}; diff --git a/test/fixtures/templates/circular-template/expected.html b/test/fixtures/templates/circular-template/expected.html new file mode 100644 index 000000000..aa3d40f06 --- /dev/null +++ b/test/fixtures/templates/circular-template/expected.html @@ -0,0 +1 @@ +test-circular-template-a test-circular-template-b \ No newline at end of file diff --git a/test/fixtures/templates/circular-template/template.marko b/test/fixtures/templates/circular-template/template.marko new file mode 100644 index 000000000..d54ac0006 --- /dev/null +++ b/test/fixtures/templates/circular-template/template.marko @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/fixtures/templates/circular-template/test.js b/test/fixtures/templates/circular-template/test.js new file mode 100644 index 000000000..c4013b344 --- /dev/null +++ b/test/fixtures/templates/circular-template/test.js @@ -0,0 +1 @@ +exports.templateData = {};