Fix live reload when editing inline partials

Fixes #13723
This commit is contained in:
Bjørn Erik Pedersen
2025-05-21 11:25:32 +02:00
parent 970b887ba1
commit 0c7b1a3f26
6 changed files with 227 additions and 90 deletions

View File

@@ -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,59 +946,77 @@ 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
}
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 {
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
}
}
for _, t := range p.baseofTextClones {
if err := addIfNotSeen(true, p.templatesIn(t)...); err != nil {
return err
if ti != nil {
ti.Template = templ
ti.noBaseOf = true
ti.subCategory = SubCategoryInline
ti.D.IsPlainText = isText(templ)
}
}
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.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
}
}
}
}
}
func (s *TemplateStore) insertEmbedded() error {
return fs.WalkDir(embeddedTemplatesFs, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
@@ -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],
@@ -1160,6 +1185,11 @@ func (s *TemplateStore) insertTemplate2(
replace = fi.Meta().ModuleOrdinal < nkExisting.Fi.Meta().ModuleOrdinal
}
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.
@@ -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
}
}