Fix live reload when editing inline partials - hugo - [fork] hugo port for 9front
 (HTM) git clone git@git.drkhsh.at/hugo.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 0c7b1a3f2679755b0fde3a230e15471f404b3aa4
 (DIR) parent 970b887ba1bba83ca843b9ece16dbf01fa1a022d
 (HTM) Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Wed, 21 May 2025 11:25:32 +0200
       
       Fix live reload when editing inline partials
       
       Fixes #13723
       
       Diffstat:
         M hugolib/rebuild_test.go             |      54 +++++++++++++++++++++++++++++++
         M tpl/internal/go_templates/htmltemp… |      14 ++++++++++++++
         M tpl/internal/go_templates/texttemp… |      16 ++++++++++++++++
         M tpl/internal/go_templates/texttemp… |       2 +-
         M tpl/tplimpl/templates.go            |      52 ++++++++++++++++++++-----------
         M tpl/tplimpl/templatestore.go        |     177 +++++++++++++++++++------------
       
       6 files changed, 226 insertions(+), 89 deletions(-)
       ---
 (DIR) diff --git a/hugolib/rebuild_test.go b/hugolib/rebuild_test.go
       @@ -1766,6 +1766,60 @@ MyTemplate: {{ partial "MyTemplate.html" . }}|
                b.AssertFileContent("public/index.html", "MyTemplate: MyTemplate Edited")
        }
        
       +func TestRebuildEditInlinePartial13723(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +baseURL = "https://example.com"
       +disableLiveReload = true
       +title = "Foo"
       +-- layouts/baseof.html --
       +{{ block "main" . }}Main.{{ end }}
       +{{ partial "myinlinepartialinbaseof.html" . }}|
       + {{- define "_partials/myinlinepartialinbaseof.html" }}
       + My inline partial in baseof.
       + {{ end }}
       +-- layouts/_partials/mypartial.html --
       +Mypartial.
       +{{ partial "myinlinepartial.html" . }}|
       +{{- define "_partials/myinlinepartial.html" }}
       +Mypartial Inline.|{{ .Title }}|
       +{{ end }}
       +-- layouts/_partials/myotherpartial.html --
       +Myotherpartial.
       +{{ partial "myotherinlinepartial.html" . }}|
       +{{- define "_partials/myotherinlinepartial.html" }}
       +Myotherpartial Inline.|{{ .Title }}|
       +{{ return "myotherinlinepartial" }}
       +{{ end }}
       +-- layouts/all.html --
       +{{ define "main" }}
       +{{ partial "mypartial.html" . }}|
       +{{ partial "myotherpartial.html" . }}|
       +  {{ partial "myinlinepartialinall.html" . }}|
       +{{ end }}
       + {{- define "_partials/myinlinepartialinall.html" }}
       + My inline partial in all.
       + {{ end }}
       +
       +`
       +        b := TestRunning(t, files)
       +        b.AssertFileContent("public/index.html", "Mypartial.", "Mypartial Inline.|Foo")
       +
       +        // Edit inline partial in partial.
       +        b.EditFileReplaceAll("layouts/_partials/mypartial.html", "Mypartial Inline.", "Mypartial Inline Edited.").Build()
       +        b.AssertFileContent("public/index.html", "Mypartial Inline Edited.|Foo")
       +
       +        // Edit inline partial in baseof.
       +        b.EditFileReplaceAll("layouts/baseof.html", "My inline partial in baseof.", "My inline partial in baseof Edited.").Build()
       +        b.AssertFileContent("public/index.html", "My inline partial in baseof Edited.")
       +
       +        // Edit inline partial in all.
       +        b.EditFileReplaceAll("layouts/all.html", "My inline partial in all.", "My inline partial in all Edited.").Build()
       +        b.AssertFileContent("public/index.html", "My inline partial in all Edited.")
       +}
       +
        func TestRebuildEditAsciidocContentFile(t *testing.T) {
                if !asciidocext.Supports() {
                        t.Skip("skip asciidoc")
 (DIR) diff --git a/tpl/internal/go_templates/htmltemplate/hugo_template.go b/tpl/internal/go_templates/htmltemplate/hugo_template.go
       @@ -15,6 +15,7 @@ package template
        
        import (
                "fmt"
       +        "iter"
        
                "github.com/gohugoio/hugo/common/types"
                template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
       @@ -38,6 +39,19 @@ func (t *Template) Prepare() (*template.Template, error) {
                return t.text, nil
        }
        
       +func (t *Template) All() iter.Seq[*Template] {
       +        return func(yield func(t *Template) bool) {
       +                ns := t.nameSpace
       +                ns.mu.Lock()
       +                defer ns.mu.Unlock()
       +                for _, v := range ns.set {
       +                        if !yield(v) {
       +                                return
       +                        }
       +                }
       +        }
       +}
       +
        // See https://github.com/golang/go/issues/5884
        func StripTags(html string) string {
                return stripTags(html)
 (DIR) diff --git a/tpl/internal/go_templates/texttemplate/hugo_template.go b/tpl/internal/go_templates/texttemplate/hugo_template.go
       @@ -17,6 +17,7 @@ import (
                "context"
                "fmt"
                "io"
       +        "iter"
                "reflect"
        
                "github.com/gohugoio/hugo/common/herrors"
       @@ -433,3 +434,18 @@ func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node
        func isTrue(val reflect.Value) (truth, ok bool) {
                return hreflect.IsTruthfulValue(val), true
        }
       +
       +func (t *Template) All() iter.Seq[*Template] {
       +        return func(yield func(t *Template) bool) {
       +                if t.common == nil {
       +                        return
       +                }
       +                t.muTmpl.RLock()
       +                defer t.muTmpl.RUnlock()
       +                for _, v := range t.tmpl {
       +                        if !yield(v) {
       +                                return
       +                        }
       +                }
       +        }
       +}
 (DIR) diff --git a/tpl/internal/go_templates/texttemplate/parse/parse.go b/tpl/internal/go_templates/texttemplate/parse/parse.go
       @@ -533,7 +533,7 @@ func (t *Tree) parseControl(context string) (pos Pos, line int, pipe *PipeNode, 
                        t.rangeDepth--
                }
                switch next.Type() {
       -        case nodeEnd: //done
       +        case nodeEnd: // done
                case nodeElse:
                        // Special case for "else if" and "else with".
                        // If the "else" is followed immediately by an "if" or "with",
 (DIR) diff --git a/tpl/tplimpl/templates.go b/tpl/tplimpl/templates.go
       @@ -2,6 +2,7 @@ package tplimpl
        
        import (
                "io"
       +        "iter"
                "regexp"
                "strconv"
                "strings"
       @@ -44,16 +45,15 @@ var embeddedTemplatesAliases = map[string][]string{
                "_shortcodes/twitter.html": {"_shortcodes/tweet.html"},
        }
        
       -func (s *TemplateStore) parseTemplate(ti *TemplInfo) error {
       -        err := s.tns.doParseTemplate(ti)
       +func (s *TemplateStore) parseTemplate(ti *TemplInfo, replace bool) error {
       +        err := s.tns.doParseTemplate(ti, replace)
                if err != nil {
                        return s.addFileContext(ti, "parse of template failed", err)
                }
       -
                return err
        }
        
       -func (t *templateNamespace) doParseTemplate(ti *TemplInfo) error {
       +func (t *templateNamespace) doParseTemplate(ti *TemplInfo, replace bool) error {
                if !ti.noBaseOf || ti.category == CategoryBaseof {
                        // Delay parsing until we have the base template.
                        return nil
       @@ -68,7 +68,7 @@ func (t *templateNamespace) doParseTemplate(ti *TemplInfo) error {
        
                if ti.D.IsPlainText {
                        prototype := t.parseText
       -                if prototype.Lookup(name) != nil {
       +                if !replace && prototype.Lookup(name) != nil {
                                name += "-" + strconv.FormatUint(t.nameCounter.Add(1), 10)
                        }
                        templ, err = prototype.New(name).Parse(ti.content)
       @@ -77,7 +77,7 @@ func (t *templateNamespace) doParseTemplate(ti *TemplInfo) error {
                        }
                } else {
                        prototype := t.parseHTML
       -                if prototype.Lookup(name) != nil {
       +                if !replace && prototype.Lookup(name) != nil {
                                name += "-" + strconv.FormatUint(t.nameCounter.Add(1), 10)
                        }
                        templ, err = prototype.New(name).Parse(ti.content)
       @@ -181,19 +181,24 @@ func (t *templateNamespace) applyBaseTemplate(overlay *TemplInfo, base keyTempla
                return nil
        }
        
       -func (t *templateNamespace) templatesIn(in tpl.Template) []tpl.Template {
       -        var templs []tpl.Template
       -        if textt, ok := in.(*texttemplate.Template); ok {
       -                for _, t := range textt.Templates() {
       -                        templs = append(templs, t)
       -                }
       -        }
       -        if htmlt, ok := in.(*htmltemplate.Template); ok {
       -                for _, t := range htmlt.Templates() {
       -                        templs = append(templs, t)
       +func (t *templateNamespace) templatesIn(in tpl.Template) iter.Seq[tpl.Template] {
       +        return func(yield func(t tpl.Template) bool) {
       +                switch in := in.(type) {
       +                case *htmltemplate.Template:
       +                        for t := range in.All() {
       +                                if !yield(t) {
       +                                        return
       +                                }
       +                        }
       +
       +                case *texttemplate.Template:
       +                        for t := range in.All() {
       +                                if !yield(t) {
       +                                        return
       +                                }
       +                        }
                        }
                }
       -        return templs
        }
        
        /*
       @@ -337,8 +342,6 @@ func (t *templateNamespace) createPrototypes(init bool) error {
                        t.prototypeHTML = htmltemplate.Must(t.parseHTML.Clone())
                        t.prototypeText = texttemplate.Must(t.parseText.Clone())
                }
       -        // t.execHTML = htmltemplate.Must(t.parseHTML.Clone())
       -        // t.execText = texttemplate.Must(t.parseText.Clone())
        
                return nil
        }
       @@ -350,3 +353,14 @@ func newTemplateNamespace(funcs map[string]any) *templateNamespace {
                        standaloneText: texttemplate.New("").Funcs(funcs),
                }
        }
       +
       +func isText(t tpl.Template) bool {
       +        switch t.(type) {
       +        case *texttemplate.Template:
       +                return true
       +        case *htmltemplate.Template:
       +                return false
       +        default:
       +                panic("unknown template type")
       +        }
       +}
 (DIR) diff --git a/tpl/tplimpl/templatestore.go b/tpl/tplimpl/templatestore.go
       @@ -114,17 +114,18 @@ func NewStore(opts StoreOptions, siteOpts SiteOptions) (*TemplateStore, error) {
                        panic("HTML output format not found")
                }
                s := &TemplateStore{
       -                opts:                opts,
       -                siteOpts:            siteOpts,
       -                optsOrig:            opts,
       -                siteOptsOrig:        siteOpts,
       -                htmlFormat:          html,
       -                storeSite:           configureSiteStorage(siteOpts, opts.Watching),
       -                treeMain:            doctree.NewSimpleTree[map[nodeKey]*TemplInfo](),
       -                treeShortcodes:      doctree.NewSimpleTree[map[string]map[TemplateDescriptor]*TemplInfo](),
       -                templatesByPath:     maps.NewCache[string, *TemplInfo](),
       -                shortcodesByName:    maps.NewCache[string, *TemplInfo](),
       -                cacheLookupPartials: maps.NewCache[string, *TemplInfo](),
       +                opts:                 opts,
       +                siteOpts:             siteOpts,
       +                optsOrig:             opts,
       +                siteOptsOrig:         siteOpts,
       +                htmlFormat:           html,
       +                storeSite:            configureSiteStorage(siteOpts, opts.Watching),
       +                treeMain:             doctree.NewSimpleTree[map[nodeKey]*TemplInfo](),
       +                treeShortcodes:       doctree.NewSimpleTree[map[string]map[TemplateDescriptor]*TemplInfo](),
       +                templatesByPath:      maps.NewCache[string, *TemplInfo](),
       +                shortcodesByName:     maps.NewCache[string, *TemplInfo](),
       +                cacheLookupPartials:  maps.NewCache[string, *TemplInfo](),
       +                templatesSnapshotSet: maps.NewCache[*parse.Tree, struct{}](),
        
                        // Note that the funcs passed below is just for name validation.
                        tns: newTemplateNamespace(siteOpts.TemplateFuncs),
       @@ -143,10 +144,10 @@ func NewStore(opts StoreOptions, siteOpts SiteOptions) (*TemplateStore, error) {
                if err := s.insertEmbedded(); err != nil {
                        return nil, err
                }
       -        if err := s.parseTemplates(); err != nil {
       +        if err := s.parseTemplates(false); err != nil {
                        return nil, err
                }
       -        if err := s.extractInlinePartials(); err != nil {
       +        if err := s.extractInlinePartials(false); err != nil {
                        return nil, err
                }
                if err := s.transformTemplates(); err != nil {
       @@ -424,10 +425,11 @@ type TemplateStore struct {
                siteOpts   SiteOptions
                htmlFormat output.Format
        
       -        treeMain         *doctree.SimpleTree[map[nodeKey]*TemplInfo]
       -        treeShortcodes   *doctree.SimpleTree[map[string]map[TemplateDescriptor]*TemplInfo]
       -        templatesByPath  *maps.Cache[string, *TemplInfo]
       -        shortcodesByName *maps.Cache[string, *TemplInfo]
       +        treeMain             *doctree.SimpleTree[map[nodeKey]*TemplInfo]
       +        treeShortcodes       *doctree.SimpleTree[map[string]map[TemplateDescriptor]*TemplInfo]
       +        templatesByPath      *maps.Cache[string, *TemplInfo]
       +        shortcodesByName     *maps.Cache[string, *TemplInfo]
       +        templatesSnapshotSet *maps.Cache[*parse.Tree, struct{}]
        
                dh descriptorHandler
        
       @@ -709,12 +711,16 @@ func (s *TemplateStore) RefreshFiles(include func(fi hugofs.FileMetaInfo) bool) 
                if err := s.insertTemplates(include, true); err != nil {
                        return err
                }
       -        if err := s.parseTemplates(); err != nil {
       +        if err := s.createTemplatesSnapshot(); err != nil {
                        return err
                }
       -        if err := s.extractInlinePartials(); err != nil {
       +        if err := s.parseTemplates(true); err != nil {
                        return err
                }
       +        if err := s.extractInlinePartials(true); err != nil {
       +                return err
       +        }
       +
                if err := s.transformTemplates(); err != nil {
                        return err
                }
       @@ -940,57 +946,75 @@ func (s *TemplateStore) extractIdentifiers(line string) []string {
                return identifiers
        }
        
       -func (s *TemplateStore) extractInlinePartials() error {
       +func (s *TemplateStore) extractInlinePartials(rebuild bool) error {
                isPartialName := func(s string) bool {
                        return strings.HasPrefix(s, "partials/") || strings.HasPrefix(s, "_partials/")
                }
        
       -        p := s.tns
                // We may find both inline and external partials in the current template namespaces,
                // so only add the ones we have not seen before.
       -        addIfNotSeen := func(isText bool, templs ...tpl.Template) error {
       -                for _, templ := range templs {
       -                        if templ.Name() == "" || !isPartialName(templ.Name()) {
       -                                continue
       -                        }
       -                        name := templ.Name()
       -                        if !paths.HasExt(name) {
       -                                // Assume HTML. This in line with how the lookup works.
       -                                name = name + s.htmlFormat.MediaType.FirstSuffix.FullSuffix
       -                        }
       -                        if !strings.HasPrefix(name, "_") {
       -                                name = "_" + name
       -                        }
       -                        pi := s.opts.PathParser.Parse(files.ComponentFolderLayouts, name)
       -                        ti, err := s.insertTemplate(pi, nil, false, s.treeMain)
       -                        if err != nil {
       -                                return err
       -                        }
       -
       -                        if ti != nil {
       -                                ti.Template = templ
       -                                ti.noBaseOf = true
       -                                ti.subCategory = SubCategoryInline
       -                                ti.D.IsPlainText = isText
       -                        }
       +        for templ := range s.allRawTemplates() {
       +                if templ.Name() == "" || !isPartialName(templ.Name()) {
       +                        continue
       +                }
       +                if rebuild && s.templatesSnapshotSet.Contains(getParseTree(templ)) {
       +                        // This partial was not created during this build.
       +                        continue
       +                }
       +                name := templ.Name()
       +                if !paths.HasExt(name) {
       +                        // Assume HTML. This in line with how the lookup works.
       +                        name = name + s.htmlFormat.MediaType.FirstSuffix.FullSuffix
       +                }
       +                if !strings.HasPrefix(name, "_") {
       +                        name = "_" + name
       +                }
       +                pi := s.opts.PathParser.Parse(files.ComponentFolderLayouts, name)
       +                ti, err := s.insertTemplate(pi, nil, SubCategoryInline, false, s.treeMain)
       +                if err != nil {
       +                        return err
       +                }
        
       +                if ti != nil {
       +                        ti.Template = templ
       +                        ti.noBaseOf = true
       +                        ti.subCategory = SubCategoryInline
       +                        ti.D.IsPlainText = isText(templ)
                        }
       -                return nil
                }
       -        addIfNotSeen(false, p.templatesIn(p.parseHTML)...)
       -        addIfNotSeen(true, p.templatesIn(p.parseText)...)
        
       -        for _, t := range p.baseofHtmlClones {
       -                if err := addIfNotSeen(false, p.templatesIn(t)...); err != nil {
       -                        return err
       +        return nil
       +}
       +
       +func (s *TemplateStore) allRawTemplates() iter.Seq[tpl.Template] {
       +        p := s.tns
       +        return func(yield func(tpl.Template) bool) {
       +                for t := range p.templatesIn(p.parseHTML) {
       +                        if !yield(t) {
       +                                return
       +                        }
                        }
       -        }
       -        for _, t := range p.baseofTextClones {
       -                if err := addIfNotSeen(true, p.templatesIn(t)...); err != nil {
       -                        return err
       +                for t := range p.templatesIn(p.parseText) {
       +                        if !yield(t) {
       +                                return
       +                        }
       +                }
       +
       +                for _, tt := range p.baseofHtmlClones {
       +                        for t := range p.templatesIn(tt) {
       +                                if !yield(t) {
       +                                        return
       +                                }
       +                        }
       +                }
       +                for _, tt := range p.baseofTextClones {
       +                        for t := range p.templatesIn(tt) {
       +                                if !yield(t) {
       +                                        return
       +                                }
       +                        }
                        }
                }
       -        return nil
        }
        
        func (s *TemplateStore) insertEmbedded() error {
       @@ -1024,7 +1048,7 @@ func (s *TemplateStore) insertEmbedded() error {
                                                return err
                                        }
                                } else {
       -                                ti, err = s.insertTemplate(pi, nil, false, s.treeMain)
       +                                ti, err = s.insertTemplate(pi, nil, SubCategoryEmbedded, false, s.treeMain)
                                        if err != nil {
                                                return err
                                        }
       @@ -1105,7 +1129,7 @@ func (s *TemplateStore) insertShortcode(pi *paths.Path, fi hugofs.FileMetaInfo, 
                return ti, nil
        }
        
       -func (s *TemplateStore) insertTemplate(pi *paths.Path, fi hugofs.FileMetaInfo, replace bool, tree doctree.Tree[map[nodeKey]*TemplInfo]) (*TemplInfo, error) {
       +func (s *TemplateStore) insertTemplate(pi *paths.Path, fi hugofs.FileMetaInfo, subCategory SubCategory, replace bool, tree doctree.Tree[map[nodeKey]*TemplInfo]) (*TemplInfo, error) {
                key, _, category, d, err := s.toKeyCategoryAndDescriptor(pi)
                // See #13577. Warn for now.
                if err != nil {
       @@ -1119,7 +1143,7 @@ func (s *TemplateStore) insertTemplate(pi *paths.Path, fi hugofs.FileMetaInfo, r
                        return nil, nil
                }
        
       -        return s.insertTemplate2(pi, fi, key, category, d, replace, false, tree)
       +        return s.insertTemplate2(pi, fi, key, category, subCategory, d, replace, false, tree)
        }
        
        func (s *TemplateStore) insertTemplate2(
       @@ -1127,6 +1151,7 @@ func (s *TemplateStore) insertTemplate2(
                fi hugofs.FileMetaInfo,
                key string,
                category Category,
       +        subCategory SubCategory,
                d TemplateDescriptor,
                replace, isLegacyMapped bool,
                tree doctree.Tree[map[nodeKey]*TemplInfo],
       @@ -1161,6 +1186,11 @@ func (s *TemplateStore) insertTemplate2(
                }
        
                if !replace && existingFound {
       +                // Always replace inline partials to allow for reloading.
       +                replace = subCategory == SubCategoryInline && nkExisting.subCategory == SubCategoryInline
       +        }
       +
       +        if !replace && existingFound {
                        if len(pi.Identifiers()) >= len(nkExisting.PathInfo.Identifiers()) {
                                // e.g. /pages/home.foo.html and  /pages/home.html where foo may be a valid language name in another site.
                                return nil, nil
       @@ -1190,7 +1220,7 @@ func (s *TemplateStore) insertTemplate2(
                return ti, nil
        }
        
       -func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) bool, replace bool) error {
       +func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) bool, partialRebuild bool) error {
                if include == nil {
                        include = func(fi hugofs.FileMetaInfo) bool {
                                return true
       @@ -1372,7 +1402,7 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
        
                        }
        
       -                if replace && pi.NameNoIdentifier() == baseNameBaseof {
       +                if partialRebuild && pi.NameNoIdentifier() == baseNameBaseof {
                                // A baseof file has changed.
                                resetBaseVariants = true
                        }
       @@ -1380,12 +1410,12 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
                        var ti *TemplInfo
                        var err error
                        if pi.Type() == paths.TypeShortcode {
       -                        ti, err = s.insertShortcode(pi, fi, replace, s.treeShortcodes)
       +                        ti, err = s.insertShortcode(pi, fi, partialRebuild, s.treeShortcodes)
                                if err != nil || ti == nil {
                                        return err
                                }
                        } else {
       -                        ti, err = s.insertTemplate(pi, fi, replace, s.treeMain)
       +                        ti, err = s.insertTemplate(pi, fi, SubCategoryMain, partialRebuild, s.treeMain)
                                if err != nil || ti == nil {
                                        return err
                                }
       @@ -1419,7 +1449,7 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
                        desc.IsPlainText = outputFormat.IsPlainText
                        desc.MediaType = mediaType.Type
        
       -                ti, err := s.insertTemplate2(pi, fi, targetPath, category, desc, true, true, s.treeMain)
       +                ti, err := s.insertTemplate2(pi, fi, targetPath, category, SubCategoryMain, desc, true, true, s.treeMain)
                        if err != nil {
                                return err
                        }
       @@ -1430,6 +1460,7 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
                        if err := s.tns.readTemplateInto(ti); err != nil {
                                return err
                        }
       +
                }
        
                if resetBaseVariants {
       @@ -1456,7 +1487,15 @@ func (s *TemplateStore) key(dir string) string {
                return paths.TrimTrailing(dir)
        }
        
       -func (s *TemplateStore) parseTemplates() error {
       +func (s *TemplateStore) createTemplatesSnapshot() error {
       +        s.templatesSnapshotSet.Reset()
       +        for t := range s.allRawTemplates() {
       +                s.templatesSnapshotSet.Set(getParseTree(t), struct{}{})
       +        }
       +        return nil
       +}
       +
       +func (s *TemplateStore) parseTemplates(replace bool) error {
                if err := func() error {
                        // Read and parse all templates.
                        for _, v := range s.treeMain.All() {
       @@ -1464,7 +1503,7 @@ func (s *TemplateStore) parseTemplates() error {
                                        if vv.state == processingStateTransformed {
                                                continue
                                        }
       -                                if err := s.parseTemplate(vv); err != nil {
       +                                if err := s.parseTemplate(vv, replace); err != nil {
                                                return err
                                        }
                                }
       @@ -1484,7 +1523,7 @@ func (s *TemplateStore) parseTemplates() error {
                                                        // The regular expression used to detect if a template needs a base template has some
                                                        // rare false positives. Assume we don't need one.
                                                        vv.noBaseOf = true
       -                                                if err := s.parseTemplate(vv); err != nil {
       +                                                if err := s.parseTemplate(vv, replace); err != nil {
                                                                return err
                                                        }
                                                        continue
       @@ -1513,7 +1552,7 @@ func (s *TemplateStore) parseTemplates() error {
                                        if vvv.state == processingStateTransformed {
                                                continue
                                        }
       -                                if err := s.parseTemplate(vvv); err != nil {
       +                                if err := s.parseTemplate(vvv, replace); err != nil {
                                                return err
                                        }
                                }