Use link component instead of catch links plugin

Luke Karrys committed Oct 20, 2023 at 10:52 UTC 1fc8539401e886201dc7b5c4656d921536c3dbaa
12 files changed +127 -100
gatsby-config.js
+1 -4
@@ -63,11 +63,8 @@ module.exports = {
63 DEV_SSR: !!process.env.DEV_SSR,
64 },
65 plugins: [
66 - {
67 - resolve: 'gatsby-plugin-styled-components',
68 - },
66 + 'gatsby-plugin-styled-components',
67 'gatsby-plugin-react-helmet',
70 - 'gatsby-plugin-catch-links',
68 'gatsby-transformer-yaml',
69 {
70 resolve: 'gatsby-plugin-mdx',
gatsby-node.js
+62 -75
@@ -1,6 +1,6 @@
1 -const path = require('path')
1 +const {resolve, join: _join, relative} = require('path')
2 +const join = (...paths) => _join(...paths).replace(/\\/g, '/')
3
3 -const DEV = process.env.NODE_ENV === 'development'
4 const SHOW_CONTRIBUTORS = false
5 const REPO = {
6 url: 'https://github.com/npm/documentation',
@@ -32,7 +32,7 @@ exports.onCreateWebpackConfig = ({actions}) => {
32 actions.setWebpackConfig({
33 resolve: {
34 alias: {
35 - '~': path.resolve(__dirname, 'src/'),
35 + '~': resolve(__dirname, 'src/'),
36 },
37 extensions: ['.js'],
38 },
@@ -61,8 +61,6 @@ exports.createSchemaCustomization = ({actions: {createTypes}}) => {
61 }
62
63 exports.createPages = async ({graphql, actions}) => {
64 - const rootAbsolutePath = process.cwd()
65 -
64 const {data} = await graphql(`
65 {
66 allMdx {
@@ -96,82 +94,71 @@ exports.createPages = async ({graphql, actions}) => {
94 `)
95
96 // Turn every MDX file into a page.
99 - return Promise.all(
100 - data.allMdx.nodes.map(async node => {
101 - const {
102 - id,
103 - frontmatter,
104 - internal: {contentFilePath},
105 - tableOfContents = {},
106 - } = node
97 + return Promise.all(data.allMdx.nodes.map(node => createPage(node, actions)))
98 +}
99
108 - const pagePath = getPath(node)
109 - const relativePath = path.relative(rootAbsolutePath, contentFilePath)
110 - const editUrl = getEditUrl(REPO, relativePath, frontmatter)
100 +async function createPage(
101 + {
102 + id,
103 + internal: {contentFilePath},
104 + fields: {slug} = {},
105 + frontmatter = {},
106 + tableOfContents = {},
107 + parent: {relativeDirectory, name: parentName},
108 + },
109 + actions,
110 +) {
111 + // sites can programmatically override slug, that takes priority
112 + // then a slug specified in frontmatter
113 + // finally, we'll just use the path on disk
114 + const pagePath = slug ?? frontmatter.slug ?? join(relativeDirectory, parentName === 'index' ? '/' : parentName)
115
112 - const contributors = SHOW_CONTRIBUTORS ? await fetchContributors(REPO, relativePath, frontmatter) : {}
116 + const relativePath = relative(process.cwd(), contentFilePath)
117
114 - // Fix some old CLI pages which have mismatched headings at the top level.
115 - // All top level headings should be the same level.
116 - const toc = tableOfContents.items?.reduce((acc, item) => {
117 - if (!item.url && Array.isArray(item.items)) {
118 - acc.push(...item.items)
119 - } else {
120 - acc.push(item)
121 - }
122 - return acc
123 - }, [])
118 + const editUrl = getEditUrl(REPO, relativePath, frontmatter)
119
125 - actions.createPage({
126 - path: pagePath,
127 - component: contentFilePath,
128 - context: {
129 - mdxId: id,
130 - editUrl,
131 - contributors,
132 - tableOfContents: toc,
133 - repositoryUrl: REPO.url,
134 - },
135 - })
136 -
137 - if (!DEV) {
138 - for (const from of frontmatter.redirect_from ?? []) {
139 - actions.createRedirect({
140 - fromPath: from,
141 - toPath: `/${pagePath}`,
142 - isPermanent: true,
143 - redirectInBrowser: true,
144 - })
120 + const contributors = SHOW_CONTRIBUTORS ? await fetchContributors(REPO, relativePath, frontmatter) : {}
121
146 - if (pagePath.startsWith('cli/') && !from.endsWith('index')) {
147 - actions.createRedirect({
148 - fromPath: `${from}.html`,
149 - toPath: `/${pagePath}`,
150 - isPermanent: true,
151 - redirectInBrowser: true,
152 - })
153 - }
154 - }
155 - }
156 - }),
157 - )
158 -}
122 + // Fix some old CLI pages which have mismatched headings at the top level.
123 + // All top level headings should be the same level.
124 + const toc = tableOfContents.items?.reduce((acc, item) => {
125 + if (!item.url && Array.isArray(item.items)) {
126 + acc.push(...item.items)
127 + } else {
128 + acc.push(item)
129 + }
130 + return acc
131 + }, [])
132 +
133 + actions.createPage({
134 + path: pagePath,
135 + component: contentFilePath,
136 + context: {
137 + mdxId: id,
138 + editUrl,
139 + contributors,
140 + tableOfContents: toc,
141 + repositoryUrl: REPO.url,
142 + },
143 + })
144
160 -function getPath(node) {
161 - // sites can programmatically override slug, that takes priority
162 - if (node.fields && node.fields.slug) {
163 - return node.fields.slug
164 - }
145 + for (const from of frontmatter.redirect_from ?? []) {
146 + actions.createRedirect({
147 + fromPath: from,
148 + toPath: `/${pagePath}`,
149 + isPermanent: true,
150 + redirectInBrowser: true,
151 + })
152
166 - // then a slug specified in frontmatter
167 - if (node.frontmatter && node.frontmatter.slug) {
168 - return node.frontmatter.slug
153 + if (pagePath.startsWith('cli/') && !from.endsWith('index')) {
154 + actions.createRedirect({
155 + fromPath: `${from}.html`,
156 + toPath: `/${pagePath}`,
157 + isPermanent: true,
158 + redirectInBrowser: true,
159 + })
160 + }
161 }
170 -
171 - // finally, we'll just use the path on disk
172 - return path
173 - .join(node.parent.relativeDirectory, node.parent.name === 'index' ? '/' : node.parent.name)
174 - .replace(/\\/g, '/') // Windows paths to forward slashes
162 }
163
164 function getGitHubData(repo, overrideData, filePath) {
@@ -204,8 +191,8 @@ function getEditUrl(repo, filePath, overrideData = {}) {
191 return null
192 }
193
207 - const gh = getGitHubData(repo, overrideData, filePath)
208 - return `https://github.com/${gh.nwo}/edit/${gh.branch}/${gh.path}`
194 + const {nwo, branch, path} = getGitHubData(repo, overrideData, filePath)
195 + return `https://github.com/${nwo}/edit/${branch}/${path}`
196 }
197
198 const CONTRIBUTOR_CACHE = new Map()
package.json
-1
@@ -37,7 +37,6 @@
37 "framer-motion": "^10.16.4",
38 "fuse.js": "^3.6.1",
39 "gatsby": "^5.12.7",
40 - "gatsby-plugin-catch-links": "^5.12.0",
40 "gatsby-plugin-manifest": "^5.12.1",
41 "gatsby-plugin-mdx": "^5.12.1",
42 "gatsby-plugin-meta-redirect": "^1.1.1",
src/components/contributors.js
+2 -1
@@ -1,5 +1,6 @@
1 -import {Box, Avatar, Link, Text, Tooltip} from '@primer/react'
1 import React from 'react'
2 +import {Box, Avatar, Text, Tooltip} from '@primer/react'
3 +import Link from './link'
4
5 const months = [
6 'January',
src/components/header.js
+2 -3
@@ -1,10 +1,10 @@
1 import React from 'react'
2 -import {Box, Link, ThemeProvider} from '@primer/react'
3 -import {Link as GatsbyLink} from 'gatsby'
2 +import {Box, ThemeProvider} from '@primer/react'
3 import styled from 'styled-components'
4 import MobileSearch from './mobile-search'
5 import NavDrawer from './nav-drawer'
6 import Search from './search'
7 +import Link from './link'
8 import useSearch from '../hooks/use-search'
9 import {HEADER_HEIGHT, HEADER_BAR, NPM_RED} from '../constants'
10 import useSiteMetadata from '../hooks/use-site-metadata'
@@ -56,7 +56,6 @@ function Header() {
56 >
57 <Box sx={{display: 'flex', alignItems: 'center'}}>
58 <Link
59 - as={GatsbyLink}
59 to="/"
60 sx={{
61 mr: 4,
src/components/link.js new
+41
@@ -0,0 +1,41 @@
1 +import React from 'react'
2 +import {Link as PrimerLink} from '@primer/react'
3 +import {Link as GatsbyLink} from 'gatsby'
4 +
5 +const FALLBACK = `http://_${Math.random().toString().slice(2)}._${Math.random().toString().slice(2)}`
6 +
7 +const getLocalPath = href => {
8 + if (!href || href.startsWith('#')) {
9 + return null
10 + }
11 +
12 + try {
13 + const url = new URL(href, FALLBACK)
14 + if (url.host === 'docs.npmjs.com' || url.origin === FALLBACK) {
15 + return `${url.pathname}${url.search}${url.hash}`
16 + }
17 + } catch {
18 + // ignore errors which will just pass along all props to PrimerLink
19 + }
20 +
21 + return null
22 +}
23 +
24 +const GatsbyLinkWithoutSxProps = React.forwardRef(function GatsbyLinkWithoutSxProps(
25 + {sx, underline, hoverColor, muted, ...props},
26 + ref,
27 +) {
28 + return <GatsbyLink ref={ref} {...props} />
29 +})
30 +
31 +const Link = React.forwardRef(function Link({to, href, ...props}, ref) {
32 + const localPath = getLocalPath(href)
33 +
34 + if (to || localPath !== null) {
35 + return <PrimerLink ref={ref} as={GatsbyLinkWithoutSxProps} to={to || localPath} {...props} />
36 + }
37 +
38 + return <PrimerLink ref={ref} href={href} {...props} />
39 +})
40 +
41 +export default Link
src/components/nav-drawer.js
+3 -3
@@ -1,7 +1,7 @@
1 import React from 'react'
2 -import {Button, Box, Link, ThemeProvider} from '@primer/react'
2 +import {Button, Box, ThemeProvider} from '@primer/react'
3 import {XIcon, ThreeBarsIcon} from '@primer/octicons-react'
4 -import {Link as GatsbyLink} from 'gatsby'
4 +import Link from './link'
5 import Drawer from './drawer'
6 import NavItems from './nav-items'
7 import useSiteMetadata from '../hooks/use-site-metadata'
@@ -71,7 +71,7 @@ function NavDrawer() {
71 display: 'flex',
72 }}
73 >
74 - <Link as={GatsbyLink} to="/" sx={{display: 'inline-block', color: 'inherit'}}>
74 + <Link to="/" sx={{display: 'inline-block', color: 'inherit'}}>
75 {siteMetadata.title}
76 </Link>
77 <Button aria-label="Close" onClick={setClose}>
src/components/nav-items.js
+2 -2
@@ -1,8 +1,8 @@
1 import React from 'react'
2 import {Box} from '@primer/react'
3 -import {Link as GatsbyLink} from 'gatsby'
3 import {NavList} from '@primer/react/drafts'
4 import {LinkExternalIcon} from '@primer/octicons-react'
5 +import Link from './link'
6 import * as getNav from '../util/get-nav'
7 import VisuallyHidden from './visually-hidden'
8 import headerNavItems from '../../content/header-nav.yml'
@@ -15,7 +15,7 @@ const NavItem = ({item, path, depth}) => {
15
16 return (
17 <NavList.Item
18 - as={GatsbyLink}
18 + as={Link}
19 to={href}
20 defaultOpen={items && isCurrent}
21 aria-current={isCurrent ? 'page' : null}
src/components/page-footer.js
+2 -1
@@ -1,6 +1,7 @@
1 import React from 'react'
2 -import {Box, Link, Octicon} from '@primer/react'
2 +import {Box, Octicon} from '@primer/react'
3 import {PencilIcon} from '@primer/octicons-react'
4 +import Link from './link'
5 import Contributors from './contributors'
6
7 function PageFooter({editUrl, contributors = {}}) {
src/mdx/index.js
+6 -5
@@ -1,12 +1,13 @@
1 import React from 'react'
2 -import {Box, Heading, themeGet, Text, Link as PrimerLink, Octicon} from '@primer/react'
2 +import {Box, Heading, themeGet, Text, Octicon} from '@primer/react'
3 +import {withPrefix} from 'gatsby'
4 import styled from 'styled-components'
5 import {variant} from 'styled-system'
5 -import {withPrefix} from 'gatsby'
6 import {LinkIcon} from '@primer/octicons-react'
7 import textContent from 'react-addons-text-content'
8 import {FULL_HEADER_HEIGHT} from '../constants'
9 import usePage from '../hooks/use-page'
10 +import SiteLink from '../components/link'
11
12 export {default as Code} from './code'
13 export {default as Index} from './nav-hierarchy'
@@ -18,7 +19,7 @@ const required = (prop, name) => {
19 return prop
20 }
21
21 -export const Link = props => <PrimerLink underline {...props} />
22 +export const Link = props => <SiteLink underline {...props} />
23
24 const StyledHeading = styled(Heading)`
25 margin-top: ${themeGet('space.4')};
@@ -40,7 +41,7 @@ const StyledHeading = styled(Heading)`
41
42 const HeaderLink = ({autolink, children, ...props}) =>
43 autolink ? (
43 - <PrimerLink
44 + <SiteLink
45 {...props}
46 sx={{
47 color: 'inherit',
@@ -60,7 +61,7 @@ const HeaderLink = ({autolink, children, ...props}) =>
61 verticalAlign: 'middle !important',
62 }}
63 />
63 - </PrimerLink>
64 + </SiteLink>
65 ) : (
66 children
67 )
src/mdx/nav-hierarchy.js
+3 -3
@@ -1,6 +1,6 @@
1 import React from 'react'
2 -import {Box, Link} from '@primer/react'
3 -import {Link as GatsbyLink} from 'gatsby'
2 +import {Box} from '@primer/react'
3 +import Link from '../components/link'
4 import * as getNav from '../util/get-nav'
5 import usePage from '../hooks/use-page'
6
@@ -9,7 +9,7 @@ const HierarchyItem = ({item, depth, ...props}) => {
9
10 return (
11 <Box as="li" key={item.url}>
12 - <Link as={GatsbyLink} key={item.title} to={item.url}>
12 + <Link key={item.title} to={item.url}>
13 {item.title}
14 </Link>
15 {item.description ? <Box sx={{fontSize: 1, mb: 1}}>{item.description}</Box> : null}
src/page.js
+3 -2
@@ -1,11 +1,12 @@
1 import React from 'react'
2 -import {BaseStyles, themeGet, Link, Box} from '@primer/react'
2 +import {BaseStyles, themeGet, Box} from '@primer/react'
3 import styled, {createGlobalStyle} from 'styled-components'
4 import {SKIP_NAV} from './constants'
5 import {Helmet} from 'react-helmet'
6 import Slugger from 'github-slugger'
7 import Header from './components/header'
8 import Sidebar from './components/sidebar'
9 +import Link from './components/link'
10 import useSiteMetdata from './hooks/use-site-metadata'
11 import usePage, {PageProvider} from './hooks/use-page'
12 import getLayout from './layout'
@@ -75,7 +76,7 @@ const GlobalStyles = createGlobalStyle`
76 }
77 `
78
78 -const PageElement = ({element, props, ...rest}) => {
79 +const PageElement = ({element, props}) => {
80 const page = {
81 pageContext: props.pageContext,
82 frontmatter: props.pageContext?.frontmatter || {},