mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-24 21:56:05 +02:00
✨ Implement Page bundling and image handling
This commit is not the smallest in Hugo's history. Some hightlights include: * Page bundles (for complete articles, keeping images and content together etc.). * Bundled images can be processed in as many versions/sizes as you need with the three methods `Resize`, `Fill` and `Fit`. * Processed images are cached inside `resources/_gen/images` (default) in your project. * Symbolic links (both files and dirs) are now allowed anywhere inside /content * A new table based build summary * The "Total in nn ms" now reports the total including the handling of the files inside /static. So if it now reports more than you're used to, it is just **more real** and probably faster than before (see below). A site building benchmark run compared to `v0.31.1` shows that this should be slightly faster and use less memory: ```bash ▶ ./benchSite.sh "TOML,num_langs=.*,num_root_sections=5,num_pages=(500|1000),tags_per_page=5,shortcodes,render" benchmark old ns/op new ns/op delta BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 101785785 78067944 -23.30% BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 185481057 149159919 -19.58% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 103149918 85679409 -16.94% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 203515478 169208775 -16.86% benchmark old allocs new allocs delta BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 532464 391539 -26.47% BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 1056549 772702 -26.87% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 555974 406630 -26.86% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 1086545 789922 -27.30% benchmark old bytes new bytes delta BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 53243246 43598155 -18.12% BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 105811617 86087116 -18.64% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 54558852 44545097 -18.35% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 106903858 86978413 -18.64% ``` Fixes #3651 Closes #3158 Fixes #1014 Closes #2021 Fixes #1240 Updates #3757
This commit is contained in:
156
hugolib/page.go
156
hugolib/page.go
@@ -25,6 +25,8 @@ import (
|
||||
"github.com/bep/gitmap"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/resource"
|
||||
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@@ -80,6 +82,8 @@ const (
|
||||
kindSitemap = "sitemap"
|
||||
kindRobotsTXT = "robotsTXT"
|
||||
kind404 = "404"
|
||||
|
||||
pageResourceType = "page"
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
@@ -101,6 +105,12 @@ type Page struct {
|
||||
// This collection will be nil for regular pages.
|
||||
Pages Pages
|
||||
|
||||
// Since Hugo 0.32, a Page can have resources such as images and CSS associated
|
||||
// with itself. The resource will typically be placed relative to the Page,
|
||||
// but templates should use the links (Permalink and RelPermalink)
|
||||
// provided by the Resource object.
|
||||
Resources resource.Resources
|
||||
|
||||
// translations will contain references to this page in other language
|
||||
// if available.
|
||||
translations Pages
|
||||
@@ -155,9 +165,6 @@ type Page struct {
|
||||
// workContent is a copy of rawContent that may be mutated during site build.
|
||||
workContent []byte
|
||||
|
||||
// state telling if this is a "new page" or if we have rendered it previously.
|
||||
rendered bool
|
||||
|
||||
// whether the content is in a CJK language.
|
||||
isCJKLanguage bool
|
||||
|
||||
@@ -218,8 +225,9 @@ type Page struct {
|
||||
Sitemap Sitemap
|
||||
|
||||
URLPath
|
||||
permalink string
|
||||
relPermalink string
|
||||
permalink string
|
||||
relPermalink string
|
||||
relPermalinkBase string // relPermalink without extension
|
||||
|
||||
layoutDescriptor output.LayoutDescriptor
|
||||
|
||||
@@ -263,6 +271,10 @@ func (p *Page) PubDate() time.Time {
|
||||
return p.Date
|
||||
}
|
||||
|
||||
func (*Page) ResourceType() string {
|
||||
return pageResourceType
|
||||
}
|
||||
|
||||
func (p *Page) RSSLink() template.URL {
|
||||
f, found := p.outputFormats.GetByName(output.RSSFormat.Name)
|
||||
if !found {
|
||||
@@ -726,22 +738,29 @@ func (p *Page) getRenderingConfig() *helpers.BlackFriday {
|
||||
}
|
||||
|
||||
func (s *Site) newPage(filename string) *Page {
|
||||
sp := source.NewSourceSpec(s.Cfg, s.Fs)
|
||||
p := &Page{
|
||||
fi := newFileInfo(
|
||||
s.SourceSpec,
|
||||
s.absContentDir(),
|
||||
filename,
|
||||
nil,
|
||||
bundleNot,
|
||||
)
|
||||
return s.newPageFromFile(fi)
|
||||
}
|
||||
|
||||
func (s *Site) newPageFromFile(fi *fileInfo) *Page {
|
||||
return &Page{
|
||||
pageInit: &pageInit{},
|
||||
Kind: kindFromFilename(filename),
|
||||
Kind: kindFromFilename(fi.Path()),
|
||||
contentType: "",
|
||||
Source: Source{File: *sp.NewFile(filename)},
|
||||
Source: Source{File: fi},
|
||||
Keywords: []string{}, Sitemap: Sitemap{Priority: -1},
|
||||
Params: make(map[string]interface{}),
|
||||
translations: make(Pages, 0),
|
||||
sections: sectionsFromFilename(filename),
|
||||
sections: sectionsFromDir(fi.Dir()),
|
||||
Site: &s.Info,
|
||||
s: s,
|
||||
}
|
||||
|
||||
s.Log.DEBUG.Println("Reading from", p.File.Path())
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Page) IsRenderable() bool {
|
||||
@@ -910,8 +929,8 @@ func (p *Page) LinkTitle() string {
|
||||
}
|
||||
|
||||
func (p *Page) shouldBuild() bool {
|
||||
return shouldBuild(p.s.Cfg.GetBool("buildFuture"), p.s.Cfg.GetBool("buildExpired"),
|
||||
p.s.Cfg.GetBool("buildDrafts"), p.Draft, p.PublishDate, p.ExpiryDate)
|
||||
return shouldBuild(p.s.BuildFuture, p.s.BuildExpired,
|
||||
p.s.BuildDrafts, p.Draft, p.PublishDate, p.ExpiryDate)
|
||||
}
|
||||
|
||||
func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool,
|
||||
@@ -967,20 +986,91 @@ func (p *Page) RelPermalink() string {
|
||||
return p.relPermalink
|
||||
}
|
||||
|
||||
func (p *Page) initURLs() error {
|
||||
if len(p.outputFormats) == 0 {
|
||||
p.outputFormats = p.s.outputFormats[p.Kind]
|
||||
func (p *Page) subResourceLinkFactory(base string) string {
|
||||
return path.Join(p.relPermalinkBase, base)
|
||||
}
|
||||
|
||||
func (p *Page) prepareForRender(cfg *BuildCfg) error {
|
||||
s := p.s
|
||||
|
||||
if !p.shouldRenderTo(s.rc.Format) {
|
||||
// No need to prepare
|
||||
return nil
|
||||
}
|
||||
|
||||
var shortcodeUpdate bool
|
||||
if p.shortcodeState != nil {
|
||||
shortcodeUpdate = p.shortcodeState.updateDelta()
|
||||
}
|
||||
|
||||
if !shortcodeUpdate && !cfg.whatChanged.other {
|
||||
// No need to process it again.
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we got this far it means that this is either a new Page pointer
|
||||
// or a template or similar has changed so wee need to do a rerendering
|
||||
// of the shortcodes etc.
|
||||
|
||||
// If in watch mode or if we have multiple output formats,
|
||||
// we need to keep the original so we can
|
||||
// potentially repeat this process on rebuild.
|
||||
needsACopy := p.s.running() || len(p.outputFormats) > 1
|
||||
var workContentCopy []byte
|
||||
if needsACopy {
|
||||
workContentCopy = make([]byte, len(p.workContent))
|
||||
copy(workContentCopy, p.workContent)
|
||||
} else {
|
||||
// Just reuse the same slice.
|
||||
workContentCopy = p.workContent
|
||||
}
|
||||
|
||||
if p.Markup == "markdown" {
|
||||
tmpContent, tmpTableOfContents := helpers.ExtractTOC(workContentCopy)
|
||||
p.TableOfContents = helpers.BytesToHTML(tmpTableOfContents)
|
||||
workContentCopy = tmpContent
|
||||
}
|
||||
rel := p.createRelativePermalink()
|
||||
|
||||
var err error
|
||||
p.permalink, err = p.s.permalinkForOutputFormat(rel, p.outputFormats[0])
|
||||
if err != nil {
|
||||
return err
|
||||
if workContentCopy, err = handleShortcodes(p, workContentCopy); err != nil {
|
||||
s.Log.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err)
|
||||
}
|
||||
rel = p.s.PathSpec.PrependBasePath(rel)
|
||||
p.relPermalink = rel
|
||||
p.layoutDescriptor = p.createLayoutDescriptor()
|
||||
|
||||
if p.Markup != "html" {
|
||||
|
||||
// Now we know enough to create a summary of the page and count some words
|
||||
summaryContent, err := p.setUserDefinedSummaryIfProvided(workContentCopy)
|
||||
|
||||
if err != nil {
|
||||
s.Log.ERROR.Printf("Failed to set user defined summary for page %q: %s", p.Path(), err)
|
||||
} else if summaryContent != nil {
|
||||
workContentCopy = summaryContent.content
|
||||
}
|
||||
|
||||
p.Content = helpers.BytesToHTML(workContentCopy)
|
||||
|
||||
if summaryContent == nil {
|
||||
if err := p.setAutoSummary(); err != nil {
|
||||
s.Log.ERROR.Printf("Failed to set user auto summary for page %q: %s", p.pathOrTitle(), err)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
p.Content = helpers.BytesToHTML(workContentCopy)
|
||||
}
|
||||
|
||||
//analyze for raw stats
|
||||
p.analyzePage()
|
||||
|
||||
// Handle bundled pages.
|
||||
for _, r := range p.Resources.ByType(pageResourceType) {
|
||||
p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages)
|
||||
bp := r.(*Page)
|
||||
if err := bp.prepareForRender(cfg); err != nil {
|
||||
s.Log.ERROR.Printf("Failed to prepare bundled page %q for render: %s", bp.BaseFileName(), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1849,14 +1939,18 @@ func (p *Page) addLangPathPrefixIfFlagSet(outfile string, should bool) string {
|
||||
return outfile
|
||||
}
|
||||
|
||||
func sectionsFromFilename(filename string) []string {
|
||||
var sections []string
|
||||
dir, _ := filepath.Split(filename)
|
||||
dir = strings.TrimSuffix(dir, helpers.FilePathSeparator)
|
||||
if dir == "" {
|
||||
func sectionsFromDir(dirname string) []string {
|
||||
sections := strings.Split(dirname, helpers.FilePathSeparator)
|
||||
if len(sections) == 1 {
|
||||
if sections[0] == "" {
|
||||
return nil
|
||||
}
|
||||
return sections
|
||||
}
|
||||
sections = strings.Split(dir, helpers.FilePathSeparator)
|
||||
if len(sections) > 1 && sections[0] == "" {
|
||||
return sections[1:]
|
||||
}
|
||||
|
||||
return sections
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user