mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
Fix some server rebuild issues for non-HTML custom output formats
The failing test case here is * A custom search output format defined on the home page, marked as `noAlternative` and not `permalinkable` * In fast render mode, when making a change to a data source for that search output format, the JSON file was not refreshed. There are variants of the above, but the gist of it is: * The change set was correctly determined, but since the search JSON file was not in the recently visited browser stack, we skipped rendering it. Running with `hugo server --disableFastRender` would be a workaround for the above. This commit fixes this by: * Adding a check for the HTTP request header `Sec-Fetch-Mode = navigation` to the condition for if we should track server request as a user navigation (and not e.g. a HTTP request for a linked CSS stylesheet). * Making sure that we compare against the real relative URL for non-permalinkable output formats. Fixes #13014
This commit is contained in:
@@ -416,8 +416,8 @@ type BuildCfg struct {
|
||||
// Set in server mode when the last build failed for some reason.
|
||||
ErrRecovery bool
|
||||
|
||||
// Recently visited URLs. This is used for partial re-rendering.
|
||||
RecentlyVisited *types.EvictingStringQueue
|
||||
// Recently visited or touched URLs. This is used for partial re-rendering.
|
||||
RecentlyTouched *types.EvictingQueue[string]
|
||||
|
||||
// Can be set to build only with a sub set of the content source.
|
||||
ContentInclusionFilter *glob.FilenameFilter
|
||||
@@ -429,7 +429,7 @@ type BuildCfg struct {
|
||||
}
|
||||
|
||||
// shouldRender returns whether this output format should be rendered or not.
|
||||
func (cfg *BuildCfg) shouldRender(p *pageState) bool {
|
||||
func (cfg *BuildCfg) shouldRender(infol logg.LevelLogger, p *pageState) bool {
|
||||
if p.skipRender() {
|
||||
return false
|
||||
}
|
||||
@@ -457,18 +457,20 @@ func (cfg *BuildCfg) shouldRender(p *pageState) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if p.outputFormat().IsHTML {
|
||||
// This is fast render mode and the output format is HTML,
|
||||
// rerender if this page is one of the recently visited.
|
||||
return cfg.RecentlyVisited.Contains(p.RelPermalink())
|
||||
if relURL := p.getRelURL(); relURL != "" {
|
||||
if cfg.RecentlyTouched.Contains(relURL) {
|
||||
infol.Logf("render recently touched URL %q (%s)", relURL, p.outputFormat().Name)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// In fast render mode, we want to avoid re-rendering the sitemaps etc. and
|
||||
// other big listings whenever we e.g. change a content file,
|
||||
// but we want partial renders of the recently visited pages to also include
|
||||
// but we want partial renders of the recently touched pages to also include
|
||||
// alternative formats of the same HTML page (e.g. RSS, JSON).
|
||||
for _, po := range p.pageOutputs {
|
||||
if po.render && po.f.IsHTML && cfg.RecentlyVisited.Contains(po.RelPermalink()) {
|
||||
if po.render && po.f.IsHTML && cfg.RecentlyTouched.Contains(po.getRelURL()) {
|
||||
infol.Logf("render recently touched URL %q, %s version of %s", po.getRelURL(), po.f.Name, p.outputFormat().Name)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@@ -341,7 +341,7 @@ func (h *HugoSites) render(l logg.LevelLogger, config *BuildCfg) error {
|
||||
loggers.TimeTrackf(l, start, h.buildCounters.loggFields(), "")
|
||||
}()
|
||||
|
||||
siteRenderContext := &siteRenderContext{cfg: config, multihost: h.Configs.IsMultihost}
|
||||
siteRenderContext := &siteRenderContext{cfg: config, infol: l, multihost: h.Configs.IsMultihost}
|
||||
|
||||
renderErr := func(err error) error {
|
||||
if err == nil {
|
||||
@@ -902,12 +902,12 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
|
||||
|
||||
needsPagesAssemble = true
|
||||
|
||||
if config.RecentlyVisited != nil {
|
||||
if config.RecentlyTouched != nil {
|
||||
// Fast render mode. Adding them to the visited queue
|
||||
// avoids rerendering them on navigation.
|
||||
for _, id := range changes {
|
||||
if p, ok := id.(page.Page); ok {
|
||||
config.RecentlyVisited.Add(p.RelPermalink())
|
||||
config.RecentlyTouched.Add(p.RelPermalink())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -487,11 +487,11 @@ func (s *IntegrationTestBuilder) BuildPartialE(urls ...string) (*IntegrationTest
|
||||
if !s.Cfg.Running {
|
||||
panic("BuildPartial can only be used in server mode")
|
||||
}
|
||||
visited := types.NewEvictingStringQueue(len(urls))
|
||||
visited := types.NewEvictingQueue[string](len(urls))
|
||||
for _, url := range urls {
|
||||
visited.Add(url)
|
||||
}
|
||||
buildCfg := BuildCfg{RecentlyVisited: visited, PartialReRender: true}
|
||||
buildCfg := BuildCfg{RecentlyTouched: visited, PartialReRender: true}
|
||||
return s, s.build(buildCfg)
|
||||
}
|
||||
|
||||
|
@@ -71,11 +71,12 @@ func newPagePaths(ps *pageState) (pagePaths, error) {
|
||||
// Use the main format for permalinks, usually HTML.
|
||||
permalinksIndex := 0
|
||||
if f.Permalinkable {
|
||||
// Unless it's permalinkable
|
||||
// Unless it's permalinkable.
|
||||
permalinksIndex = i
|
||||
}
|
||||
|
||||
targets[f.Name] = targetPathsHolder{
|
||||
relURL: relPermalink,
|
||||
paths: paths,
|
||||
OutputFormat: pageOutputFormats[permalinksIndex],
|
||||
}
|
||||
|
@@ -469,13 +469,21 @@ type pagePerOutputProviders interface {
|
||||
|
||||
type targetPather interface {
|
||||
targetPaths() page.TargetPaths
|
||||
getRelURL() string
|
||||
}
|
||||
|
||||
type targetPathsHolder struct {
|
||||
paths page.TargetPaths
|
||||
// relURL is usually the same as OutputFormat.RelPermalink, but can be different
|
||||
// for non-permalinkable output formats. These shares RelPermalink with the main (first) output format.
|
||||
relURL string
|
||||
paths page.TargetPaths
|
||||
page.OutputFormat
|
||||
}
|
||||
|
||||
func (t targetPathsHolder) getRelURL() string {
|
||||
return t.relURL
|
||||
}
|
||||
|
||||
func (t targetPathsHolder) targetPaths() page.TargetPaths {
|
||||
return t.paths
|
||||
}
|
||||
|
@@ -357,8 +357,8 @@ RegularPages: {{ range .Site.RegularPages }}{{ .RelPermalink }}|{{ end }}$
|
||||
}
|
||||
|
||||
func TestRebuildRenameDirectoryWithBranchBundleFastRender(t *testing.T) {
|
||||
recentlyVisited := types.NewEvictingStringQueue(10).Add("/a/b/c/")
|
||||
b := TestRunning(t, rebuildFilesSimple, func(cfg *IntegrationTestConfig) { cfg.BuildCfg = BuildCfg{RecentlyVisited: recentlyVisited} })
|
||||
recentlyVisited := types.NewEvictingQueue[string](10).Add("/a/b/c/")
|
||||
b := TestRunning(t, rebuildFilesSimple, func(cfg *IntegrationTestConfig) { cfg.BuildCfg = BuildCfg{RecentlyTouched: recentlyVisited} })
|
||||
b.RenameDir("content/mysection", "content/mysectionrenamed").Build()
|
||||
b.AssertFileContent("public/mysectionrenamed/index.html", "My Section")
|
||||
b.AssertFileContent("public/mysectionrenamed/mysectionbundle/index.html", "My Section Bundle")
|
||||
@@ -1181,6 +1181,49 @@ Content: {{ .Content }}
|
||||
b.AssertFileContent("public/index.html", "Content: <p>Home</p>")
|
||||
}
|
||||
|
||||
// Issue #13014.
|
||||
func TestRebuildEditNotPermalinkableCustomOutputFormatTemplateInFastRenderMode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com/docs/"
|
||||
disableLiveReload = true
|
||||
[internal]
|
||||
fastRenderMode = true
|
||||
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404"]
|
||||
[outputFormats]
|
||||
[outputFormats.SearchIndex]
|
||||
baseName = 'Search'
|
||||
isPlainText = true
|
||||
mediaType = 'text/plain'
|
||||
noAlternative = true
|
||||
permalinkable = false
|
||||
|
||||
[outputs]
|
||||
home = ['HTML', 'SearchIndex']
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: "Home"
|
||||
---
|
||||
Home.
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
-- layouts/_default/index.searchindex.txt --
|
||||
Text. {{ .Title }}|{{ .RelPermalink }}|
|
||||
|
||||
`
|
||||
b := TestRunning(t, files, TestOptInfo())
|
||||
|
||||
b.AssertFileContent("public/search.txt", "Text.")
|
||||
|
||||
b.EditFileReplaceAll("layouts/_default/index.searchindex.txt", "Text.", "Text Edited.").Build()
|
||||
|
||||
b.BuildPartial("/docs/search.txt")
|
||||
|
||||
b.AssertFileContent("public/search.txt", "Text Edited.")
|
||||
}
|
||||
|
||||
func TestRebuildVariationsAssetsJSImport(t *testing.T) {
|
||||
t.Parallel()
|
||||
files := `
|
||||
|
@@ -20,6 +20,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/hugolib/doctree"
|
||||
|
||||
@@ -33,6 +34,8 @@ import (
|
||||
type siteRenderContext struct {
|
||||
cfg *BuildCfg
|
||||
|
||||
infol logg.LevelLogger
|
||||
|
||||
// languageIdx is the zero based index of the site.
|
||||
languageIdx int
|
||||
|
||||
@@ -86,7 +89,7 @@ func (s *Site) renderPages(ctx *siteRenderContext) error {
|
||||
Tree: s.pageMap.treePages,
|
||||
Handle: func(key string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
if p, ok := n.(*pageState); ok {
|
||||
if cfg.shouldRender(p) {
|
||||
if cfg.shouldRender(ctx.infol, p) {
|
||||
select {
|
||||
case <-s.h.Done():
|
||||
return true, nil
|
||||
|
Reference in New Issue
Block a user