Skip to content

Commit bf618f7

Browse files
committed
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 gohugoio#13014
1 parent a563783 commit bf618f7

8 files changed

+81
-23
lines changed

commands/server.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,11 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
364364
}
365365

366366
if f.c.fastRenderMode && f.c.errState.buildErr() == nil {
367-
if strings.HasSuffix(requestURI, "/") || strings.HasSuffix(requestURI, "html") || strings.HasSuffix(requestURI, "htm") {
367+
// Sec-Fetch-Dest = document
368+
// Sec-Fetch-Mode should be sent by all recent browser versions, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode#navigate
369+
// Fall back to the file extension if not set.
370+
// The main take here is that we don't want to have CSS/JS files etc. partake in this logic.
371+
if r.Header.Get("Sec-Fetch-Mode") == "navigate" || strings.HasSuffix(requestURI, "/") || strings.HasSuffix(requestURI, "html") || strings.HasSuffix(requestURI, "htm") {
368372
if !f.c.visitedURLs.Contains(requestURI) {
369373
// If not already on stack, re-render that single page.
370374
if err := f.c.partialReRender(requestURI); err != nil {

common/types/evictingqueue.go

+3
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ func (q *EvictingStringQueue) Peek() string {
8888

8989
// PeekAll looks at all the elements in the queue, with the newest first.
9090
func (q *EvictingStringQueue) PeekAll() []string {
91+
if q == nil {
92+
return nil
93+
}
9194
q.mu.Lock()
9295
vals := make([]string, len(q.vals))
9396
copy(vals, q.vals)

hugolib/hugo_sites.go

+9-7
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ type BuildCfg struct {
429429
}
430430

431431
// shouldRender returns whether this output format should be rendered or not.
432-
func (cfg *BuildCfg) shouldRender(p *pageState) bool {
432+
func (cfg *BuildCfg) shouldRender(infol logg.LevelLogger, p *pageState) bool {
433433
if p.skipRender() {
434434
return false
435435
}
@@ -457,18 +457,20 @@ func (cfg *BuildCfg) shouldRender(p *pageState) bool {
457457
return false
458458
}
459459

460-
if p.outputFormat().IsHTML {
461-
// This is fast render mode and the output format is HTML,
462-
// rerender if this page is one of the recently visited.
463-
return cfg.RecentlyVisited.Contains(p.RelPermalink())
460+
if relURL := p.getRelURL(); relURL != "" {
461+
if cfg.RecentlyVisited.Contains(relURL) {
462+
infol.Logf("render recently navigated: %s", relURL)
463+
return true
464+
}
464465
}
465466

466467
// In fast render mode, we want to avoid re-rendering the sitemaps etc. and
467468
// other big listings whenever we e.g. change a content file,
468-
// but we want partial renders of the recently visited pages to also include
469+
// but we want partial renders of the recently navigated pages to also include
469470
// alternative formats of the same HTML page (e.g. RSS, JSON).
470471
for _, po := range p.pageOutputs {
471-
if po.render && po.f.IsHTML && cfg.RecentlyVisited.Contains(po.RelPermalink()) {
472+
if po.render && po.f.IsHTML && cfg.RecentlyVisited.Contains(po.getRelURL()) {
473+
infol.Logf("render recently navigated HTML version: %s", po.getRelURL())
472474
return true
473475
}
474476
}

hugolib/hugo_sites_build.go

+1-12
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ import (
4646
"github.com/gohugoio/hugo/common/paths"
4747
"github.com/gohugoio/hugo/common/rungroup"
4848
"github.com/gohugoio/hugo/config"
49-
"github.com/gohugoio/hugo/resources/page"
5049
"github.com/gohugoio/hugo/resources/page/siteidentities"
5150
"github.com/gohugoio/hugo/resources/postpub"
5251

@@ -341,7 +340,7 @@ func (h *HugoSites) render(l logg.LevelLogger, config *BuildCfg) error {
341340
loggers.TimeTrackf(l, start, h.buildCounters.loggFields(), "")
342341
}()
343342

344-
siteRenderContext := &siteRenderContext{cfg: config, multihost: h.Configs.IsMultihost}
343+
siteRenderContext := &siteRenderContext{cfg: config, infol: l, multihost: h.Configs.IsMultihost}
345344

346345
renderErr := func(err error) error {
347346
if err == nil {
@@ -902,16 +901,6 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
902901

903902
needsPagesAssemble = true
904903

905-
if config.RecentlyVisited != nil {
906-
// Fast render mode. Adding them to the visited queue
907-
// avoids rerendering them on navigation.
908-
for _, id := range changes {
909-
if p, ok := id.(page.Page); ok {
910-
config.RecentlyVisited.Add(p.RelPermalink())
911-
}
912-
}
913-
}
914-
915904
h.pageTrees.treeTaxonomyEntries.DeletePrefix("")
916905

917906
if delete && !isContentDataFile {

hugolib/page__paths.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,17 @@ func newPagePaths(ps *pageState) (pagePaths, error) {
7171
// Use the main format for permalinks, usually HTML.
7272
permalinksIndex := 0
7373
if f.Permalinkable {
74-
// Unless it's permalinkable
74+
// Unless it's permalinkable.
7575
permalinksIndex = i
7676
}
7777

78+
relURL := relPermalink
79+
if relURL == "" {
80+
relURL = paths.RelPermalink(s.PathSpec)
81+
}
82+
7883
targets[f.Name] = targetPathsHolder{
84+
relURL: relURL,
7985
paths: paths,
8086
OutputFormat: pageOutputFormats[permalinksIndex],
8187
}

hugolib/page__per_output.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -469,13 +469,21 @@ type pagePerOutputProviders interface {
469469

470470
type targetPather interface {
471471
targetPaths() page.TargetPaths
472+
getRelURL() string
472473
}
473474

474475
type targetPathsHolder struct {
475-
paths page.TargetPaths
476+
// relURL is usually the same as OutputFormat.RelPermalink, but can be different
477+
// for non-permalinkable output formats. These shares RelPermalink with the main (first) output format.
478+
relURL string
479+
paths page.TargetPaths
476480
page.OutputFormat
477481
}
478482

483+
func (t targetPathsHolder) getRelURL() string {
484+
return t.relURL
485+
}
486+
479487
func (t targetPathsHolder) targetPaths() page.TargetPaths {
480488
return t.paths
481489
}

hugolib/rebuild_test.go

+43
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,49 @@ Content: {{ .Content }}
11811181
b.AssertFileContent("public/index.html", "Content: <p>Home</p>")
11821182
}
11831183

1184+
// Issue #13014.
1185+
func TestRebuildEditNotPermalinkableCustomOutputFormatTemplateInFastRenderMode(t *testing.T) {
1186+
t.Parallel()
1187+
1188+
files := `
1189+
-- hugo.toml --
1190+
baseURL = "https://example.com/docs/"
1191+
disableLiveReload = true
1192+
[internal]
1193+
fastRenderMode = true
1194+
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404"]
1195+
[outputFormats]
1196+
[outputFormats.SearchIndex]
1197+
baseName = 'Search'
1198+
isPlainText = true
1199+
mediaType = 'text/plain'
1200+
noAlternative = true
1201+
permalinkable = false
1202+
1203+
[outputs]
1204+
home = ['HTML', 'SearchIndex']
1205+
-- content/_index.md --
1206+
---
1207+
title: "Home"
1208+
---
1209+
Home.
1210+
-- layouts/index.html --
1211+
Home.
1212+
-- layouts/_default/index.searchindex.txt --
1213+
Text. {{ .Title }}|{{ .RelPermalink }}|
1214+
1215+
`
1216+
b := TestRunning(t, files, TestOptInfo())
1217+
1218+
b.AssertFileContent("public/search.txt", "Text.")
1219+
1220+
b.EditFileReplaceAll("layouts/_default/index.searchindex.txt", "Text.", "Text Edited.").Build()
1221+
1222+
b.BuildPartial("/docs/search.txt")
1223+
1224+
b.AssertFileContent("public/search.txt", "Text Edited.")
1225+
}
1226+
11841227
func TestRebuildVariationsAssetsJSImport(t *testing.T) {
11851228
t.Parallel()
11861229
files := `

hugolib/site_render.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"strings"
2121
"sync"
2222

23+
"github.com/bep/logg"
2324
"github.com/gohugoio/hugo/common/herrors"
2425
"github.com/gohugoio/hugo/hugolib/doctree"
2526

@@ -33,6 +34,8 @@ import (
3334
type siteRenderContext struct {
3435
cfg *BuildCfg
3536

37+
infol logg.LevelLogger
38+
3639
// languageIdx is the zero based index of the site.
3740
languageIdx int
3841

@@ -86,7 +89,7 @@ func (s *Site) renderPages(ctx *siteRenderContext) error {
8689
Tree: s.pageMap.treePages,
8790
Handle: func(key string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
8891
if p, ok := n.(*pageState); ok {
89-
if cfg.shouldRender(p) {
92+
if cfg.shouldRender(ctx.infol, p) {
9093
select {
9194
case <-s.h.Done():
9295
return true, nil

0 commit comments

Comments
 (0)