-
-
Notifications
You must be signed in to change notification settings - Fork 7.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reimplement and simplify Hugo's template system #13541
base: master
Are you sure you want to change the base?
Conversation
This is really, really good. |
Yea, I'm pretty happy with the end result, but I have changed my mind a few times. Note that this is still a draft; it's mostly working, but there's some TODOs sprinkled around, some commented out tests, and everything I say about shortcodes above isn't yet entirely true, but it will be. |
I've gone-to-market with less, but in most cases under protest. |
aaf7932
to
193a885
Compare
I am not trying to increase the scope of this PR, but am curious if either of these are affected: |
6a025f2
to
6ac1b67
Compare
@jmooring none of those issues is somehow fixed by this (whatever that would mean). But the order of things is certainly easier to reason about in the new setup: if err := s.insertTemplates(nil, false); err != nil {
return nil, err
}
if err := s.insertEmbedded(); err != nil {
return nil, err
}
if err := s.parseTemplates(); err != nil {
return nil, err
}
if err := s.extractInlinePartials(); err != nil {
return nil, err
}
if err := s.transformTemplates(); err != nil {
return nil, err
}
if err := s.tns.createPrototypes(true); err != nil {
return nil, err
}
if err := s.prepareTemplates(); err != nil {
return nil, err
} So, while we Footnotes
|
Fixes gohugoio#13545 Fixes gohugoio#13515 Closes gohugoio#7964 Closes gohugoio#13365 Closes gohugoio#12988 Closes gohugoio#4891
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR reimplements and simplifies Hugo’s template system by unifying template lookup rules across page layouts, shortcodes, and render hook templates. It also refactors internal data structures (e.g. the radix tree and path parsing logic) and renames constants and methods for improved consistency.
- Unified template lookup using a single code path with a new TemplateStore.
- Refactored concurrency handling in the radix tree and improved path parsing to incorporate language, output format, and type.
- Updated tests and configuration to reflect these internal API and naming changes.
Reviewed Changes
Copilot reviewed 133 out of 133 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
hugolib/doctree/simpletree.go | Refactored tree interfaces and locking; clarified thread safety. |
hugolib/content_render_hooks_test.go | Added tests for render hooks and shortcode reuse behavior. |
hugolib/content_map_page.go | Updated references from old constants to new type names. |
hugolib/content_map.go | Adjusted switch cases to newer type constants and template store ref. |
hugolib/content_factory.go | Replaced old template handler calls with TemplateStore methods. |
hugolib/alias_test.go | Updated test setup to use inline file definitions and new templates. |
hugolib/alias.go | Switched alias handling to use the new TemplateStore API. |
deps/deps.go | Removed TemplateProvider in favor of TemplateStore and added counters. |
create/content.go | Changed key generation for archetypes to exclude language/format. |
config/allconfig/allconfig.go | Extended ContentPathParser with output format detection. |
common/types/types.go | Added Locker and RWLocker interfaces for consistency. |
common/paths/* | Renamed and reworked path parsing and type handling logic. |
common/maps/* | Minor enhancements to ordered map and cache functionality. |
commands/server.go | Updated error template lookup to use the TemplateStore. |
@jmooring All tests are green and I have completed my TODO list. I will do some more manual testing of this myself, but I would appreciate if you could take it for a spin, and especially see if you agree with my choices in the "lookup department"; the most opinionated is probably the path/depth logic. I have tried to explain it above, I'm not sure how well I succeeded. |
At the top of the page you'll see this: |
@jmooring good catch, odd that none of my tests caught this. Probably a confusion between text and html parsing for the partial func. If you do this, it "works": {{ partialCached "head/css.html" . | safeHTML }} Which is obviously not what we want. I will add a test for this and fix it. |
Here's what happens:
The isn't entirely incorrect and comes from us now handling all templates more or less the same in this department, but I see that we need to add an additional check to make sure that the suffix matches the output format. |
Found while verifying behavior of #13515.
I don't think this is anything new. See: |
Also found while verifying behavior of #13515.
As expected, both are published to |
This was unexpected, perhaps a specificity problem with the "all" layout.
test casefunc TestLayoutAllRevised(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['rss','sitemap','taxonomy','term']
-- content/s1/p1.md --
---
title: p1
---
-- content/s2/p2.md --
---
title: p2
---
-- layouts/single.html --
layouts/single.html
-- layouts/list.html --
layouts/list.html
-- layouts/s1/all.html --
layouts/s1/all.html
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", "layouts/list.html")
b.AssertFileContent("public/s1/index.html", "layouts/s1/all.html") // fails
b.AssertFileContent("public/s1/p1/index.html", "layouts/s1/all.html") // fails
b.AssertFileContent("public/s2/index.html", "layouts/list.html")
b.AssertFileContent("public/s2/p2/index.html", "layouts/single.html")
} |
|
I am not using index as a layout; including the public directory in the tree above might have made that unclear. I just added a test case. |
OK, sorry, I read that a little fast. I have put it on my list. |
@jmooring I have pushed fixes to 2 issues reported above, the path warnings and the all layout behavior. |
This is also related to #13515. The existing behavior may be correct, but it caught my attention. When given a content path such as p2.x, content adapters do not remove the extraneous portions of the file name. integration testfunc TestContentTemplateDotSegments(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
-- content/s2/_content.gotmpl --
{{ $page := dict "path" "p2.x" "title" "p2 x"}}
{{ .AddPage $page }}
-- content/s1/p1.x.md --
---
title: p1 x
---
-- layouts/single.html --
{{ .Title }}
`
b := hugolib.Test(t, files)
b.AssertFileExists("public/s1/p1/index.html", true)
b.AssertFileExists("public/s2/p2/index.html", true) // fail, got public/s2/p2.x/index.html instead
} |
I see your point, but I think I'm going to leave this as-is. The "dot" issue is about delmiters in base filenames used to create a path. In your example, the path is pre-defined. There's no filename in play, no delimiters, no ambigouity,. |
Some key differences from
v0.145.0
:section
/type
) when resolving templates.baseof.list.html
, templates), shortcode templates, and render hook templates._shortcodes
and_markup
directories can be added to any level in the file tree.shortcodes
andpartials
are renamed_shortcodes
and_partials
._internal/
templates are removed (but will still work, will be deprecated a later Hugo version), so e.g. the Twitter card template needs to be loaded via{{ partial "twitter_cards.html" . }}
.all
, see Add new standard layout 'all' (in addition to single,list) #13545.Note
I have gone to great lengths to make this as backwards compatible as possible. All existing test passes and I have added lots more. But with the amount of Hugo sites in the wild, I would surprised if we end up with no "my site is broken" reports after releasing this, but we really need to do this.
Identifiers and their weights
Path
. Thedistance
is measured in directory levels from the pagePath
up to the template path.Page
Kind
, one ofhome
,page
,section
,term
ortaxonomy
.all
,list
orsingle
.layout
front matter key, e.g.mylayout
.type
front matter key. For template lookups, this effectively changes the root directory ofPage.Path
.html
.text/html
.en
.link
.Template Resolving
This section covers template resolving for page layouts, shortcodes and render hook templates. Partials has currently a much simpler logic.
To resolve a template, we
w1
,w2
andw3
.w2
andw3
, in that order. For example, given a Page path/foo/bar
, a layout in/layouts/foo/single.html
would win over/layouts/page.html
even if the latter has a higherw1
weight.w1
.layout
is set, we prefer any value beforeall
.There are some special cases to the above:
Example layouts directory
Fixes #13515
Updates #7964
Updates #13365
Updates #12988
Updates #4891
Fix/adjusment tasks
head/css.html
and similar partials.