Skip to content

Commit

Permalink
Adopt Gatsby (badges#2906)
Browse files Browse the repository at this point in the history
While Next.js can handle static sites, we've had a few issues with it, notably a performance hit at runtime and some bugginess around routing and SSR. Gatsby being fully intended for high-performance static sites makes it a great technical fit for the Shields frontend. The `createPages()` API should be a really nice way to add a page for each service family, for example.

This migrates the frontend from Next.js to Gatsby. Gatsby is a powerful tool, which has a bit of downside as there's a lot to dig through. Overall I found configuration easier than Next.js. There are a lot of plugins and for the most part they worked out of the box. The documentation is good.

Links are cleaner now: there is no #. This will break old links though perhaps we could add some redirection to help with that. The only one I’m really concerned about `/#/endpoint`. I’m not sure if folks are deep-linking to the category pages.

There are a lot of enhancements we could add, in order to speed up the site even more. In particular we could think about inlining the SVGs rather than making separate requests for each one.

While Gatsby recommends GraphQL, it's not required. To keep things simple and reduce the learning curve, I did not use it here.

Close badges#1943 
Fix badges#2837 Fix badges#2616
  • Loading branch information
paulmelnikow authored Feb 6, 2019
1 parent cf7b76d commit d8ce045
Show file tree
Hide file tree
Showing 45 changed files with 11,217 additions and 5,268 deletions.
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ jobs:
- run:
name: Danger
when: always
environment:
# https://github.com/gatsbyjs/gatsby/pull/11555
NODE_ENV: test
command: npm run danger ci

frontend:
Expand Down
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/build
/coverage
/__snapshots__
/public
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,7 @@ service-definitions.yml

# Template for the local runtime configuration.
!/config/local*.template.yml

# Gatsby
/.cache
/public
30 changes: 30 additions & 0 deletions .nowignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
*
!frontend/
!gh-badges/
!lib/
!core/
!logo/
!pages/
!public/
!templates/
!services/
!package-lock.json
!/*.js
!scripts/export-*.js
!config/
config/local*.yml
*.spec.js
*~
.env
.circleci
.github
.vscode
__snapshots__
.buildpacks
.eslint*
.editorconfig
.nycrc*
.gitpod*
.prettier*
CONTRIBUTING.md
Dockerfile
5 changes: 3 additions & 2 deletions .nycrc-frontend.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"reporter": ["lcov"],
"all": true,
"all": false,
"silent": true,
"clean": false,
"sourceMap": false,
"instrument": false,
"exclude": ["**/*.spec.js"]
"include": ["frontend/**/*.js"],
"exclude": ["**/*.spec.js", "**/mocha-*.js"]
}
2 changes: 1 addition & 1 deletion .nycrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"**/test-helpers.js",
"**/*-test-helpers.js",
"dangerfile.js",
"next.config.js",
"gatsby-*.js",
"services/**/*.tester.js",
"services/service-tester.js",
"services/create-service-tester.js",
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package-lock.json
/__snapshots__
/.next
/build
/public
/coverage
private/*.json
/.nyc_output
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ push-s2:
deploy-gh-pages:
rm -rf ${FRONTEND_TMP}
git worktree prune
BASE_URL=https://img.shields.io \
GATSBY_BASE_URL=https://img.shields.io \
NEXT_ASSET_PREFIX=https://shields.io \
npm run build
git worktree add -B gh-pages ${FRONTEND_TMP}
Expand Down
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="https://rawgit.com/badges/shields/master/static/logo.svg"
<img src="https://rawgit.com/badges/shields/master/frontend/static/logo.svg"
height="130">
</p>
<p align="center">
Expand Down Expand Up @@ -116,9 +116,6 @@ Please report any Gitpod bugs, questions, or suggestions in issue

[![Edit with Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/badges/shields)

To analyze the frontend bundle, run `npm install webpack-bundle-analyzer` and
then `ANALYZE=true npm start`.

[Snapshot tests][] ensure we don't inadvertently make changes that affect the
SVG or JSON output. When deliberately changing the output, run
`SNAPSHOT_DRY=1 npm run test:js:server` to preview changes to the saved
Expand Down
29 changes: 18 additions & 11 deletions core/base-service/legacy-result-sender.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
'use strict'

const fs = require('fs')
const path = require('path')
const stream = require('stream')
const svg2img = require('../../gh-badges/lib/svg-to-img')
const log = require('../server/log')

const internalError = fs.readFileSync(
path.resolve(__dirname, '..', 'server', 'error-pages', '500.html'),
'utf-8'
)

function streamFromString(str) {
const newStream = new stream.Readable()
newStream._read = () => {
Expand All @@ -13,16 +20,6 @@ function streamFromString(str) {
return newStream
}

function makeSend(format, askres, end) {
if (format === 'svg') {
return res => sendSVG(res, askres, end)
} else if (format === 'json') {
return res => sendJSON(res, askres, end)
} else {
return res => sendOther(format, res, askres, end)
}
}

function sendSVG(res, askres, end) {
askres.setHeader('Content-Type', 'image/svg+xml;charset=utf-8')
end(null, { template: streamFromString(res) })
Expand All @@ -39,7 +36,7 @@ function sendOther(format, res, askres, end) {
.catch(err => {
// This emits status code 200, though 500 would be preferable.
log.error('svg2img error', err)
end(null, { template: '500.html' })
end(internalError)
})
}

Expand All @@ -49,6 +46,16 @@ function sendJSON(res, askres, end) {
end(null, { template: streamFromString(res) })
}

function makeSend(format, askres, end) {
if (format === 'svg') {
return res => sendSVG(res, askres, end)
} else if (format === 'json') {
return res => sendJSON(res, askres, end)
} else {
return res => sendOther(format, res, askres, end)
}
}

module.exports = {
makeSend,
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
10 changes: 8 additions & 2 deletions core/server/server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const fs = require('fs')
const bytes = require('bytes')
const path = require('path')
const url = require('url')
Expand All @@ -25,6 +26,11 @@ const PrometheusMetrics = require('./prometheus-metrics')
const optionalUrl = Joi.string().uri({ scheme: ['http', 'https'] })
const requiredUrl = optionalUrl.required()

const notFound = fs.readFileSync(
path.resolve(__dirname, 'error-pages', '404.html'),
'utf-8'
)

const publicConfigSchema = Joi.object({
bind: {
port: Joi.number().port(),
Expand Down Expand Up @@ -172,7 +178,7 @@ module.exports = class Server {
})

camp.notfound(/.*/, (query, match, end, request) => {
end(null, { template: '404.html' })
end(notFound)
})
}

Expand Down Expand Up @@ -238,7 +244,7 @@ module.exports = class Server {
log(`Server is starting up: ${this.baseUrl}`)

const camp = (this.camp = Camp.start({
documentRoot: path.join(__dirname, '..', '..', 'public'),
documentRoot: path.resolve(__dirname, '..', '..', 'public'),
port,
hostname,
secure,
Expand Down
2 changes: 1 addition & 1 deletion core/server/server.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe('The server', function() {

context('with svg2img error', function() {
const expectedError = fs.readFileSync(
path.resolve(__dirname, '..', '..', 'public', '500.html')
path.resolve(__dirname, 'error-pages', '500.html')
)

let toBufferStub
Expand Down
3 changes: 1 addition & 2 deletions dangerfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ const packageJson = fileMatch('package.json')
const packageLock = fileMatch('package-lock.json')
const secretsDocs = fileMatch('doc/server-secrets.md')
const capitals = fileMatch('**/*[A-Z]*.js')
// _document.js is used by convention by Next.
const underscores = fileMatch('**/*_*.js', '!pages/_document.js')
const underscores = fileMatch('**/*_*.js')
const targetBranch = danger.github.pr.base.ref

message(
Expand Down
4 changes: 2 additions & 2 deletions doc/self-hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,10 @@ These are documented in [server-secrets.md](./server-secrets.md)
If you want to host the frontend on a separate server, such as cloud storage
or a CDN, you can do that.

First, build the frontend, pointing `BASE_URL` to your server.
First, build the frontend, pointing `GATSBY_BASE_URL` to your server.

```sh
BASE_URL=https://your-server.example.com npm run build
GATSBY_BASE_URL=https://your-server.example.com npm run build
```

Then copy the contents of the `build/` folder to your static hosting / CDN.
Expand Down
8 changes: 3 additions & 5 deletions frontend/components/category-headings.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Link, NavLink } from 'react-router-dom'
import { Link } from 'gatsby'
import { H3 } from './common'

const CategoryHeading = ({ category }) => {
const { id, name } = category

return (
<Link to={`/examples/${id}`}>
<Link to={`/category/${id}`}>
<H3 id={id}>{name}</H3>
</Link>
)
Expand Down Expand Up @@ -67,9 +67,7 @@ const CategoryNav = ({ categories }) => (
<ul>
{categories.map(({ id, name }) => (
<li key={id}>
<NavLink to={`/examples/${id}`} activeClassName="active">
{name}
</NavLink>
<Link to={`/category/${id}`}>{name}</Link>
</li>
))}
</ul>
Expand Down
32 changes: 14 additions & 18 deletions frontend/components/donate.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import React from 'react'
import styled from 'styled-components'

export default class DonateBox extends React.Component {
render() {
return (
<div>
<div id="donate">
Love Shields? Please consider{' '}
<a href="https://opencollective.com/shields">donating</a> to sustain
our activities
</div>
<style jsx>{`
#donate {
padding: 25px 50px;
}
`}</style>
</div>
)
}
}
const Donate = styled.div`
padding: 25px 50px;
`

const DonateBox = () => (
<Donate>
Love Shields? Please consider{' '}
<a href="https://opencollective.com/shields">donating</a> to sustain our
activities
</Donate>
)

export default DonateBox
5 changes: 3 additions & 2 deletions frontend/components/header.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Link } from 'react-router-dom'
import { Link } from 'gatsby'
import React from 'react'
import styled from 'styled-components'
import { VerticalSpace } from './common'
import Logo from '../images/logo.svg'

const Highlights = styled.p`
font-style: italic;
Expand All @@ -10,7 +11,7 @@ const Highlights = styled.p`
export default () => (
<section>
<Link to="/">
<img alt="Shields.io" src="/static/logo.svg" />
<Logo />
</Link>

<VerticalSpace />
Expand Down
27 changes: 12 additions & 15 deletions frontend/components/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,14 @@ export default class Main extends React.Component {
}

static propTypes = {
match: PropTypes.object.isRequired,
}

get category() {
return this.props.match.params.category
// `pageContext` is the `context` passed to `createPage()` in
// `gatsby-node.js`. In the case of the index page, `pageContext` is empty.
pageContext: {
category: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
}),
}.isRequired,
}

performSearch(query) {
Expand Down Expand Up @@ -114,11 +117,11 @@ export default class Main extends React.Component {
}

renderMain() {
const { category: categoryId } = this
const {
pageContext: { category },
} = this.props
const { isSearchInProgress, isQueryTooShort, searchResults } = this.state

const category = findCategory(categoryId)

if (isSearchInProgress) {
return <div>searching...</div>
} else if (isQueryTooShort) {
Expand All @@ -129,7 +132,7 @@ export default class Main extends React.Component {
)
} else if (category) {
const definitions = ServiceDefinitionSetHelper.create(
getDefinitionsForCategory(categoryId)
getDefinitionsForCategory(category.id)
)
.notDeprecated()
.toArray()
Expand All @@ -139,12 +142,6 @@ export default class Main extends React.Component {
{this.renderCategory(category, definitions)}
</div>
)
} else if (categoryId) {
return (
<div>
Unknown category <b>{categoryId}</b>
</div>
)
} else {
return <CategoryHeadings categories={categories} />
}
Expand Down
Loading

0 comments on commit d8ce045

Please sign in to comment.