11ty / eleventy

A simpler site generator. Transforms a directory of templates (of varying types) into HTML.
https://www.11ty.dev/
MIT License
17.31k stars 495 forks source link

EleventyHtmlBasePlugin is clobbering fragments in href #2898

Open aarongustafson opened 1 year ago

aarongustafson commented 1 year ago

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

  1. View the source of my post template, where the URL is "#webmentions"
  2. 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

aarongustafson commented 1 year ago

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.

aarongustafson commented 1 year ago

It’s also worth noting that this is wreaking havoc with heading anchors from Markdown.

zachleat commented 1 year ago

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>
aarongustafson commented 1 year ago

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.

zachleat commented 1 year ago

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?

aarongustafson commented 1 year ago

When I disabled it in the Eleventy config, the issue went away, but I can take another look.

julrichkieffer commented 1 year ago

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.

gael-ian commented 4 months ago

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.

sachac commented 2 months ago

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.