Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby): Set up Fast Refresh #29588

Merged
merged 23 commits into from
Feb 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 14 additions & 34 deletions packages/gatsby/cache-dir/error-overlay-handler.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,25 @@
const overlayPackage = require(`@pmmmwh/react-refresh-webpack-plugin/overlay`)

const ErrorOverlay = {
showCompileError: overlayPackage.showCompileError,
clearCompileError: overlayPackage.clearCompileError,
}

const errorMap = {}

function flat(arr) {
return Array.prototype.flat ? arr.flat() : [].concat(...arr)
}

const handleErrorOverlay = () => {
const errors = Object.values(errorMap)
let errorStringsToDisplay = []
let errorsToDisplay = []
if (errors.length > 0) {
errorStringsToDisplay = flat(errors)
.map(error => {
if (typeof error === `string`) {
return error
} else if (typeof error === `object`) {
const errorStrBuilder = [error.text]

if (error.filePath) {
errorStrBuilder.push(`File: ${error.filePath}`)
}

return errorStrBuilder.join(`\n\n`)
}

return null
})
.filter(Boolean)
errorsToDisplay = errors.flatMap(e => e).filter(Boolean)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary to do this handling here, I grab all info in the graphql-error component itself.
Also, we can use flatMap because we're now at Node 12.13 👍 and that's a Node 11 feature.

}

if (errorStringsToDisplay.length > 0) {
ErrorOverlay.showCompileError(errorStringsToDisplay.join(`\n\n`))
if (errorsToDisplay.length > 0) {
window._gatsbyEvents.push([
`FAST_REFRESH`,
{
action: `SHOW_GRAPHQL_ERRORS`,
payload: errorsToDisplay,
},
])
} else {
ErrorOverlay.clearCompileError()
window._gatsbyEvents.push([
`FAST_REFRESH`,
{ action: `CLEAR_GRAPHQL_ERRORS` },
])
}
}

Expand All @@ -52,5 +34,3 @@ export const reportError = (errorID, error) => {
}
handleErrorOverlay()
}

export { errorMap }
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import * as React from "react"
import { useId } from "./use-id"
import * as keys from "../helpers/keys"
import { match } from "../helpers/match"

function ChevronIcon() {
return (
<svg
focusable="false"
preserveAspectRatio="xMidYMid meet"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width="16"
height="16"
viewBox="0 0 16 16"
aria-hidden="true"
data-gatsby-overlay="chevron-icon"
>
<path d="M11 8L6 13 5.3 12.3 9.6 8 5.3 3.7 6 3z" />
</svg>
)
}

export function Accordion({ children, ...rest }) {
return (
<ul data-gatsby-overlay="accordion" {...rest}>
{children}
</ul>
)
}

export function AccordionItem({
children,
disabled = false,
open = false,
title = `title`,
...rest
}) {
const [isOpen, setIsOpen] = React.useState(open)
const [prevIsOpen, setPrevIsOpen] = React.useState(open)
const id = useId(`accordion-item`)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a stable id here (for both SSR and client for re-hydration) so that's why this helper exists


if (open !== prevIsOpen) {
setIsOpen(open)
setPrevIsOpen(open)
}

const toggleOpen = () => {
const nextValue = !isOpen
setIsOpen(nextValue)
}

// If the AccordionItem is open, and the user hits the ESC key, then close it
const onKeyDown = event => {
if (isOpen && match(event, keys.Escape)) {
setIsOpen(false)
}
}

return (
<li
data-gatsby-overlay="accordion__item"
data-accordion-active={isOpen ? `true` : `false`}
{...rest}
>
<button
data-gatsby-overlay="accordion__item__heading"
type="button"
disabled={disabled}
aria-controls={id}
aria-expanded={isOpen}
onClick={toggleOpen}
onKeyDown={onKeyDown}
>
<ChevronIcon />
<div data-gatsby-overlay="accordion__item__title">{title}</div>
</button>
<div id={id} data-gatsby-overlay="accordion__item__content">
{children}
</div>
</li>
)
}
Original file line number Diff line number Diff line change
@@ -1,44 +1,35 @@
import React from "react"
import Overlay from "./overlay"
import Anser from "anser"
import CodeFrame from "./code-frame"
import { prettifyStack } from "../utils"
import * as React from "react"
import { Overlay, Header, Body, Footer, HeaderOpenClose } from "./overlay"
import { CodeFrame } from "./code-frame"
import { prettifyStack, openInEditor } from "../utils"

const BuildError = ({ error, open, dismiss }) => {
const [file, cause, _emptyLine, ...rest] = error.split(`\n`)
const [_fullPath, _detailedError] = rest
const detailedError = Anser.ansiToJson(_detailedError, {
remove_empty: true,
json: true,
})
const lineNumberRegex = /^[0-9]*:[0-9]*$/g
const lineNumberFiltered = detailedError.filter(
d => d.content !== ` ` && d.content.match(lineNumberRegex)
)[0]?.content
const lineNumber = lineNumberFiltered.substr(
0,
lineNumberFiltered.indexOf(`:`)
)
// Error that is thrown on e.g. webpack errors and thus can't be dismissed and must be fixed
export function BuildError({ error }) {
// Incoming build error shape is like this:
// ./relative-path-to-file
// Additional information (sometimes empty line => handled in "prettifyStack" function)
// /absolute-path-to-file
// Errors/Warnings
const [file, ...rest] = error.split(`\n`)
const decoded = prettifyStack(rest)

const header = (
<>
<div data-gatsby-overlay="header__cause-file">
<p>{cause}</p>
<span>{file}</span>
</div>
<button
onClick={() => open(file, lineNumber)}
data-gatsby-overlay="header__open-in-editor"
>
Open in editor
</button>
</>
return (
<Overlay>
<Header data-gatsby-error-type="build-error">
<div data-gatsby-overlay="header__cause-file">
<h1 id="gatsby-overlay-labelledby">Failed to compile</h1>
<span>{file}</span>
</div>
<HeaderOpenClose open={() => openInEditor(file, 1)} dismiss={false} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a follow up: we should put this openInEditor on the same place as runtime errors so people know where to look.

</Header>
<Body>
<h2>Source</h2>
<CodeFrame decoded={decoded} />
</Body>
<Footer id="gatsby-overlay-describedby">
This error occurred during the build process and can only be dismissed
by fixing the error.
</Footer>
</Overlay>
)

const body = <CodeFrame decoded={decoded} />

return <Overlay header={header} body={body} dismiss={dismiss} />
}

export default BuildError
Original file line number Diff line number Diff line change
@@ -1,28 +1,54 @@
import React from "react"
import * as React from "react"

const CodeFrame = ({ decoded }) => (
<pre data-gatsby-overlay="pre">
<code data-gatsby-overlay="pre__code">
{decoded
? decoded.map((entry, index) => (
export function CodeFrame({ decoded }) {
if (!decoded) {
return (
<pre data-gatsby-overlay="pre">
<code data-gatsby-overlay="pre__code" />
</pre>
)
}

return (
<pre data-gatsby-overlay="pre">
<code data-gatsby-overlay="pre__code">
{decoded.map((entry, index) => {
// Check if content is "Enter" and render other element that collapses
// Otherwise an empty line would be printed
if (
index === 0 &&
entry.content ===
`
`
) {
return (
<span
key={`frame-${index}`}
data-gatsby-overlay="pre__code__span__empty"
/>
)
}

const style = {
color: entry.fg ? `var(--color-${entry.fg})` : undefined,
...(entry.decoration === `bold`
? { fontWeight: 800 }
: entry.decoration === `italic`
? { fontStyle: `italic` }
: undefined),
}

return (
<span
key={`frame-${index}`}
data-gatsby-overlay="pre__code__span"
style={{
color: entry.fg ? `var(--color-${entry.fg})` : undefined,
...(entry.decoration === `bold`
? { fontWeight: 800 }
: entry.decoration === `italic`
? { fontStyle: `italic` }
: undefined),
}}
style={style}
>
{entry.content}
</span>
))
: null}
</code>
</pre>
)

export default CodeFrame
)
})}
</code>
</pre>
)
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import React from "react"
import * as React from "react"

class ErrorBoundary extends React.Component {
state = { hasError: false }
export class ErrorBoundary extends React.Component {
state = { error: null }

componentDidCatch(error) {
this.props.onError(error)
}

componentDidMount() {
this.props.clearErrors()
}

static getDerivedStateFromError() {
return { hasError: true }
this.setState({ error })
}

render() {
return this.state.hasError ? null : this.props.children
// Without this check => possible infinite loop
return this.state.error && this.props.hasErrors ? null : this.props.children
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Necessary to also have this.state.error so that the background doesn't vanish

}
}

export default ErrorBoundary
Loading