diff --git a/src/builtin/tags/block.ts b/src/builtin/tags/block.ts index f8ad384bfe..fb9ab7d295 100644 --- a/src/builtin/tags/block.ts +++ b/src/builtin/tags/block.ts @@ -1,8 +1,9 @@ import BlockMode from '../../context/block-mode' -import { ParseStream, TagToken, TopLevelToken, Template, Context, TagImplOptions, Emitter } from '../../types' +import { BlockDrop } from '../../drop/block-drop' +import { ParseStream, TagToken, TopLevelToken, Template, Context, TagImpl, Emitter } from '../../types' export default { - parse: function (token: TagToken, remainTokens: TopLevelToken[]) { + parse (this: TagImpl, token: TagToken, remainTokens: TopLevelToken[]) { const match = /\w+/.exec(token.args) this.block = match ? match[0] : '' this.tpls = [] as Template[] @@ -14,18 +15,31 @@ export default { }) stream.start() }, - render: function * (ctx: Context, emitter: Emitter) { - const blocks = ctx.getRegister('blocks') - const childDefined = blocks[this.block] - const r = this.liquid.renderer - const html = childDefined !== undefined - ? childDefined - : yield r.renderTemplates(this.tpls, ctx) + * render (this: TagImpl, ctx: Context, emitter: Emitter) { + const blockRender = this.getBlockRender(ctx) + yield this.emitHTML(ctx, emitter, blockRender) + }, + + getBlockRender (this: TagImpl, ctx: Context) { + const { liquid, tpls } = this + const extendedBlockRender = ctx.getRegister('blocks')[this.block] + const defaultBlockRender = function * (superBlock: BlockDrop) { + ctx.push({ block: superBlock }) + const result = yield liquid.renderer.renderTemplates(tpls, ctx) + ctx.pop() + return result + } + return extendedBlockRender + ? (superBlock: BlockDrop) => extendedBlockRender(new BlockDrop(() => defaultBlockRender(superBlock))) + : defaultBlockRender + }, + + * emitHTML (this: TagImpl, ctx: Context, emitter: Emitter, blockRender: (block: BlockDrop) => string) { if (ctx.getRegister('blockMode', BlockMode.OUTPUT) === BlockMode.STORE) { - blocks[this.block] = html - return + ctx.getRegister('blocks')[this.block] = blockRender + } else { + emitter.write(yield blockRender(new BlockDrop())) } - emitter.write(html) } -} as TagImplOptions +} diff --git a/src/builtin/tags/layout.ts b/src/builtin/tags/layout.ts index a27cf5a130..f822c655d7 100644 --- a/src/builtin/tags/layout.ts +++ b/src/builtin/tags/layout.ts @@ -20,15 +20,17 @@ export default { : evalToken(this.file, ctx)) : file.getText() assert(filepath, () => `illegal filename "${file.getText()}":"${filepath}"`) + const templates = yield liquid._parseFile(filepath, ctx.opts, ctx.sync) - // render the remaining tokens immediately + // render remaining contents and store rendered results ctx.setRegister('blockMode', BlockMode.STORE) - const blocks = ctx.getRegister('blocks') const html = yield renderer.renderTemplates(this.tpls, ctx) - if (blocks[''] === undefined) blocks[''] = html - const templates = yield liquid._parseFile(filepath, ctx.opts, ctx.sync) - ctx.push(yield hash.render(ctx)) + const blocks = ctx.getRegister('blocks') + if (blocks[''] === undefined) blocks[''] = () => html ctx.setRegister('blockMode', BlockMode.OUTPUT) + + // render the layout file use stored blocks + ctx.push(yield hash.render(ctx)) const partial = yield renderer.renderTemplates(templates, ctx) ctx.pop() emitter.write(partial) diff --git a/src/drop/block-drop.ts b/src/drop/block-drop.ts new file mode 100644 index 0000000000..23c32d33c9 --- /dev/null +++ b/src/drop/block-drop.ts @@ -0,0 +1,13 @@ +import { Drop } from './drop' + +export class BlockDrop extends Drop { + constructor ( + // the block render from layout template + private superBlockRender: () => Iterable = () => '' + ) { + super() + } + public super () { + return this.superBlockRender() + } +} diff --git a/src/types.ts b/src/types.ts index 063b94e164..750dfbe48d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ export { Context } from './context/context' export { Template } from './template/template' export { FilterImplOptions } from './template/filter/filter-impl-options' export { TagImplOptions } from './template/tag/tag-impl-options' +export { TagImpl } from './template/tag/tag-impl' export { ParseStream } from './parser/parse-stream' export { Token } from './tokens/token' export { TopLevelToken } from './tokens/toplevel-token' diff --git a/test/integration/builtin/tags/layout.ts b/test/integration/builtin/tags/layout.ts index 1b84f38a64..0f9c940f03 100644 --- a/test/integration/builtin/tags/layout.ts +++ b/test/integration/builtin/tags/layout.ts @@ -69,6 +69,36 @@ describe('tags/layout', function () { const html = await liquid.parseAndRender(src) return expect(html).to.equal('XAYBZ') }) + it('should support block.super', async function () { + mock({ + '/parent.html': '{% block css %}{% endblock %}' + }) + const src = '{% layout "parent.html" %}' + + '{%block css%}{{block.super}}{%endblock%}' + const html = await liquid.parseAndRender(src) + const output = '' + return expect(html).to.equal(output) + }) + it('should render block.super to empty if no parent exists', async function () { + mock({ + '/parent.html': '{% block css %}{{block.super}}{% endblock %}' + }) + const src = '{% layout "parent.html" %}' + + '{%block css%}{{block.super}}{%endblock%}' + const html = await liquid.parseAndRender(src) + const output = '' + return expect(html).to.equal(output) + }) + it('should support nested block.super', async function () { + mock({ + '/root.html': '{% block css %}{% endblock %}', + '/parent.html': '{% layout "root.html" %}{% block css %}{{block.super}}{% endblock %}' + }) + const src = '{% layout "parent.html" %}{%block css%}{{block.super}}{%endblock%}' + const html = await liquid.parseAndRender(src) + const output = '' + return expect(html).to.equal(output) + }) it('should support variable as layout name', async function () { mock({ '/parent.html': 'X{% block "a"%}{% endblock %}Y'