-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
feat(gatsby): Add support for relative links #24054
Conversation
} | ||
// Calculate path relative to current location, adding a trailing slash to | ||
// match behavior of @reach/router | ||
return new URL(path, window.location.href.replace(/([^/])$/, `$1/`)).pathname |
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.
URL constructor is not supported in IE11. https://caniuse.com/#search=url, does polyfiling take care of that for us?
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.
Yeah, I wondered about that, but our docs says that we have babel auto-polyfills so I guessed it was ok
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.
This is a really awesome start! I think the direction is looking great!
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.
The hunt for regexes is on.
packages/gatsby-link/src/index.js
Outdated
return [__PATH_PREFIX__].concat([path.replace(/^\//, ``)]).join(`/`) | ||
return isRelativePath(path) | ||
? path | ||
: [__PATH_PREFIX__].concat([path.replace(/^\//, ``)]).join(`/`) |
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.
Not your code, but if you're willing to drop the regex:
There's no need for the regex here. Not sure if the absolute path can safely assumed to start with /
after the isRelative
check, otherwise you can simplify that logic to only .slice(1)
there. (That func only tests the opposite.)
: [__PATH_PREFIX__].concat([path.replace(/^\//, ``)]).join(`/`) | |
: `${__PATH_PREFIX__}/${path.startsWith('/')?path.slice(1):path` |
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.
sort of but there's browser support to consider for startsWith
. It's a better API (that may not have existed or been implemented broadly at the time of this code change), but I'm not convinced it's worth making the change when it's functionally the same thing and more supported.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
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.
I'd assume we're polyfilling it already
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.
🤷 are we comfortable going forward with an assumption? What's the benefit of changing this code path?
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.
The reason I said I assumed it would be polyfilled is because the docs say we automatically include them. The benefit is performance. Regexes are really expensive. We could in theory replace it with a substring comparison instead, but that's a lot less readable, and by the time we've used it a few times will be larger than the polyfill (and unlike the polyfill it won't be removed if people target new browsers).
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.
My other suggestions use startsWith
and endsWith
as well, fwiw.
If this is really a concern then yeah, don't do it. I too thought the polyfills are in place for this.
💜 the changes, thank you :) |
Let's make sure to add some test cases for relative links in Also please make sure that prefetching ( so current export default React.forwardRef((props, ref) => (
<GatsbyLink innerRef={ref} {...props} />
)) would be something like export default React.forwardRef(({to, ...restOfProps}, ref) => (
<Location>
{({ location }) => {
const adjustedTo = magic(to, location)
return <GatsbyLink innerRef={ref} to={adjustedTo} {...restOfProps} />
})
</Location>
)) so GatsbyLink itself already get adjusted |
I have some questions about link behaviour. This table shows how it behaves right now:
* Gives warning about external links All changed behaviour was already giving a warning. There are a few slightly confusing cases, which are due to how @reach/router handles trailing slashes. Because of how ambigious they can be, Reach treats them as if they all have a trailing slash, so linking from My main question is whether we should warn for any of these now, particularly ones that are potentially ambiguous like a bare link with no inital slash or dot like |
@ascorbic Tests are great! Thank you for those. Do we need to test this with |
Canary is out at
|
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.
Looks great!
We do not have a sizebot yet 😛 |
@wardpeet You are our sizebot 😛 |
@ascorbic Pulling my Slack comment out into the open! Would you mind updating the linking between pages doc as part of this PR? Let me know if you'd like a little direction there! I just want to make sure we're correctly describing the component's behavior wherever it's mentioned. |
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.
This looks good to me. Thanks @ascorbic for documenting.
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.
💥
<Location> | ||
{({ location }) => { | ||
const prefixedTo = rewriteLinkPath(to, location.pathname) | ||
return isLocalLink(prefixedTo) ? ( |
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.
@ascorbic did this also add support for external links?
I'm asking because the docs have long warned that should only be used for local links.
I think it's a nice feature, until now I've had to implement a similar workaround in a wrapper around to render either a Gatsby Link for local links or an anchor for others.
However this PR misses two things if this really is adding support for external links:
- we need to update the docs for it
- we need to remove this warning:
gatsby/packages/gatsby-link/src/index.js
Lines 169 to 173 in be86318
if (process.env.NODE_ENV !== `production` && !isLocalLink(to)) { | |
console.warn( | |
`External link ${to} was detected in a Link component. Use the Link component only for internal links. See: https://gatsby.dev/internal-links` | |
) | |
} |
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.
@robinmetral This change handles them more gracefully, but we still discourage their use, which is why we still have the warning. It means we can change the behaviour in future without making it a breaking change.
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.
Thanks @ascorbic, got it. So as far as I understood, I can basically use Link for external links now, but I'll continue getting warnings.
What would be missing to make this a more officially supported feature? I can help if necessary, I'd love to get rid of my custom wrapper 😛
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.
You can continue to use them, but risk having us change behaviour in the future. For example, adding relative link support broke some existing links without leading slashes, which had worked but with warnings.
Thank you heaps for this! |
Currently gatsby-link doesn't support relative paths, unlike the underlying @reach/router. This is because we need to do a lot more than navigation, particularly loading of resources. This is confusing to users, and people have long requested that we add support (e.g. #6945).
This PR adds support for relative links. It does this by resolving the links, both at runtime and build time, relative to the current route. It handles pathPrefix, but I've not yet tested it with client-only routes, which is why it's a draft. It follows the @reach/router behaviour of treating relative links as if the current page has a trailing slash. i.e linking from
/foo/bar
to..
resolves to/foo
not/
as it would with a POSIX path.