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 || {},