Repository navigation
fix(pug-linker): render nested block append/prepend once - #3480
Open
seebaermichi wants to merge 5 commits into
Open
seebaermichi wants to merge 5 commits into
seebaermichi wants to merge 5 commits into
Conversation
added 5 commits
November 6, 2026 10:27
A block append/prepend/replace nested inside another block of an extending template was merged into the layout's block and also kept in place, so its content rendered twice (pugjs#3158). Empty the block in place when a block it was merged into is still part of the tree; keep it when all of those were replaced away.
Add regression cases for a nested replace, an appending include (once and twice), a nested append inside a mixin, `each` and another nested append, a block the layout declares twice, three-level chains, and the guard cases where the parent block is replaced away (the nested block must still render once, in place).
…inked Emptying a merged block at every `extends` level lost its content when a template further down replaced an ancestor of the block it had been merged into: the copy in place was already gone. Run the pass once, on the fully linked tree, so only blocks that still have a rendered target are emptied.
… later The page replaces the layout block that holds the append's target, one extends level above the template that nests the append. The append renders once where it was written, as on master.
…s a layout An included file that extends a layout has its named blocks turned into plain blocks before the includer is linked, so the final pass no longer saw them and a block append nested in that file rendered twice again. The included tree is final at that point, so run the pass on it before unwrapping its blocks.
|
There is no change log for this pull request yet. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3158. This picks up #3475 by @afonsojanu, which its author closed unmerged. It uses a different approach, because #3475 drops content in a few cases (details below).
The bug. In a template that extends a layout, a
block append/prepend/ replace that is nested inside another block is merged into the layout's block, but it also stays where it was written. So its content renders twice. For example, with a layout that hasblock headinsideheadandblock contentinsidebody, a page that writesblock append headwithscript(src="page.js")inside itsblock contentgetspage.jsinheadand a second time inbody. Onpug@3.0.4and on master (c323ed3e) it renders<html><head><script src="layout.js"></script><script src="page.js"></script></head><body><p>Page content</p><script src="page.js"></script></body></html>. The fixtureissue-3158.nested-append.pugis exactly this case.The same happens when the nested block comes from an include (the case in #3158), sits in a mixin definition, an
eachor another nested append, targets a block the layout declares twice, or is threeextendslevels deep.Cause.
extendinpug-linkerwalks the whole child AST, not only its top level, so nested blocks are merged too. Nothing removes them from where they were written.The fix. A new
removeMergedBlockspass empties a named block in place when a block it was merged into is still part of the tree. When every block it was merged into has been replaced away (for example a page that replacesblock contentand declaresblock sidebaragain inside it, while the layout'ssidebarsat inside the oldcontent), the copy in place is the only one left, so it is kept. The pass runs once, on the fully linked tree, because a block that is still there at a middleextendslevel can be replaced away by a template further down. An included file that extends a layout gets the same pass before its blocks are unwrapped into plain blocks, because nothing can extend them after that.Why not #3475's approach. #3475 empties every merged block while extending, also when the block it was merged into is replaced later. That loses content. With its linker, a block redeclared or appended inside a replaced parent block disappears entirely, where master renders it once. It also still renders twice when a grandchild template appends to or replaces a block that the middle template nests, because the grandchild's
extendrefills the emptied block. The existing fixture cases render identically with #3475, because none of them covers these.Output change. A nested replace (
block headwritten insideblock content) used to render the new content both in the layout'sheadand in place. It now renders once, inhead. A template that relied on the double render will change. Unchanged: merging stays static, so a nested append insideif falseor an uncalled mixin still reaches its target, now once.Tests. 19 fixture cases in
packages/pug/test/cases/issue-3158.*.pug, plus their templates inauxiliary/:pug-linkersnapshots are unchanged (24/24).prettier --list-differentis clean onpug-linker/index.js.Happy to cut the fixtures down to a handful if 19 is too many.