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

feat(gatsby): Add support for relative links #24054

Merged
merged 18 commits into from
Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
138 changes: 83 additions & 55 deletions packages/gatsby-link/src/index.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import PropTypes from "prop-types"
import React from "react"
import { Link } from "@reach/router"
import { Link, Location } from "@reach/router"
import { resolve } from "@reach/router/lib/utils"

import { parsePath } from "./parse-path"

export { parsePath }

export function withPrefix(path) {
return normalizePath(
[
typeof __BASE_PATH__ !== `undefined` ? __BASE_PATH__ : __PATH_PREFIX__,
path,
].join(`/`)
function isAbsolutePath(path) {
return !(
path.startsWith(`./`) ||
path.startsWith(`../`) ||
path === `..` ||
ascorbic marked this conversation as resolved.
Show resolved Hide resolved
path === `.`
)
}

export function withAssetPrefix(path) {
return [__PATH_PREFIX__].concat([path.replace(/^\//, ``)]).join(`/`)
export function withPrefix(path) {
if (isAbsolutePath(path)) {
return normalizePath(`${__BASE_PATH__ ?? __PATH_PREFIX__}/${path}`)
}
ascorbic marked this conversation as resolved.
Show resolved Hide resolved
return normalizePath(path)
}

export function withAssetPrefix(path) {
return isAbsolutePath(path)
? `${__PATH_PREFIX__}/${path.startsWith(`/`) ? path.substring(1) : path}`
: path
}
function normalizePath(path) {
return path.replace(/\/+/g, `/`)
}

function absolutify(path, current) {
// If it's already absolute, return as-is
if (isAbsolutePath(path)) {
return path
}
return resolve(path, current)
}

const NavLinkPropTypes = {
activeClassName: PropTypes.string,
activeStyle: PropTypes.object,
Expand Down Expand Up @@ -131,59 +148,70 @@ class GatsbyLink extends React.Component {
/* eslint-enable no-unused-vars */
...rest
} = this.props

const LOCAL_URL = /^\/(?!\/)/
if (process.env.NODE_ENV !== `production` && !LOCAL_URL.test(to)) {
const isAbsolute = isAbsolutePath(to)
const isLocal = to.startsWith(`/`) && !to.startsWith(`//`)
if (process.env.NODE_ENV !== `production` && !isLocal && isAbsolute) {
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`
)
}

const prefixedTo = withPrefix(to)

return (
<Link
to={prefixedTo}
state={state}
getProps={getProps}
innerRef={this.handleRef}
onMouseEnter={e => {
if (onMouseEnter) {
onMouseEnter(e)
}
___loader.hovering(parsePath(to).pathname)
}}
onClick={e => {
if (onClick) {
onClick(e)
}

if (
e.button === 0 && // ignore right clicks
!this.props.target && // let browser handle "target=_blank"
!e.defaultPrevented && // onClick prevented default
!e.metaKey && // ignore clicks with modifier keys...
!e.altKey &&
!e.ctrlKey &&
!e.shiftKey
) {
e.preventDefault()

let shouldReplace = replace
const isCurrent = encodeURI(to) === window.location.pathname
if (typeof replace !== `boolean` && isCurrent) {
shouldReplace = true
}

// Make sure the necessary scripts and data are
// loaded before continuing.
navigate(to, { state, replace: shouldReplace })
}

return true
<Location>
{({ location }) => {
const prefixedTo =
isAbsolute && isLocal
? withPrefix(to)
: absolutify(to, location.pathname)
console.log({ to, prefixedTo, isAbsolute, isLocal })
ascorbic marked this conversation as resolved.
Show resolved Hide resolved
return (
<Link
to={prefixedTo}
state={state}
getProps={getProps}
innerRef={this.handleRef}
onMouseEnter={e => {
if (onMouseEnter) {
onMouseEnter(e)
}
___loader.hovering(parsePath(prefixedTo).pathname)
}}
onClick={e => {
if (onClick) {
onClick(e)
}

if (
e.button === 0 && // ignore right clicks
!this.props.target && // let browser handle "target=_blank"
!e.defaultPrevented && // onClick prevented default
!e.metaKey && // ignore clicks with modifier keys...
!e.altKey &&
!e.ctrlKey &&
!e.shiftKey
) {
e.preventDefault()

let shouldReplace = replace
const isCurrent = encodeURI(to) === window.location.pathname
if (typeof replace !== `boolean` && isCurrent) {
shouldReplace = true
}
// Make sure the necessary scripts and data are
// loaded before continuing.
window.___navigate(prefixedTo, {
state,
replace: shouldReplace,
})
}

return true
}}
{...rest}
/>
)
}}
{...rest}
/>
</Location>
)
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/gatsby/cache-dir/__tests__/strip-prefix.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ describe(`strip-prefix`, () => {
it(`returns input str if no prefix is provided`, () => {
expect(stripPrefix(`/bar`)).toBe(`/bar`)
})

it(`returns "/" if str equals prefix`, () => {
expect(stripPrefix(`/bar`, `/bar`)).toBe(`/bar`)
})
})
18 changes: 15 additions & 3 deletions packages/gatsby/cache-dir/find-path.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ const trimPathname = rawPathname => {
return trimmedPathname
}

function absolutify(path) {
// If it's already absolute, return as-is
if (!path.startsWith(`./`) && !path.startsWith(`../`) && path !== `..`) {
ascorbic marked this conversation as resolved.
Show resolved Hide resolved
return path
}
// Calculate path relative to current location, adding a trailing slash to
// match behavior of @reach/router
return new URL(
path,
window.location.href + (window.location.href.endsWith(`/`) ? `` : `/`)
).pathname
}

/**
* Set list of matchPaths
*
Expand Down Expand Up @@ -55,8 +68,7 @@ export const findMatchPath = rawPathname => {
// Or if `match-paths.json` contains `{ "/foo*": "/page1", ...}`, then
// `/foo?bar=far` => `/page1`
export const findPath = rawPathname => {
const trimmedPathname = trimPathname(rawPathname)

const trimmedPathname = trimPathname(absolutify(rawPathname))
if (pathCache.has(trimmedPathname)) {
return pathCache.get(trimmedPathname)
}
Expand All @@ -80,7 +92,7 @@ export const findPath = rawPathname => {
* @return {string}
*/
export const cleanPath = rawPathname => {
const trimmedPathname = trimPathname(rawPathname)
const trimmedPathname = trimPathname(absolutify(rawPathname))

let foundPath = trimmedPathname
if (foundPath === `/index.html`) {
Expand Down
10 changes: 6 additions & 4 deletions packages/gatsby/cache-dir/strip-prefix.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
* isn't found.
*/

export default (str, prefix = ``) => {
export default function stripPrefix(str, prefix = ``) {
if (!prefix) {
return str
}

prefix += `/`
if (str === prefix) {
return `/`
}

if (str.substr(0, prefix.length) === prefix) {
return str.slice(prefix.length - 1)
if (str.startsWith(`${prefix}/`)) {
return str.slice(prefix.length)
}

return str
Expand Down