EleventyHtmlBasePlugin is clobbering fragments in href
Operating system
macOS Ventura 13.3
Eleventy
2.0.0
Describe the bug
Since adding the EleventyHtmlBasePlugin, I noticed that anchor references within a page are now broken. For example, consider https://www.aaron-gustafson.com/notebook/a-tab-interface-before-its-time/
In the sidebar for this page, under Reactions, there is a link to the webmentions at the bottom of the page. The template makes that href "#webmentions", but after activating the plugin, it’s now the current URL with no fragment identifier. It seems like the plugin should ignore in-page fragment identifiers by default, but if that’s not possible, it at least needs to maintain them as part of the link.
To be clear, I’m not sure if this is a core issue with posthtml-urls or just config issue in Eleventy.
Reproduction steps
- View the source of my post template, where the URL is "#webmentions"
- Note that the built site no longer includes that fragment: https://www.aaron-gustafson.com/notebook/a-tab-interface-before-its-time/
Expected behavior
Fragment identifiers should be left intact.
Reproduction URL
https://github.com/aarongustafson/aaron-gustafson.com/
Screenshots
No response
I think the best approach would be to adjust the plugin by adding a check to the early exit conditional:
if (isValidUrl(url) || (url.startsWith("#") && url !== "#")) {
return url;
}
I tested locally and it resolves the issue, but if you’d rather have it resolve to the full URL with the fragment, I could figure that out.
It’s also worth noting that this is wreaking havoc with heading anchors from Markdown.
I wasn’t able to reproduce this with a simple test case and I tried to get your site running but it looks like it needs some environment variables—help!
Specifically, this test worked fine:
<a href="#webmentions">Testing</a>
<a href="/hi/#webmentions">Testing</a>
Generated as (without --pathprefix):
<a href="#webmentions">Testing</a>
<a href="/hi/#webmentions">Testing</a>
Generated as (with --pathprefix=/test/):
<a href="#webmentions">Testing</a>
<a href="/test/hi/#webmentions">Testing</a>
I can do a screenshare with you if you like. Hit me up on Mastodon if you want to set that up.
If I recall, the issue did not show up when I ran the local dev server, but did show up when I did the production build, I have not had a chance to dig into it more yet.
Ah, this might be a third party plugin thing—you may want to start by disabling the plugins that use transforms to see if you can isolate?
When I disabled it in the Eleventy config, the issue went away, but I can take another look.
I found the same issue: In a layout template, I have
<!-- omitted for brevity -->
<a href="#main-content">Skip to main content</a>
<!-- omitted for brevity -->
<main id="main-content">{{ content | safe }}</main>
<!-- omitted for brevity -->
And when EleventyHtmlBasePlugin is enabled with options including { baseHref: 'path', }, then #main-content is replaced with href="[path-without-#-anchor]". To retain this plugin's use while preserving #main-content, I removed the baseHref option. However, I think # anchors should be treated as a special use case, as suggested by @aarongustafson.
You can work around this unexpected behavior if you prefix your anchor links with current page URL:
<a href="{{ page.url }}#main-content">Skip to main content</a>
<main id="main-content">{{ content | safe }}</main>
The HTML base plugin will identify such links as not to be transformed and not mess with the fragment. From a browser point of view, having the full page URL before your anchor won't trigger a page reload.
I also think the plugin should handle this out of the box.
Edit: Oh! The fix is already in https://github.com/11ty/eleventy/commit/db06e76a3b21f246e4d7b55aa4a9244c5230d994 (as of April 2024), so it's fixed in Eleventy 3.0.0-beta.
To make the plugin handle this out of the box, I think you can change @11ty/eleventy/src/Plugins/HtmlBasePlugins transformUrl to be:
// pathprefix is only used when overrideBase is a full URL
function transformUrl(url, base, opts = {}) {
let { pathPrefix, pageUrl } = opts;
// full URL, return as-is
if (isValidUrl(url)) {
return url;
}
// Not a full URL, but with a full base URL
// e.g. relative urls like "subdir/", "../subdir", "./subdir"
if (isValidUrl(base)) {
// convert relative paths to absolute path first using pageUrl
if (pageUrl && !url.startsWith("/")) {
url = new URL(url, `http://example.com${pageUrl}`);
url = url.pathname + (url.hash || '');
}
return addPathPrefixToUrl(url, pathPrefix, base);
}
// Not a full URL, nor a full base URL (call the built-in `url` filter)
return urlFilter.call(this, url, base);
}
This adds url.hash back.
Fixed by https://github.com/11ty/eleventy/pull/3181 first shipped with 3.0.0-alpha.6 Not sure why this one didn’t get closed but thank you!