diff --git a/docs/docs/building-apps-with-gatsby.md b/docs/docs/building-apps-with-gatsby.md index 9baf20246fc76..6c61403f93837 100644 --- a/docs/docs/building-apps-with-gatsby.md +++ b/docs/docs/building-apps-with-gatsby.md @@ -41,7 +41,7 @@ exports.onCreatePage = async ({ page, actions }) => { // page.matchPath is a special key that's used for matching pages // only on the client. if (page.path.match(/^\/app/)) { - page.matchPath = "/app/:path" + page.matchPath = "/app/*" // Update the page. createPage(page) diff --git a/docs/docs/gatsby-link.md b/docs/docs/gatsby-link.md new file mode 100644 index 0000000000000..5003ba14c3602 --- /dev/null +++ b/docs/docs/gatsby-link.md @@ -0,0 +1,131 @@ +--- +title: Gatsby Link +--- + +A `` component for Gatsby. + +It's a wrapper around +[@reach/router's Link component](https://reach.tech/router/api/Link) +that adds enhancements specific to Gatsby. All props are passed through to @reach/router's `Link` component. + +You can set the `activeStyle` or `activeClassName` prop to add styling +attributes to the rendered element when it matches the current URL. + +Gatsby does per-route code splitting. This means that when navigating to a new +page, the code chunks necessary for that page might not be loaded. This is bad as +any unnecessary latency when changing pages should be avoided. So to avoid that, +Gatsby preloads code chunks and page data. + +Preloading is triggered by a link entering the viewport; Gatsby uses +`Link`'s `innerRef` property to create a new InteractionObserver (on +supported browsers) to monitor visible links. This way, Gatsby only prefetches +code/data chunks for pages the user is likely to navigate to. You can also get +access to the link element by passing in a `innerRef` prop. + +## How to use + +In JavaScript: + +```jsx +import React from "react" +import { Link } from "gatsby" + +class Page extends React.Component { + render() { + return ( +
+ { + this.myLink = el + }} + > + Another page + +
+ ) + } +} +``` + +## Programmatic navigation + +For cases when you can only use event handlers for navigation, you can use `push` or `replace`. `push` is a wrapper for `history.push` and `replace` wraps `history.replace`. + +```jsx +import { push } from "gatsby" + +render () { +
push('/example')}> +

Example

+
+} +``` + +Note that `push` was previously named `navigateTo`. `navigateTo` is deprecated in Gatsby v2. + +## Prefixed paths helper + +It is common to host sites in a sub-directory of a site. Gatsby let's you [set +the path prefix for your site](/docs/path-prefix/). After doing so, Gatsby's `` component will automatically handle constructing the correct URL in development and production. + +For pathnames you construct manually, there's a helper function, `withPrefix` that prepends your path prefix in production (but doesn't during development where paths don't need prefixed). + +```jsx +import { withPrefix } from "gatsby" + +const IndexLayout = ({ children, location }) => { + const isHomepage = location.pathname === withPrefix("/") + + return ( +
+

Welcome {isHomepage ? "home" : "aboard"}!

+ {children} +
+ ) +} +``` + +## Use `` only for internal links! + +This component is intended _only_ for links to pages handled by Gatsby. For links to pages on other domains or pages on the same domain not handled by the current Gatsby site, use the normal `` element. + +Sometimes you won't know ahead of time whether a link will be internal or not, +such as when the data is coming from a CMS. +In these cases you may find it useful to make a component which inspects the +link and renders either with Gatsby's `` or with a regular `` tag +accordingly. + +Since deciding whether a link is internal or not depends on the site in +question, you may need to customize the heuristic to your environment, but the +following may be a good starting point: + +```jsx +import { Link as GatsbyLink } from "gatsby" + +const Link = ({ children, to, ...other }) => { + // Tailor the following test to your environment. + // This example assumes that any internal link (intended for Gatsby) + // will start with exactly one slash, and that anything else is external. + const internal = /^\/(?!\/)/.test(to) + + // Use Gatsby Link for internal links, and for others + if (internal) { + return ( + + {children} + + ) + } + return ( + + {children} + + ) +} + +export default Link +``` diff --git a/docs/docs/migrating-from-v1-to-v2.md b/docs/docs/migrating-from-v1-to-v2.md index 0e6adbfac2295..5fcf6c26b2439 100644 --- a/docs/docs/migrating-from-v1-to-v2.md +++ b/docs/docs/migrating-from-v1-to-v2.md @@ -25,6 +25,9 @@ This is a reference for upgrading your site from Gatsby v1 to Gatsby v2. While t - [Convert to either pure CommonJS or pure ES6](#convert-to-either-pure-commonjs-or-pure-es6) - [Move Babel configuration](#move-babel-configuration) - [Restore v1 PostCSS plugin setup](#restore-v1-post-css-setup) + - [Migrate from React Router` to @reach/router](#migrate-from-react-router-to-reachrouter) + - [APIs onPreRouteUpdate and onRouteUpdate no longer called with the route update action](#apis-onprerouteupdate-and-onrouteupdate-no-longer-called-with-the-route-update-action) + - [Browser API `relaceRouterComponent` was removed](#browser-api-relaceroutercomponent-was-removed) - [Don't query nodes by ID](#dont-query-nodes-by-id) - [Typography.js Plugin Config](#typographyjs-plugin-config-changes) @@ -349,6 +352,197 @@ module.exports = () => ({ }) ``` +### Migrate from React Router to @reach/router + +We switched our router from [React Router v4](https://reacttraining.com/react-router/) to [@reach/router](https://reach.tech/router) as @reach/router is smaller and most importantly, has 1st class support +for accessibility. + +@reach/router is written by [Ryan Florence](https://twitter.com/ryanflorence), who was also the founder of React Router. He says @reach/router restores +things he misses from React Router v3 while retaining the best parts of React Router v4 _and_ adds full accessibility support. + +For _most_ sites, this change won't cause any breaking changes as the two routers are quite similar. + +Two common ways this change _might_ break your site is: + +- You use the object form of the `to` prop in the `` component +- You have client side routes + +Read more about the features of our new router at https://reach.tech/router + +**NOTE:** One prominant feature of @reach/router, relative routes, isn't working currently in Gatsby. We're working with Ryan Florence +on fixing that so hopefully it'll be supported soon. + +Read on for instructions on migrating your site to @reach/router. + +#### Only string `to` allowed + +React Router allowed you to pass objects to the `to` prop e.g. + +```jsx + + Our people + +``` + +React Router would then simply concatenate the object values together into the full pathname e.g. `/about/?fun=true&pizza=false#people`. + +Now you'll need to concatenate together the full pathname yourself. + +```diff +- Our people ++ Our people +``` + +#### Pass state to the `state` prop + +Previously with React Router to pass state to a link, you would pass it as part of a `to` object prop. + +Now, to add state to a link, pass it via a `state` prop. + +```jsx +const NewsFeed = () => ( +
+ +
+) + +const Photo = ({ location, photoId }) => { + if (location.state.fromFeed) { + return + } else { + return + } +} +``` + +#### A `history` prop is no longer passed to page components + +React Router would pass a `history` prop to components that you could use to navigate. + +If you need to do programmatic navigation, import instead the @reach/router's `navigate` function. + +```javascript +import { navigate } from "@reach/router" +``` + +#### The following props are no longer available on `` + +- `exact` +- `strict` +- `location` + +`exact` and `strict` are no longer necessary as @reach/router does matching +this way by default. + +You could pass `location` previously to manually compute whether the +link is active or not. For advanced link stylings, use `getProps` now. + +#### Use `getProps` for advanced link styling + +Gatsby's `` component supports out-of-the-box `activeClassName` and `activeStyle`. + +If you have more advanced styling needs, [use the `getProps` prop](https://reach.tech/router/api/Link). + +#### Change client paths to use a splat + +When creating a client route in `gatsby-node.js`, use a `*` to select all child routes instead of `:path`. + +```diff +exports.onCreatePage = async ({ page, actions }) => { + const { createPage } = actions + + // page.matchPath is a special key that's used for matching pages + // only on the client. + if (page.path.match(/^\/app/)) { +- page.matchPath = "/app/:path" ++ page.matchPath = "/app/*" + + // Update the page. + createPage(page) + } +} +``` + +#### Migrating React Router client routes to @reach/router + +- Use `` instead of `withRouter` +- import `{ navigate }` from `@reach/router` for programmatic navigation instead of the history object +- There's no `Route` component any more. You add a `` component (a site can have as many routers as it wishes) and then the immediate children of `` must have a prop named `path`. + +A basic example of the `` component: + +```jsx +import React from "react" +import { Router } from "@reach/router" + +export default () => ( + +
I am the home!
+
Here's a bit about me
+
Buy my t-shirts!
+
+) +``` + +Here's a more complex example of migrating a `` component (used +in store.gatsbyjs.org) from React Router to @reach/router. + +```diff + import React from 'react'; +-import { Redirect, Route } from 'react-router-dom'; ++import { Router, navigate } from '@reach/router'; + import { isAuthenticated } from '../../utils/auth'; + +-export default ({ component: Component, ...rest }) => ( +- +- !isAuthenticated() ? ( +- // If we’re not logged in, redirect to the home page. +- +- ) : ( +- +- ) +- } +- /> +-); ++export default ({ component: Component, ...rest }) => { ++ if (!isAuthenticated() && window.location.pathname !== `/login`) { ++ // If we’re not logged in, redirect to the home page. ++ navigate(`/app/login`); ++ return null; ++ } ++ ++ return ( ++ ++ ++ ++ ); ++}; +``` + +Here's links to diffs for three sites with client routes that were upgraded to @reach/router + +- [store.gatsbyjs.org](https://github.com/gatsbyjs/store.gatsbyjs.org/pull/111) +- [client-only-routes](https://github.com/gatsbyjs/gatsby/pull/6918/files#diff-69757e54875e28ef83eb8efe45a33fdf) +- [simple-auth](https://github.com/gatsbyjs/gatsby/pull/6918/files#diff-53ac112a4b2ec760b26a86c953df2339) + +### APIs `onPreRouteUpdate` and `onRouteUpdate` no longer called with the route update action + +React Router v4 would tell us the "action" (push/replace) that triggered the route +transition. We passed this as one of the arguments along with `location` to plugins. @reach/router doesn't support this so we've removed it from the API calls. + +### Browser API `relaceRouterComponent` was removed + +React Router allowed you to swap out its history object. To enable this in Gatsby, an API, `replaceRouterComponent` was added so that you could use a custom version of history or React Router. As @reach/router doesn't support this, we've removed this API. + +We did, erroneously, suggest using this API for adding support for Redux, etc. where you need to wrap the root Gatsby component with your own component. + +If you were using `replaceRouterComponent` for this, you'll need to migrate to +`wrapRootComponent`. See this PR migrating the `using-redux` example site as a pattern to follow https://github.com/gatsbyjs/gatsby/pull/6986 + ### Don't query nodes by ID Source and transformer plugins now use UUIDs for IDs. If you used glob or regex to query nodes by id then you'll need to query something else. diff --git a/docs/tutorial/part-one/index.md b/docs/tutorial/part-one/index.md index 467853ba7a641..ea496199a881f 100644 --- a/docs/tutorial/part-one/index.md +++ b/docs/tutorial/part-one/index.md @@ -47,7 +47,7 @@ Open the file at `/src/pages/index.js`. The code in this file creates a componen > 💡 Gatsby uses **hot reloading** to speed up your development process. Essentially, when you’re running a Gatsby development server, the Gatsby site files are being “watched” in the background — any time you save a file, your changes will be immediately reflected in the browser. You don’t need to hard refresh the page, or restart the development server — your changes just appear. -2. Let’s make our changes a little more visible. Try replacing the code in `/src/pages/index.js` with the code below, and save again. You’ll see changes to the text; The text color will be purple, and the font size will be larger. +2. Let’s make our changes a little more visible. Try replacing the code in `/src/pages/index.js` with the code below, and save again. You’ll see changes to the text; The text color will be purple, and the font size will be larger. ```jsx import React from "react" diff --git a/examples/client-only-paths/gatsby-node.js b/examples/client-only-paths/gatsby-node.js index 14084ba1ac422..149ebf1051be6 100644 --- a/examples/client-only-paths/gatsby-node.js +++ b/examples/client-only-paths/gatsby-node.js @@ -1,17 +1,11 @@ -const Promise = require(`bluebird`) -const path = require(`path`) - // Implement the Gatsby API “onCreatePage”. This is // called after every page is created. exports.onCreatePage = ({ page, actions }) => { const { createPage } = actions - return new Promise((resolve, reject) => { - // Make the front page match everything client side. - // Normally your paths should be a bit more judicious. - if (page.path === `/`) { - page.matchPath = `/:path` - createPage(page) - } - resolve() - }) + // Make the front page match everything client side. + // Normally your paths should be a bit more judicious. + if (page.path === `/`) { + page.matchPath = `/*` + createPage(page) + } } diff --git a/examples/client-only-paths/package.json b/examples/client-only-paths/package.json index 47f254ac9d8df..a7e173f029698 100644 --- a/examples/client-only-paths/package.json +++ b/examples/client-only-paths/package.json @@ -6,19 +6,17 @@ "author": "Kyle Mathews ", "dependencies": { "gatsby": "next", - "gatsby-plugin-typography": "next", + "gatsby-react-router-scroll": "next", "lodash": "^4.16.4", "react": "^16.3.1", - "react-addons-css-transition-group": "^15.5.2", "react-dom": "^16.3.1", + "react-transition-group": "^2.4.0", "react-typography": "^0.16.13", "slash": "^1.0.0", "typography": "^0.16.6", "typography-breakpoint-constants": "^0.15.10" }, - "keywords": [ - "gatsby" - ], + "keywords": ["gatsby"], "license": "MIT", "main": "n/a", "scripts": { diff --git a/examples/client-only-paths/src/pages/index.js b/examples/client-only-paths/src/pages/index.js index 44d8a4f11d907..1805fb4cf0804 100644 --- a/examples/client-only-paths/src/pages/index.js +++ b/examples/client-only-paths/src/pages/index.js @@ -1,117 +1,48 @@ import React from "react" -import { Link } from "gatsby" -import ReactCSSTransitionGroup from "react-addons-css-transition-group" -import { Route, Redirect } from "react-router-dom" - +import { Router, Link, Location } from "@reach/router" +import { TransitionGroup, CSSTransition } from "react-transition-group" import "./main.css" -import Layout from "../components/layout" - -class AnimationExample extends React.Component { - render() { - return ( - -
- ( -
- } - /> - -
    - Red - Green - Blue - Pink -
-
- - {/* no different than other usage of - ReactCSSTransitionGroup, just make - sure to pass `location` to `Route` - so it can match the old location - as it animates out - */} - - -
-
- )} - /> -
-
- ) - } -} +const App = () => ( +
+ + + + + + +
+) -const NavLink = props => ( -
  • - -
  • +const FadeTransitionRouter = props => ( + + {({ location }) => ( + + + {/* the only difference between a router animation and + any other animation is that you have to pass the + location to the router so the old screen renders + the "old location" */} + + {props.children} + + + + )} + ) -const HSL = ({ match: { params } }) => ( +const Page = props => (
    - hsl({params.h}, {params.s}%, {params.l}%) + {props.page}
    ) -const styles = {} - -styles.fill = { - position: `absolute`, - left: 0, - right: 0, - top: 0, - bottom: 0, -} - -styles.content = { - ...styles.fill, - top: `40px`, - textAlign: `center`, -} - -styles.nav = { - padding: 0, - margin: 0, - position: `absolute`, - top: 0, - height: `40px`, - width: `100%`, - display: `flex`, -} - -styles.navItem = { - textAlign: `center`, - flex: 1, - listStyleType: `none`, - padding: `10px`, -} - -styles.hsl = { - ...styles.fill, - color: `white`, - paddingTop: `20px`, - fontSize: `30px`, -} - -export default AnimationExample +export default App diff --git a/examples/client-only-paths/src/pages/main.css b/examples/client-only-paths/src/pages/main.css index 52cd3f848256f..30085377ca780 100644 --- a/examples/client-only-paths/src/pages/main.css +++ b/examples/client-only-paths/src/pages/main.css @@ -1,10 +1,62 @@ -.fade-enter { +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, + Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; +} + +a { + color: black; + padding: 10px; + display: block; +} + +.app { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: flex; + flex-direction: column; +} + +.nav { + display: flex; + justify-content: space-around; +} + +.transition-group { + flex: 1; + position: relative; +} + +.router { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; +} + +.page { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + color: white; + text-align: center; + font-size: 100px; + font-style: italic; + font-family: Times; + padding-top: 20px; +} + +.fade-enter .page { opacity: 0; z-index: 1; } -.fade-enter.fade-enter-active { +.fade-enter.fade-enter-active .page { opacity: 1; - transition: opacity 250ms ease-in; + transition: opacity 450ms ease-in; } - diff --git a/examples/simple-auth/gatsby-node.js b/examples/simple-auth/gatsby-node.js index 14d290f4e6fce..791ad4955c5ca 100644 --- a/examples/simple-auth/gatsby-node.js +++ b/examples/simple-auth/gatsby-node.js @@ -10,7 +10,7 @@ exports.onCreatePage = async ({ page, actions }) => { // page.matchPath is a special key that's used for matching pages // only on the client. if (page.path.match(/^\/app/)) { - page.matchPath = `/app/:path` + page.matchPath = `/app/*` // Update the page. createPage(page) diff --git a/examples/simple-auth/src/components/Form/index.js b/examples/simple-auth/src/components/Form/index.js index 0070de2745381..ed7c67bc4ce96 100644 --- a/examples/simple-auth/src/components/Form/index.js +++ b/examples/simple-auth/src/components/Form/index.js @@ -1,14 +1,14 @@ import React from "react" -import { withRouter } from "react-router-dom" import styles from "./form.module.css" +import { navigate } from "@reach/router" -export default withRouter(({ handleSubmit, handleUpdate, history }) => ( +export default ({ handleSubmit, handleUpdate }) => (
    { handleSubmit(event) - history.push(`/app/profile`) + navigate(`/app/profile`) }} >

    @@ -35,4 +35,4 @@ export default withRouter(({ handleSubmit, handleUpdate, history }) => (

    -)) +) diff --git a/examples/simple-auth/src/components/Layout/index.js b/examples/simple-auth/src/components/Layout/index.js index 1c3636edaffa8..67ee926b1a4db 100644 --- a/examples/simple-auth/src/components/Layout/index.js +++ b/examples/simple-auth/src/components/Layout/index.js @@ -1,5 +1,4 @@ import React from "react" -import PropTypes from "prop-types" import Helmet from "react-helmet" import Header from "../Header" @@ -16,8 +15,4 @@ const Layout = ({ children }) => ( ) -Layout.propTypes = { - children: PropTypes.func, -} - export default Layout diff --git a/examples/simple-auth/src/components/Login.js b/examples/simple-auth/src/components/Login.js index f2609666977b0..598c2fc909932 100644 --- a/examples/simple-auth/src/components/Login.js +++ b/examples/simple-auth/src/components/Login.js @@ -1,5 +1,5 @@ import React from "react" -import { Redirect } from "react-router-dom" +import { redirectTo } from "@reach/router" import Form from "./Form" import View from "./View" import { handleLogin, isLoggedIn } from "../utils/auth" @@ -23,7 +23,7 @@ class Login extends React.Component { render() { if (isLoggedIn()) { - return + redirectTo(`/app/profile`) } return ( diff --git a/examples/simple-auth/src/components/PrivateRoute.js b/examples/simple-auth/src/components/PrivateRoute.js index 9dec9f1662b15..455be0f2bf52f 100644 --- a/examples/simple-auth/src/components/PrivateRoute.js +++ b/examples/simple-auth/src/components/PrivateRoute.js @@ -1,22 +1,22 @@ import React from "react" import PropTypes from "prop-types" -import { Redirect, Route } from "react-router-dom" +import { Router, navigate } from "@reach/router" import { isLoggedIn } from "../utils/auth" // More info at https://reacttraining.com/react-router/web/example/auth-workflow -const PrivateRoute = ({ component: Component, ...rest }) => ( - - !isLoggedIn() ? ( - // If we’re not logged in, redirect to the home page. - - ) : ( - - ) - } - /> -) +const PrivateRoute = ({ component: Component, ...rest }) => { + if (!isLoggedIn() && window.location.pathname !== `/app/login`) { + // If we’re not logged in, redirect to the home page. + navigate(`/app/login`) + return null + } + + return ( + + + + ) +} PrivateRoute.propTypes = { component: PropTypes.any.isRequired, diff --git a/examples/simple-auth/src/components/Status/index.js b/examples/simple-auth/src/components/Status/index.js index 71f68b8bb7390..6eea2a9a51d03 100644 --- a/examples/simple-auth/src/components/Status/index.js +++ b/examples/simple-auth/src/components/Status/index.js @@ -1,14 +1,15 @@ import React from "react" -import { Link, withRouter } from "react-router-dom" +import { Link, navigate } from "@reach/router" import { getCurrentUser, isLoggedIn, logout } from "../../utils/auth" import styles from "./status.module.css" -export default withRouter(({ history }) => { +export default () => { let details if (!isLoggedIn()) { details = (

    - To get the full app experience, you’ll need to{` `} + To get the full app experience, you’ll need to + {` `} log in.

    ) @@ -17,12 +18,14 @@ export default withRouter(({ history }) => { details = (

    - Logged in as {name} ({email})!{` `} + Logged in as {name} ({email} + )! + {` `} { event.preventDefault() - logout(() => history.push(`/app/login`)) + logout(() => navigate(`/app/login`)) }} > log out @@ -32,4 +35,4 @@ export default withRouter(({ history }) => { } return

    -}) +} diff --git a/examples/simple-auth/src/pages/app.js b/examples/simple-auth/src/pages/app.js index 68180d38954a4..03dd7f645028b 100644 --- a/examples/simple-auth/src/pages/app.js +++ b/examples/simple-auth/src/pages/app.js @@ -1,5 +1,5 @@ import React from "react" -import { Route } from "react-router-dom" +import { Router } from "@reach/router" import Layout from "../components/Layout" import Details from "../components/Details" import Home from "../components/Home" @@ -12,7 +12,9 @@ const App = () => ( - + + + ) diff --git a/packages/gatsby-link/index.d.ts b/packages/gatsby-link/index.d.ts index ea3f42a5168e4..229ee821ec08f 100644 --- a/packages/gatsby-link/index.d.ts +++ b/packages/gatsby-link/index.d.ts @@ -1,12 +1,4 @@ import * as React from "react"; -import { NavLinkProps } from "react-router-dom"; -import { LocationDescriptor } from "history"; - -export interface GatsbyLinkProps extends NavLinkProps { - onClick?: (event: any) => void - className?: string - style?:any; -} export const push: (to: LocationDescriptor) => void; export const replace: (to: LocationDescriptor) => void; diff --git a/packages/gatsby-link/src/__tests__/index.js b/packages/gatsby-link/src/__tests__/index.js index 7dfe945691237..972b4046f3f36 100644 --- a/packages/gatsby-link/src/__tests__/index.js +++ b/packages/gatsby-link/src/__tests__/index.js @@ -1,6 +1,10 @@ import React from "react" import ReactDOM from "react-dom" -import { MemoryRouter } from "react-router-dom" +import { + createMemorySource, + createHistory, + LocationProvider, +} from "@reach/router" const getInstance = (props, pathPrefix = ``) => { Object.assign(global.window, { @@ -10,7 +14,7 @@ const getInstance = (props, pathPrefix = ``) => { const context = { router: { history: {} } } const Link = require(`../`).default - return new Link(props, context) + return Link(props, context) } const getPush = () => { @@ -43,16 +47,6 @@ describe(``, () => { }).not.toThrow() }) - describe(`path prefixing`, () => { - it(`does not include path prefix`, () => { - const to = `/path` - const pathPrefix = `/blog` - const instance = getInstance({ to }, pathPrefix) - - expect(instance.state.to.pathname).toEqual(to) - }) - }) - describe(`the location to link to`, () => { global.window.___loader = { enqueue: jest.fn(), @@ -63,11 +57,13 @@ describe(``, () => { const node = document.createElement(`div`) const Link = require(`../`).default + let source = createMemorySource(`/`) + let history = createHistory(source) ReactDOM.render( - + link - , + , node ) @@ -75,50 +71,6 @@ describe(``, () => { expect(href).toEqual(location) }) - - it(`accepts a location "to" prop`, () => { - const location = { - pathname: `/courses`, - search: `?sort=name`, - hash: `#the-hash`, - state: { fromDashboard: true }, - } - - const node = document.createElement(`div`) - const Link = require(`../`).default - - ReactDOM.render( - - link - , - node - ) - - const href = node.querySelector(`a`).getAttribute(`href`) - - expect(href).toEqual(`/courses?sort=name#the-hash`) - }) - - it(`resolves to with no pathname using current location`, () => { - const location = { - search: `?sort=name`, - hash: `#the-hash`, - } - - const node = document.createElement(`div`) - const Link = require(`../`).default - - ReactDOM.render( - - link - , - node - ) - - const href = node.querySelector(`a`).getAttribute(`href`) - - expect(href).toEqual(`/somewhere?sort=name#the-hash`) - }) }) it(`push is called with correct args`, () => { diff --git a/packages/gatsby-link/src/index.js b/packages/gatsby-link/src/index.js index 123b5a61cafe0..0ac8482627755 100644 --- a/packages/gatsby-link/src/index.js +++ b/packages/gatsby-link/src/index.js @@ -1,9 +1,9 @@ /*global __PATH_PREFIX__ */ import PropTypes from "prop-types" import React from "react" -import { Link, NavLink } from "react-router-dom" +import { Link, Location } from "@reach/router" import { polyfill } from "react-lifecycles-compat" -import { createLocation, createPath } from "history" +import { parsePath } from "gatsby" export function withPrefix(path) { return normalizePath(`${__PATH_PREFIX__}/${path}`) @@ -16,10 +16,6 @@ function normalizePath(path) { const NavLinkPropTypes = { activeClassName: PropTypes.string, activeStyle: PropTypes.object, - exact: PropTypes.bool, - strict: PropTypes.bool, - isActive: PropTypes.func, - location: PropTypes.object, } // Set up IntersectionObserver @@ -50,36 +46,26 @@ class GatsbyLink extends React.Component { IOSupported = true } - const { location } = context.router.history - const to = createLocation(props.to, null, null, location) + const { location } = props this.state = { - path: createPath(to), - to, IOSupported, location, } this.handleRef = this.handleRef.bind(this) } - static getDerivedStateFromProps(nextProps, prevState) { - if (prevState.to === nextProps.to) return null - const to = createLocation(nextProps.to, null, null, prevState.location) - const path = createPath(to) - return { path, to } - } - componentDidUpdate(prevProps, prevState) { // Preserve non IO functionality if no support if (this.props.to !== prevProps.to && !this.state.IOSupported) { - ___loader.enqueue(this.state.path) + ___loader.enqueue(parsePath(this.props.to).pathname) } } componentDidMount() { // Preserve non IO functionality if no support if (!this.state.IOSupported) { - ___loader.enqueue(this.state.to.pathname) + ___loader.enqueue(parsePath(this.props.to).pathname) } } @@ -89,26 +75,35 @@ class GatsbyLink extends React.Component { if (this.state.IOSupported && ref) { // If IO supported and element reference found, setup Observer functionality handleIntersection(ref, () => { - ___loader.enqueue(this.state.to.pathname) + ___loader.enqueue(parsePath(this.props.to).pathname) }) } } render() { const { onClick, onMouseEnter, ...rest } = this.props - let El - if (Object.keys(NavLinkPropTypes).some(propName => this.props[propName])) { - El = NavLink + let getProps + if (this.props.getProps) { + getProps = this.props.getProps } else { - El = Link + getProps = ({ isCurrent }) => + isCurrent + ? { + className: [this.props.className, this.props.activeClassName] + .filter(i => i) + .join(` `), + style: { ...this.props.style, ...this.props.activeStyle }, + } + : null } return ( - { // eslint-disable-line onMouseEnter && onMouseEnter(e) - ___loader.hovering(this.state.path) + ___loader.hovering(parsePath(this.props.to).pathname) }} onClick={e => { // eslint-disable-line @@ -123,44 +118,30 @@ class GatsbyLink extends React.Component { !e.ctrlKey && !e.shiftKey ) { + e.preventDefault() // Is this link pointing to a hash on the same page? If so, // just scroll there. - let pathname = this.state.path - if (pathname.split(`#`).length > 1) { - pathname = pathname - .split(`#`) - .slice(0, -1) - .join(``) - } + const { pathname, hash } = parsePath(this.props.to) if (pathname === window.location.pathname) { - const hashFragment = this.state.path - .split(`#`) - .slice(1) - .join(`#`) - const element = hashFragment - ? document.getElementById(hashFragment) - : null + const element = hash ? document.getElementById(hash) : null if (element !== null) { element.scrollIntoView() - return true } else { // This is just a normal link to the current page so let's emulate default // browser behavior by scrolling now to the top of the page. window.scrollTo(0, 0) - return true } } // Make sure the necessary scripts and data are // loaded before continuing. - e.preventDefault() - window.___push(this.state.to) + window.___push(this.props.to) } return true }} {...rest} - to={this.state.to} + to={this.props.to} innerRef={this.handleRef} /> ) @@ -178,7 +159,12 @@ GatsbyLink.contextTypes = { router: PropTypes.object, } -export default polyfill(GatsbyLink) +// eslint-disable-next-line react/display-name +const withLocation = Comp => props => ( + {location => } +) + +export default withLocation(polyfill(GatsbyLink)) export const push = to => { window.___push(to) diff --git a/packages/gatsby-plugin-create-client-paths/src/gatsby-node.js b/packages/gatsby-plugin-create-client-paths/src/gatsby-node.js index fe6d90ff17f79..11bb7683ca90b 100644 --- a/packages/gatsby-plugin-create-client-paths/src/gatsby-node.js +++ b/packages/gatsby-plugin-create-client-paths/src/gatsby-node.js @@ -29,7 +29,7 @@ exports.onCreatePage = ({ page, store, actions }, { prefixes }) => { const path = page.path.match(/\/$/) ? page.path : `${page.path}/` if (path.match(re[prefix])) { - page.matchPath = prefix.replace(/\*$/, `:path`) + page.matchPath = prefix.replace(/\*$/, `*`) createPage(page) return true } diff --git a/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js b/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js index 3c431db06558a..ac9e996ca6f9c 100644 --- a/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js +++ b/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js @@ -1,7 +1,7 @@ import React from "react" -import { withRouter } from "react-router-dom" import ScrollBehavior from "scroll-behavior" import PropTypes from "prop-types" +import { globalHistory as history } from "@reach/router/lib/history" import SessionStorage from "./StateStorage" @@ -9,7 +9,6 @@ const propTypes = { shouldUpdateScroll: PropTypes.func, children: PropTypes.element.isRequired, location: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, } const childContextTypes = { @@ -20,8 +19,6 @@ class ScrollContext extends React.Component { constructor(props, context) { super(props, context) - const { history } = props - this.scrollBehavior = new ScrollBehavior({ addTransitionHook: history.listen, stateStorage: new SessionStorage(), @@ -39,7 +36,7 @@ class ScrollContext extends React.Component { } componentDidUpdate(prevProps) { - const { location, history } = this.props + const { location } = this.props const prevLocation = prevProps.location if (location === prevLocation) { @@ -47,13 +44,14 @@ class ScrollContext extends React.Component { } const prevRouterProps = { - history: prevProps.history, location: prevProps.location, } // The "scroll-behavior" package expects the "action" to be on the location // object so let's copy it over. - location.action = history.action + if (!location.action) { + location.action = `push` + } this.scrollBehavior.updateScroll(prevRouterProps, { history, location }) } @@ -62,8 +60,8 @@ class ScrollContext extends React.Component { } getRouterProps() { - const { history, location } = this.props - return { history, location } + const { location } = this.props + return { location, history } } shouldUpdateScroll = (prevRouterProps, routerProps) => { @@ -101,4 +99,4 @@ class ScrollContext extends React.Component { ScrollContext.propTypes = propTypes ScrollContext.childContextTypes = childContextTypes -export default withRouter(ScrollContext) +export default ScrollContext diff --git a/packages/gatsby/cache-dir/find-page.js b/packages/gatsby/cache-dir/find-page.js index 8697223e9f2dd..f25078f4f6b7b 100644 --- a/packages/gatsby/cache-dir/find-page.js +++ b/packages/gatsby/cache-dir/find-page.js @@ -1,5 +1,5 @@ // TODO add tests especially for handling prefixed links. -import { matchPath } from "react-router-dom" +import { match as matchPath } from "@reach/router/lib/utils" import stripPrefix from "./strip-prefix" const pageCache = {} @@ -34,40 +34,18 @@ export default (pages, pathPrefix = ``) => rawPathname => { // Array.prototype.find is not supported in IE so we use this somewhat odd // work around. pages.some(page => { - if (page.matchPath) { - // Try both the path and matchPath - if ( - matchPath(trimmedPathname, { path: page.path }) || - matchPath(trimmedPathname, { - path: page.matchPath, - }) - ) { - foundPage = page - pageCache[trimmedPathname] = page - return true - } - } else { - if ( - matchPath(trimmedPathname, { - path: page.path, - exact: true, - }) - ) { - foundPage = page - pageCache[trimmedPathname] = page - return true - } + let pathToMatch = page.matchPath ? page.matchPath : page.path + if (matchPath(pathToMatch, trimmedPathname)) { + foundPage = page + pageCache[trimmedPathname] = page + return true + } - // Finally, try and match request with default document. - if ( - matchPath(trimmedPathname, { - path: page.path + `index.html`, - }) - ) { - foundPage = page - pageCache[trimmedPathname] = page - return true - } + // Finally, try and match request with default document. + if (matchPath(`${page.path}index.html`, trimmedPathname)) { + foundPage = page + pageCache[trimmedPathname] = page + return true } return false diff --git a/packages/gatsby/cache-dir/gatsby-browser-entry.js b/packages/gatsby/cache-dir/gatsby-browser-entry.js index 30070e3c7bf10..3b3623b731380 100644 --- a/packages/gatsby/cache-dir/gatsby-browser-entry.js +++ b/packages/gatsby/cache-dir/gatsby-browser-entry.js @@ -2,6 +2,7 @@ import React from "react" import PropTypes from "prop-types" import Link, { withPrefix, push, replace, navigateTo } from "gatsby-link" import PageRenderer from "./public-page-renderer" +import parsePath from "./parse-path" const StaticQueryContext = React.createContext({}) @@ -42,6 +43,7 @@ export { Link, withPrefix, graphql, + parsePath, push, replace, navigateTo, // TODO: remove navigateTo for v3 diff --git a/packages/gatsby/cache-dir/history.js b/packages/gatsby/cache-dir/history.js deleted file mode 100644 index daf86ec8b778a..0000000000000 --- a/packages/gatsby/cache-dir/history.js +++ /dev/null @@ -1,10 +0,0 @@ -import createHistory from "history/createBrowserHistory" -import { apiRunner } from "./api-runner-browser" - -const basename = __PATH_PREFIX__ - -const pluginResponses = apiRunner(`replaceHistory`, { basename }) -const replacementHistory = pluginResponses[0] - -const history = replacementHistory || createHistory({ basename }) -export default history diff --git a/packages/gatsby/cache-dir/navigation.js b/packages/gatsby/cache-dir/navigation.js index 96fe8b400857d..763bc30e44e03 100644 --- a/packages/gatsby/cache-dir/navigation.js +++ b/packages/gatsby/cache-dir/navigation.js @@ -1,8 +1,10 @@ import loader, { setApiRunnerForLoader } from "./loader" import redirects from "./redirects.json" import { apiRunner } from "./api-runner-browser" -import { createLocation } from "history" import emitter from "./emitter" +import { globalHistory } from "@reach/router/lib/history" +import { navigate as reachNavigate } from "@reach/router" +import parsePath from "./parse-path" // Convert to a map for faster lookup in maybeRedirect() const redirectMap = redirects.reduce((map, redirect) => { @@ -33,34 +35,26 @@ function maybeRedirect(pathname) { let lastNavigateToLocationString = null -let initialAttachDone = false -function attachToHistory(history) { - if (!window.___history || initialAttachDone === false) { - window.___history = history - initialAttachDone = true - - history.listen((location, action) => { - if (!maybeRedirect(location.pathname)) { - // Check if we already ran onPreRouteUpdate API - // in navigateTo function - if ( - lastNavigateToLocationString !== - `${location.pathname}${location.search}${location.hash}` - ) { - apiRunner(`onPreRouteUpdate`, { location, action }) - } - // Make sure React has had a chance to flush to DOM first. - setTimeout(() => { - apiRunner(`onRouteUpdate`, { location, action }) - }, 0) - } - }) +globalHistory.listen(() => { + const location = globalHistory.location + if (!maybeRedirect(location.pathname)) { + // Check if we already ran onPreRouteUpdate API + // in navigateTo function + if ( + lastNavigateToLocationString !== + `${location.pathname}${location.search}${location.hash}` + ) { + apiRunner(`onPreRouteUpdate`, { location }) + } + // Make sure React has had a chance to flush to DOM first. + setTimeout(() => { + apiRunner(`onRouteUpdate`, { location }) + }, 0) } -} +}) const navigate = (to, replace) => { - const location = createLocation(to, null, null, history.location) - let { pathname } = location + let { pathname } = parsePath(to) const redirect = redirectMap[pathname] // If we're redirecting, just replace the passed in pathname @@ -74,38 +68,18 @@ const navigate = (to, replace) => { window.location = pathname } - const wl = window.location - - // If we're already at this location, do nothing. - if ( - wl.pathname === location.pathname && - wl.search === location.search && - wl.hash === location.hash - ) { - return - } - - const historyNavigateFunc = replace - ? window.___history.replace - : window.___history.push - - const historyNavigateAction = replace ? `REPLACE` : `PUSH` - // Start a timer to wait for a second before transitioning and showing a // loader in case resources aren't around yet. const timeoutId = setTimeout(() => { emitter.emit(`onDelayedLoadPageResources`, { pathname }) apiRunner(`onRouteUpdateDelayed`, { - location, - action: historyNavigateAction, + location: window.location, }) }, 1000) - lastNavigateToLocationString = `${location.pathname}${location.search}${ - location.hash - }` + lastNavigateToLocationString = to - apiRunner(`onPreRouteUpdate`, { location, action: historyNavigateAction }) + apiRunner(`onPreRouteUpdate`, { location: window.location }) const loaderCallback = pageResources => { if (!pageResources) { @@ -115,7 +89,7 @@ const navigate = (to, replace) => { loader.getResourcesForPathname(`/404.html`, loaderCallback) } else { clearTimeout(timeoutId) - historyNavigateFunc(location) + reachNavigate(to, { replace }) } } @@ -152,4 +126,4 @@ function init() { maybeRedirect(window.location.pathname) } -export { init, shouldUpdateScroll, attachToHistory } +export { init, shouldUpdateScroll } diff --git a/packages/gatsby/cache-dir/parse-path.js b/packages/gatsby/cache-dir/parse-path.js new file mode 100644 index 0000000000000..040ffefdc53f6 --- /dev/null +++ b/packages/gatsby/cache-dir/parse-path.js @@ -0,0 +1,23 @@ +export default function parsePath(path) { + var pathname = path || `/` + var search = `` + var hash = `` + + var hashIndex = pathname.indexOf(`#`) + if (hashIndex !== -1) { + hash = pathname.substr(hashIndex) + pathname = pathname.substr(0, hashIndex) + } + + var searchIndex = pathname.indexOf(`?`) + if (searchIndex !== -1) { + search = pathname.substr(searchIndex) + pathname = pathname.substr(0, searchIndex) + } + + return { + pathname: pathname, + search: search === `?` ? `` : search, + hash: hash === `#` ? `` : hash, + } +} diff --git a/packages/gatsby/cache-dir/production-app.js b/packages/gatsby/cache-dir/production-app.js index 81f1903d906f9..a14b127e8c973 100644 --- a/packages/gatsby/cache-dir/production-app.js +++ b/packages/gatsby/cache-dir/production-app.js @@ -1,16 +1,10 @@ import { apiRunner, apiRunnerAsync } from "./api-runner-browser" import React, { createElement } from "react" import ReactDOM from "react-dom" -import { Router, Route, withRouter, matchPath } from "react-router-dom" +import { Router } from "@reach/router" import { ScrollContext } from "gatsby-react-router-scroll" import domReady from "domready" -import { - shouldUpdateScroll, - attachToHistory, - init as navigationInit, -} from "./navigation" -import history from "./history" -window.___history = history +import { shouldUpdateScroll, init as navigationInit } from "./navigation" import emitter from "./emitter" window.___emitter = emitter import PageRenderer from "./page-renderer" @@ -18,7 +12,8 @@ import asyncRequires from "./async-requires" import loader from "./loader" window.asyncRequires = asyncRequires -window.matchPath = matchPath +window.___emitter = emitter +window.___loader = loader loader.addPagesArray([window.page]) loader.addDataPaths({ [window.page.jsonName]: window.dataPath }) @@ -36,41 +31,52 @@ apiRunnerAsync(`onClientEntry`).then(() => { // Call onRouteUpdate on the initial page load. apiRunner(`onRouteUpdate`, { - location: history.location, - action: history.action, + location: window.history.location, }) - const AltRouter = apiRunner(`replaceRouterComponent`, { history })[0] + class RouteHandler extends React.Component { + render() { + const { location } = this.props + let child + + // TODO + // check if hash + if element and if so scroll + // remove hash handling from gatsby-link + // check if scrollbehavior handles back button for + // restoring old position + // if not, add that. + + if (loader.getPage(location.pathname)) { + child = createElement(PageRenderer, { + isPage: true, + ...this.props, + }) + } else { + child = createElement(PageRenderer, { + isPage: true, + location: { pathname: `/404.html` }, + }) + } + + return ( + + {child} + + ) + } + } loader.getResourcesForPathname(window.location.pathname, () => { const Root = () => createElement( - AltRouter ? AltRouter : Router, + Router, { - basename: __PATH_PREFIX__, - history: !AltRouter ? history : undefined, + basepath: __PATH_PREFIX__, }, - createElement( - ScrollContext, - { shouldUpdateScroll }, - createElement(withRouter(Route), { - render: routeProps => { - attachToHistory(routeProps.history) - - if (loader.getPage(routeProps.location.pathname)) { - return createElement(PageRenderer, { - isPage: true, - ...routeProps, - }) - } else { - return createElement(PageRenderer, { - isPage: true, - location: { pathname: `/404.html` }, - }) - } - }, - }) - ) + createElement(RouteHandler, { path: `/*` }) ) const NewRoot = apiRunner(`wrapRootComponent`, { Root }, Root)[0] diff --git a/packages/gatsby/cache-dir/root.js b/packages/gatsby/cache-dir/root.js index f592de2f33a8c..7a8ede5911e73 100644 --- a/packages/gatsby/cache-dir/root.js +++ b/packages/gatsby/cache-dir/root.js @@ -1,12 +1,7 @@ -import { createElement } from "react" -import { Router, Route } from "react-router-dom" +import React, { createElement } from "react" +import { Router } from "@reach/router" import { ScrollContext } from "gatsby-react-router-scroll" -import { - shouldUpdateScroll, - attachToHistory, - init as navigationInit, -} from "./navigation" -import history from "./history" +import { shouldUpdateScroll, init as navigationInit } from "./navigation" import { apiRunner } from "./api-runner-browser" import syncRequires from "./sync-requires" import pages from "./pages.json" @@ -50,53 +45,54 @@ navigationInit() // Call onRouteUpdate on the initial page load. apiRunner(`onRouteUpdate`, { - location: history.location, - action: history.action, + location: window.history.location, }) -const AltRouter = apiRunner(`replaceRouterComponent`, { history })[0] +class RouteHandler extends React.Component { + render() { + const { location } = this.props + const { pathname } = location + const pageResources = loader.getResourcesForPathname(pathname) + const isPage = !!(pageResources && pageResources.component) + let child + if (isPage) { + child = ( + + ) + } else { + const dev404Page = pages.find(p => /^\/dev-404-page/.test(p.path)) + child = createElement( + syncRequires.components[dev404Page.componentChunkName], + { + pages, + ...this.props, + } + ) + } + + return ( + + {child} + + ) + } +} const Root = () => createElement( - AltRouter ? AltRouter : Router, + Router, { - basename: __PATH_PREFIX__, - history: !AltRouter ? history : undefined, + basepath: __PATH_PREFIX__, }, - createElement( - ScrollContext, - { shouldUpdateScroll }, - createElement(Route, { - // eslint-disable-next-line react/display-name - render: routeProps => { - attachToHistory(routeProps.history) - const { pathname } = routeProps.location - const pageResources = loader.getResourcesForPathname(pathname) - const isPage = !!(pageResources && pageResources.component) - if (isPage) { - return createElement(JSONStore, { - pages, - ...routeProps, - pageResources, - }) - } else { - const dev404Page = pages.find(p => /^\/dev-404-page/.test(p.path)) - return createElement(Route, { - key: `404-page`, - // eslint-disable-next-line react/display-name - component: props => - createElement( - syncRequires.components[dev404Page.componentChunkName], - { - pages, - ...routeProps, - } - ), - }) - } - }, - }) - ) + createElement(RouteHandler, { path: `/*` }) ) // Let site, plugins wrap the site e.g. for Redux. diff --git a/packages/gatsby/cache-dir/static-entry.js b/packages/gatsby/cache-dir/static-entry.js index d9909375355ba..c6947a175ba7c 100644 --- a/packages/gatsby/cache-dir/static-entry.js +++ b/packages/gatsby/cache-dir/static-entry.js @@ -2,7 +2,7 @@ const React = require(`react`) const fs = require(`fs`) const { join } = require(`path`) const { renderToString, renderToStaticMarkup } = require(`react-dom/server`) -const { StaticRouter, Route } = require(`react-router-dom`) +const { ServerLocation, Router } = require(`@reach/router`) const { get, merge, isObject, flatten, uniqBy } = require(`lodash`) const apiRunner = require(`./api-runner-ssr`) @@ -107,28 +107,26 @@ export default (pagePath, callback) => { } } - const AltStaticRouter = apiRunner(`replaceStaticRouterComponent`)[0] - - apiRunner(`replaceStaticRouterComponent`) + class RouteHandler extends React.Component { + render() { + return createElement(syncRequires.components[page.componentChunkName], { + ...this.props, + ...dataAndContext, + pathContext: dataAndContext.pageContext, + }) + } + } const bodyComponent = createElement( - AltStaticRouter || StaticRouter, - { - basename: pathPrefix.slice(0, -1), - location: { - pathname: pagePath, + ServerLocation, + { url: pagePath }, + createElement( + Router, + { + basepath: pathPrefix.slice(0, -1), }, - context: {}, - }, - createElement(Route, { - // eslint-disable-next-line react/display-name - render: routeProps => - createElement(syncRequires.components[page.componentChunkName], { - ...routeProps, - ...dataAndContext, - pathContext: dataAndContext.pageContext, - }), - }) + createElement(RouteHandler, { path: `/*` }) + ) ) // Let the site or plugin render the page component. diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 75ee7f28dfa3f..d88899600787c 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -22,6 +22,7 @@ "@babel/preset-react": "7.0.0-beta.52", "@babel/runtime": "7.0.0-beta.52", "@babel/traverse": "7.0.0-beta.52", + "@reach/router": "^1.1.1", "async": "^2.1.2", "autoprefixer": "^8.6.5", "babel-core": "7.0.0-bridge.0", @@ -109,8 +110,6 @@ "react-error-overlay": "^3.0.0", "react-hot-loader": "^4.1.0", "react-lifecycles-compat": "^3.0.2", - "react-router": "^4.1.1", - "react-router-dom": "^4.1.1", "redux": "^3.6.0", "relay-compiler": "1.5.0", "request": "^2.85.0", diff --git a/packages/gatsby/src/utils/webpack.config.js b/packages/gatsby/src/utils/webpack.config.js index e593c44efbe2d..a1d2feb5ca335 100644 --- a/packages/gatsby/src/utils/webpack.config.js +++ b/packages/gatsby/src/utils/webpack.config.js @@ -331,15 +331,6 @@ module.exports = async ( { oneOf: [rules.cssModules(), rules.css()], }, - - // Remove manually unused React Router modules. Try removing these - // rules whenever they get around to making a new release with their - // tree shaking fixes. - { test: /HashHistory/, use: `null-loader` }, - { test: /MemoryHistory/, use: `null-loader` }, - { test: /StaticRouter/, use: `null-loader` }, - { test: /MemoryRouter/, use: `null-loader` }, - { test: /HashRouter/, use: `null-loader` }, ]) break diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 1ddc9cf494185..f06c10ab17c63 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -172,6 +172,8 @@ link: /docs/lighthouse-audit/ - title: API Reference items: + - title: Gatsby Link + link: /docs/gatsby-link/ - title: Gatsby Node APIs link: /docs/node-apis/ - title: Gatsby Browser APIs @@ -265,4 +267,4 @@ - title: Glossary* link: /docs/glossary/ - title: Commands (Gatsby CLI)* - link: /docs/gatsby-cli/ \ No newline at end of file + link: /docs/gatsby-cli/ diff --git a/www/src/templates/template-starter-showcase.js b/www/src/templates/template-starter-showcase.js index 492d49cc0faae..da69e2d64d66c 100644 --- a/www/src/templates/template-starter-showcase.js +++ b/www/src/templates/template-starter-showcase.js @@ -109,7 +109,8 @@ class StarterTemplate extends React.Component { }} > {starterShowcase.owner.login} - {` `} + + {` `} /

    @@ -118,7 +119,8 @@ class StarterTemplate extends React.Component { ⭐ - {` `} + + {` `} {starterShowcase.stars}

    @@ -367,7 +369,8 @@ class StarterTemplate extends React.Component { {` `} - {` `} + + {` `} {dep} ) diff --git a/www/src/views/starter-showcase/showcase-list.js b/www/src/views/starter-showcase/showcase-list.js index 0257387a96722..acf99df4c6bea 100644 --- a/www/src/views/starter-showcase/showcase-list.js +++ b/www/src/views/starter-showcase/showcase-list.js @@ -148,7 +148,8 @@ const ShowcaseList = ({ urlState, items, imgs, count, sortRecent }) => { } css={{ ...styles.noLinkUnderline }} > - {` `} + + {` `} { rel="noopener noreferrer" css={{ ...styles.noLinkUnderline }} > - {` `} + + {` `} { rel="noopener noreferrer" css={{ ...styles.noLinkUnderline }} > - {` `} + + {` `} diff --git a/yarn.lock b/yarn.lock index 0c84890b580e8..36b0e6d0e6d34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1578,6 +1578,16 @@ version "1.1.0" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a" +"@reach/router@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.1.1.tgz#24a5b20f1cc9e55e2cbcdc454fb82c94db479a81" + dependencies: + create-react-context "^0.2.1" + invariant "^2.2.3" + prop-types "^15.6.1" + react-lifecycles-compat "^3.0.4" + warning "^3.0.0" + "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" @@ -4752,6 +4762,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-context@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.2.tgz#9836542f9aaa22868cd7d4a6f82667df38019dca" + dependencies: + fbjs "^0.8.0" + gud "^1.0.0" + cross-env@^5.0.5, cross-env@^5.1.4: version "5.2.0" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.0.tgz#6ecd4c015d5773e614039ee529076669b9d126f2" @@ -6555,7 +6572,7 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.8.14, fbjs@^0.8.16: +fbjs@^0.8.0, fbjs@^0.8.14, fbjs@^0.8.16: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" dependencies: @@ -7525,6 +7542,10 @@ gtoken@^2.3.0: mime "^2.2.0" pify "^3.0.0" +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + guess-ga@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/guess-ga/-/guess-ga-0.1.1.tgz#7d92ce2bbdfa93e992efaee30ae2bd4465177a68" @@ -8291,7 +8312,7 @@ into-stream@^3.1.0: from2 "^2.1.1" p-is-promise "^1.1.0" -invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -12566,7 +12587,7 @@ react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" -react-router-dom@^4.1.1, react-router-dom@^4.2.2: +react-router-dom@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" dependencies: @@ -12577,7 +12598,7 @@ react-router-dom@^4.1.1, react-router-dom@^4.2.2: react-router "^4.3.1" warning "^4.0.1" -react-router@^4.1.1, react-router@^4.3.1: +react-router@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" dependencies: