Skip to content
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

Add a build option to duplicate non-page (e.g. images) resources across languages #11904

Closed
Tracked by #11549
bep opened this issue Jan 19, 2024 · 11 comments
Closed
Tracked by #11549
Assignees
Milestone

Comments

@bep
Copy link
Member

bep commented Jan 19, 2024

See #11453.

That is mostly a good thing.

But it doesn't work if:

  • The multilingual site doesn't have image render hooks.
  • The multilingual site have Markup formats (e.g. HTML, Asciidoc) that does not support render hooks.

So, I suggest that we extend the front matter build options to look something like this:

[_build]
  list = 'always'
  publishResources = true
  duplicateResourceFiles = true
  render = 'always'

When duplicateResourceFiles = true we copy non-page resources (e.g. foo.png) for the languages without a dedicated version.

@bep bep added the Proposal label Jan 19, 2024
@bep bep added this to the v0.122.0 milestone Jan 19, 2024
@bep bep self-assigned this Jan 19, 2024
@jmooring
Copy link
Member

jmooring commented Jan 19, 2024

In support of #11453 and #11540, for multilingual sites with page resources shared between languages, sites will need both link and image render hooks that capture the page resource and use its .RelPermalink.

structure

content/
├── my-bundle/
│   ├── index.de.md
│   ├── index.en.md
│   ├── kitten.en.jpg
│   └── notes.en.pdf
├── _index.de.md
└── _index.en.md

published

├── de/
│   ├── my-bundle/
│   │   └── index.html
│   └── index.html
├── en/
│   ├── my-bundle/
│   │   ├── index.html
│   │   ├── kitten.jpg  <-- de site will need image render hook to get this URL
│   │   └── notes.pdf   <-- de site will need link render hook to get this URL
│   └── index.html
├── favicon.ico
└── index.html

This example contains candidate image and link render hooks, and comprehensive test cases for each.

git clone --single-branch -b hugo-github-issue-11453 https://github.com/jmooring/hugo-testing hugo-github-issue-11453
cd hugo-github-issue-11453
hugo server
layouts/_default/_markup/render-image.html
{{- $u := urls.Parse .Destination -}}
{{- $src := $u.String -}}
{{- if not $u.IsAbs -}}
  {{- with or (.Page.Resources.Get $u.Path) (resources.Get $u.Path) -}}
    {{- $src = .RelPermalink -}}
  {{- end -}}
{{- end -}}
{{- $attributes := dict "alt" .Text "src" $src "title" .Title -}}
<img
  {{- range $k, $v := $attributes -}}
    {{- if $v -}}
      {{- printf " %s=%q" $k $v | safeHTMLAttr -}}
    {{- end -}}
  {{- end -}}
>
{{- /**/ -}}
layouts/_default/_markup/render-link.html
{{- $u := urls.Parse .Destination -}}
{{- $href := $u.String -}}
{{- if not $u.IsAbs -}}
  {{- with or
    ($.Page.GetPage $u.Path)
    ($.Page.GetPage (strings.TrimRight "/" $u.Path))
    ($.Page.Resources.Get $u.Path)
    (resources.Get $u.Path)
  -}}
    {{- $href = .RelPermalink -}}
    {{- with $u.RawQuery -}}
      {{- $href = printf "%s?%s" $href . -}}
    {{- end -}}
    {{- with $u.Fragment -}}
      {{- $href = printf "%s#%s" $href . -}}
    {{- end -}}
  {{- end -}}
{{- end -}}
{{- $attributes := dict "href" $href "title" .Title -}}
<a
  {{- range $k, $v := $attributes -}}
    {{- if $v -}}
      {{- printf " %s=%q" $k $v | safeHTMLAttr -}}
    {{- end -}}
  {{- end -}}
>{{ .Text | safeHTML }}</a>
{{- /**/ -}}

If we add these (or something similar) as built-in templates, we can close #10585.

@bep bep changed the title Consider adding a default link render hook (at least for multilingual sites) Add a build option to duplicate non-page (e.g. images) resources across languages Jan 20, 2024
@bep
Copy link
Member Author

bep commented Jan 20, 2024

@jmooring yea, I have slept on this, and the hook approach doesn't really work. See my revised proposal above.Does that make sense to you?

@bep
Copy link
Member Author

bep commented Jan 20, 2024

I have revised the proposal a little:

@jmooring
Copy link
Member

jmooring commented Jan 20, 2024

My thoughts...

Not duplicating page resources is a huge win for multilingual, single host sites. For something this valuable, it would be great if there were a simple on/off switch somewhere.

The challenges with enabling it by default are:

  1. The site must use link and image render hooks to capture the markdown destination as a resource.
  2. Markdown is the only content format that supports render hooks.

Item 1 doesn't concern me so much; I have advocated for these before in #10585. Proper resolution of markdown link and image destinations has been a challenge for site, theme, and content authors for a very long time. We can handle this initially with documented examples, and perhaps create embedded templates at some point.

With respect to Item 2:

  • 99% of sites use markdown only.
  • The other 1% may be entirely of one format, or a mix of formats. For example, I've seen sites where one section is markdown, while another is reStructuredText. I've also seen a mix of formats in the same section.

Enabling render hooks for other content formats is a non-starter, so... can we limit resource de-duplication to markdown pages? There's an edge case that looks like this, but probably uncommon:

content/
└── posts/
    └── post-1/
        ├── a.jpg
        ├── index.de.adoc
        ├── index.en.md
        ├── index.es.org
        ├── index.fr.pdc
        ├── index.it.html
        └── index.pt.rst

If we can limit resource de-duplication to markdown pages...

A) Disable the feature by default with an on switch in site config under markup.goldmark (the feature is specific to markdown, and render hooks are limited to Goldmark).

B) Document its usage.

  • Are you using link and/or image render hooks? If no, use these examples, if yes, proceed to next step
  • Build site and run link checker (e.g., muffet) to verify internal links1
  • If you have related errors, compare your render hooks to the examples...

And we have to do B regardless of how this feature is enabled.

Footnotes

  1. A built-in link checker would be useful at some point, as suggested in #5080, perhaps as a CLI flag.

@bep
Copy link
Member Author

bep commented Jan 20, 2024

can we limit resource de-duplication to markdown pages?

I hadn't thought about that, but that actually makes sense and should be a decent compromise.

content/
└── posts/
    └── post-1/
        ├── a.jpg
        ├── index.de.adoc
        ├── index.en.md
        ├── index.es.org
        ├── index.fr.pdc
        ├── index.it.html
        └── index.pt.rst

That looks ... made up. But if we say:

[markup]
  [markup.goldmark]
        duplicateResourceFiles = false

And somehow, default to true for the others.

It is the owner of the resource that dictates the behaviour; in the example above (assuming the default content language is en), the resource owner is index.en.md. The site owner would need to switch to duplicateResourceFiles = true in Goldmark to make it work for the other formats.

But with that we're back to a situation where we probably need some default render hooks ...

@jmooring
Copy link
Member

jmooring commented Jan 20, 2024

we probably need some default render hooks

See #11904 (comment) for candidates and test cases. I am very comfortable with these for any new sites, and mostly comfortable for existing sites, but difficult to know for certain (there's some wacky stuff out there).

Multilingual, single host sites with existing render hooks will have to run a link checker before going live to make sure their hooks work as required. See my previous comment.

That looks ... made up

Definitely. I have never seen anything like it.

@bep
Copy link
Member Author

bep commented Jan 20, 2024

We could probably safeguard this by doing:

  1. Only use the default render hooks when len(languages) > 1 and
  2. add a way to disable
[markup]
  [markup.goldmark]
        duplicateResourceFiles = false
        disableDefaultRenderHooks = false

@jmooring
Copy link
Member

jmooring commented Jan 20, 2024

Works for me.

At some point, though, default render hooks for every site would be good. But that's for another day...

@jmooring
Copy link
Member

jmooring commented Jan 20, 2024

What about something like this instead:

[markup.goldmark.renderhooks.image]
enable = true
multilingualOnly = true

[markup.goldmark.renderhooks.link]
enable = true
multilingualOnly = true

Benefits:

  • Granular control (obviously)

  • Users can enable as needed/desired for any site

  • Sets us up for future capabilities, for example:

    [markup.goldmark.renderhooks.image]
    enable = true
    multilingualOnly = true
    errorLevel = 'warning' # ignore (default), warning, or error (fails the build)
    
    [markup.goldmark.renderhooks.link]
    enable = true
    multilingualOnly = true
    errorLevel = 'warning' # ignore (default), warning, or error (fails the build)
    

@bep
Copy link
Member Author

bep commented Jan 21, 2024

Yea, that makes sense.

bep added a commit that referenced this issue Jan 22, 2024
…guages

Which will be default behaviour for all other than Goldmark, for which there is a config option:

```
[markup.goldmark]
duplicateResourceFiles = true
```

Fixes #11904
bep added a commit that referenced this issue Jan 22, 2024
…guages

Which will be default behaviour for all other than Goldmark, for which there is a config option:

```
[markup.goldmark]
duplicateResourceFiles = true
```

Fixes #11904
bep added a commit that referenced this issue Jan 26, 2024
…d some general spring cleaning

There are some breaking changes in this commit, see #11455.

Closes #11455
Closes #11549

Fixes #10169
Fixes #10364
Fixes #10482
Fixes #10630
Fixes #10656
Fixes #10694
Fixes #10918
Fixes #11262
Fixes #11439
Fixes #11453
Fixes #11457
Fixes #11466
Fixes #11540
Fixes #11551
Fixes #11556
Fixes #11654
Fixes #11661
Fixes #11663
Fixes #11664
Fixes #11669
Fixes #11671
Fixes #11807
Fixes #11808
Fixes #11809
Fixes #11815
Fixes #11840
Fixes #11853
Fixes #11860
Fixes #11883
Fixes #11904
Fixes #7388
Fixes #7425
Fixes #7436
Fixes #7544
Fixes #7882
Fixes #7960
Fixes #8255
Fixes #8307
Fixes #8863
Fixes #8927
Fixes #9192
Fixes #9324
bep added a commit that referenced this issue Jan 27, 2024
…d some general spring cleaning

There are some breaking changes in this commit, see #11455.

Closes #11455
Closes #11549

Fixes #10169
Fixes #10364
Fixes #10482
Fixes #10630
Fixes #10656
Fixes #10694
Fixes #10918
Fixes #11262
Fixes #11439
Fixes #11453
Fixes #11457
Fixes #11466
Fixes #11540
Fixes #11551
Fixes #11556
Fixes #11654
Fixes #11661
Fixes #11663
Fixes #11664
Fixes #11669
Fixes #11671
Fixes #11807
Fixes #11808
Fixes #11809
Fixes #11815
Fixes #11840
Fixes #11853
Fixes #11860
Fixes #11883
Fixes #11904
Fixes #7388
Fixes #7425
Fixes #7436
Fixes #7544
Fixes #7882
Fixes #7960
Fixes #8255
Fixes #8307
Fixes #8863
Fixes #8927
Fixes #9192
Fixes #9324
bep added a commit that referenced this issue Jan 27, 2024
…d some general spring cleaning

There are some breaking changes in this commit, see #11455.

Closes #11455
Closes #11549

This fixes a set of bugs (see issue list) and it is also paying some technical debt accumulated over the years. We now build with Staticcheck enabled in the CI build.

The performance should be about the same as before for regular sized Hugo sites, but it should perform and scale much better to larger data sets, as objects that uses lots of memory (e.g. rendered Markdown, big JSON files read into maps with transform.Unmarshal etc.) will now get automatically garbage collected if needed. Performance on partial rebuilds when running the server in fast render mode should be the same, but the change detection should be much more accurate.

A list of the notable new features:

* A new dependency tracker that covers (almost) all of Hugo's API and is used to do fine grained partial rebuilds when running the server.
* A new and simpler tree document store which allows fast lookups and prefix-walking in all dimensions (e.g. language) concurrently.
* You can now configure an upper memory limit allowing for much larger data sets and/or running on lower specced PCs.
We have lifted the "no resources in sub folders" restriction for branch bundles (e.g. sections).
Memory Limit
* Hugos will, by default, set aside a quarter of the total system memory, but you can set this via the OS environment variable HUGO_MEMORYLIMIT (in gigabytes). This is backed by a partitioned LRU cache used throughout Hugo. A cache that gets dynamically resized in low memory situations, allowing Go's Garbage Collector to free the memory.

New Dependency Tracker: Hugo has had a rule based coarse grained approach to server rebuilds that has worked mostly pretty well, but there have been some surprises (e.g. stale content). This is now revamped with a new dependency tracker that can quickly calculate the delta given a changed resource (e.g. a content file, template, JS file etc.). This handles transitive relations, e.g. $page -> js.Build -> JS import, or $page1.Content -> render hook -> site.GetPage -> $page2.Title, or $page1.Content -> shortcode -> partial -> site.RegularPages -> $page2.Content -> shortcode ..., and should also handle changes to aggregated values (e.g. site.Lastmod) effectively.

This covers all of Hugo's API with 2 known exceptions (a list that may not be fully exhaustive):

Changes to files loaded with template func os.ReadFile may not be handled correctly. We recommend loading resources with resources.Get
Changes to Hugo objects (e.g. Page) passed in the template context to lang.Translate may not be detected correctly. We recommend having simple i18n templates without too much data context passed in other than simple types such as strings and numbers.
Note that the cachebuster configuration (when A changes then rebuild B) works well with the above, but we recommend that you revise that configuration, as it in most situations should not be needed. One example where it is still needed is with TailwindCSS and using changes to hugo_stats.json to trigger new CSS rebuilds.

Document Store: Previously, a little simplified, we split the document store (where we store pages and resources) in a tree per language. This worked pretty well, but the structure made some operations harder than they needed to be. We have now restructured it into one Radix tree for all languages. Internally the language is considered to be a dimension of that tree, and the tree can be viewed in all dimensions concurrently. This makes some operations re. language simpler (e.g. finding translations is just a slice range), but the idea is that it should also be relatively inexpensive to add more dimensions if needed (e.g. role).

Fixes #10169
Fixes #10364
Fixes #10482
Fixes #10630
Fixes #10656
Fixes #10694
Fixes #10918
Fixes #11262
Fixes #11439
Fixes #11453
Fixes #11457
Fixes #11466
Fixes #11540
Fixes #11551
Fixes #11556
Fixes #11654
Fixes #11661
Fixes #11663
Fixes #11664
Fixes #11669
Fixes #11671
Fixes #11807
Fixes #11808
Fixes #11809
Fixes #11815
Fixes #11840
Fixes #11853
Fixes #11860
Fixes #11883
Fixes #11904
Fixes #7388
Fixes #7425
Fixes #7436
Fixes #7544
Fixes #7882
Fixes #7960
Fixes #8255
Fixes #8307
Fixes #8863
Fixes #8927
Fixes #9192
Fixes #9324
bep added a commit that referenced this issue Jan 27, 2024
…d some general spring cleaning

There are some breaking changes in this commit, see #11455.

Closes #11455
Closes #11549

This fixes a set of bugs (see issue list) and it is also paying some technical debt accumulated over the years. We now build with Staticcheck enabled in the CI build.

The performance should be about the same as before for regular sized Hugo sites, but it should perform and scale much better to larger data sets, as objects that uses lots of memory (e.g. rendered Markdown, big JSON files read into maps with transform.Unmarshal etc.) will now get automatically garbage collected if needed. Performance on partial rebuilds when running the server in fast render mode should be the same, but the change detection should be much more accurate.

A list of the notable new features:

* A new dependency tracker that covers (almost) all of Hugo's API and is used to do fine grained partial rebuilds when running the server.
* A new and simpler tree document store which allows fast lookups and prefix-walking in all dimensions (e.g. language) concurrently.
* You can now configure an upper memory limit allowing for much larger data sets and/or running on lower specced PCs.
We have lifted the "no resources in sub folders" restriction for branch bundles (e.g. sections).
Memory Limit
* Hugos will, by default, set aside a quarter of the total system memory, but you can set this via the OS environment variable HUGO_MEMORYLIMIT (in gigabytes). This is backed by a partitioned LRU cache used throughout Hugo. A cache that gets dynamically resized in low memory situations, allowing Go's Garbage Collector to free the memory.

New Dependency Tracker: Hugo has had a rule based coarse grained approach to server rebuilds that has worked mostly pretty well, but there have been some surprises (e.g. stale content). This is now revamped with a new dependency tracker that can quickly calculate the delta given a changed resource (e.g. a content file, template, JS file etc.). This handles transitive relations, e.g. $page -> js.Build -> JS import, or $page1.Content -> render hook -> site.GetPage -> $page2.Title, or $page1.Content -> shortcode -> partial -> site.RegularPages -> $page2.Content -> shortcode ..., and should also handle changes to aggregated values (e.g. site.Lastmod) effectively.

This covers all of Hugo's API with 2 known exceptions (a list that may not be fully exhaustive):

Changes to files loaded with template func os.ReadFile may not be handled correctly. We recommend loading resources with resources.Get
Changes to Hugo objects (e.g. Page) passed in the template context to lang.Translate may not be detected correctly. We recommend having simple i18n templates without too much data context passed in other than simple types such as strings and numbers.
Note that the cachebuster configuration (when A changes then rebuild B) works well with the above, but we recommend that you revise that configuration, as it in most situations should not be needed. One example where it is still needed is with TailwindCSS and using changes to hugo_stats.json to trigger new CSS rebuilds.

Document Store: Previously, a little simplified, we split the document store (where we store pages and resources) in a tree per language. This worked pretty well, but the structure made some operations harder than they needed to be. We have now restructured it into one Radix tree for all languages. Internally the language is considered to be a dimension of that tree, and the tree can be viewed in all dimensions concurrently. This makes some operations re. language simpler (e.g. finding translations is just a slice range), but the idea is that it should also be relatively inexpensive to add more dimensions if needed (e.g. role).

Fixes #10169
Fixes #10364
Fixes #10482
Fixes #10630
Fixes #10656
Fixes #10694
Fixes #10918
Fixes #11262
Fixes #11439
Fixes #11453
Fixes #11457
Fixes #11466
Fixes #11540
Fixes #11551
Fixes #11556
Fixes #11654
Fixes #11661
Fixes #11663
Fixes #11664
Fixes #11669
Fixes #11671
Fixes #11807
Fixes #11808
Fixes #11809
Fixes #11815
Fixes #11840
Fixes #11853
Fixes #11860
Fixes #11883
Fixes #11904
Fixes #7388
Fixes #7425
Fixes #7436
Fixes #7544
Fixes #7882
Fixes #7960
Fixes #8255
Fixes #8307
Fixes #8863
Fixes #8927
Fixes #9192
Fixes #9324
@bep bep modified the milestones: v0.122.0, v0.123.0 Jan 27, 2024
@bep bep closed this as completed in e39c0f4 Jan 28, 2024
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 19, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants