diff --git a/packages/gatsby/cache-dir/__tests__/.babelrc b/packages/gatsby/cache-dir/__tests__/.babelrc index bde1b665cf99c..b1ff77b727284 100644 --- a/packages/gatsby/cache-dir/__tests__/.babelrc +++ b/packages/gatsby/cache-dir/__tests__/.babelrc @@ -1,41 +1,5 @@ { - babelrc: false, - presets: [ - [ - "@babel/preset-env", - { - loose: true, - modules: false, - useBuiltIns: "usage", - shippedProposals: true, - targets: { - browsers: [">0.25%", "not dead"], - }, - }, - ], - [ - "@babel/preset-react", - { - useBuiltIns: true, - pragma: "React.createElement", - }, - ], - ], - plugins: [ - [ - "@babel/plugin-proposal-class-properties", - { - loose: true, - }, - ], - "@babel/plugin-syntax-dynamic-import", - [ - "@babel/plugin-transform-runtime", - { - helpers: true, - regenerator: true, - corejs: false, - }, - ], - ], + "presets": [ + ["babel-preset-gatsby"] + ] } diff --git a/packages/gatsby/cache-dir/__tests__/error-overlay-handler.js b/packages/gatsby/cache-dir/__tests__/error-overlay-handler.js new file mode 100644 index 0000000000000..088793742e9e4 --- /dev/null +++ b/packages/gatsby/cache-dir/__tests__/error-overlay-handler.js @@ -0,0 +1,59 @@ +import "@babel/polyfill" +const { + reportError, + clearError, + errorMap, +} = require(`../error-overlay-handler`) + +import * as ErrorOverlay from "react-error-overlay" + +jest.mock(`react-error-overlay`, () => { + return { + reportBuildError: jest.fn(), + dismissBuildError: jest.fn(), + startReportingRuntimeErrors: jest.fn(), + setEditorHandler: jest.fn(), + } +}) + +beforeEach(() => { + ErrorOverlay.reportBuildError.mockClear() + ErrorOverlay.dismissBuildError.mockClear() +}) + +describe(`errorOverlayHandler`, () => { + describe(`clearError()`, () => { + beforeEach(() => { + reportError(`foo`, `error`) + reportError(`bar`, `error`) + }) + afterAll(() => { + clearError(`foo`) + clearError(`bar`) + }) + it(`should clear specific error type`, () => { + expect(Object.keys(errorMap)).toHaveLength(2) + clearError(`foo`) + expect(Object.keys(errorMap)).toHaveLength(1) + expect(ErrorOverlay.dismissBuildError).not.toHaveBeenCalled() + }) + + it(`should call ErrorOverlay to dismiss build errors`, () => { + clearError(`foo`) + clearError(`bar`) + expect(ErrorOverlay.dismissBuildError).toHaveBeenCalled() + }) + }) + describe(`reportErrorOverlay()`, () => { + it(`should not add error if it's empty and not call ErrorOverlay`, () => { + reportError(`foo`, null) + expect(Object.keys(errorMap)).toHaveLength(0) + expect(ErrorOverlay.reportBuildError).not.toHaveBeenCalled() + }) + it(`should add error if it has a truthy value and call ErrorOverlay`, () => { + reportError(`foo`, `bar`) + expect(Object.keys(errorMap)).toHaveLength(1) + expect(ErrorOverlay.reportBuildError).toHaveBeenCalled() + }) + }) +}) diff --git a/packages/gatsby/cache-dir/__tests__/minimal-config.js b/packages/gatsby/cache-dir/__tests__/minimal-config.js index e81b3421a7c2e..7f7f7c9d59529 100644 --- a/packages/gatsby/cache-dir/__tests__/minimal-config.js +++ b/packages/gatsby/cache-dir/__tests__/minimal-config.js @@ -9,6 +9,8 @@ it( path.join(__dirname, `..`), `--config-file`, path.join(__dirname, `.babelrc`), + `--ignore`, + `**/__tests__`, ] const spawn = child.spawn(process.execPath, args) diff --git a/packages/gatsby/cache-dir/error-overlay-handler.js b/packages/gatsby/cache-dir/error-overlay-handler.js new file mode 100644 index 0000000000000..038156789e0f0 --- /dev/null +++ b/packages/gatsby/cache-dir/error-overlay-handler.js @@ -0,0 +1,41 @@ +import * as ErrorOverlay from "react-error-overlay" + +// Report runtime errors +ErrorOverlay.startReportingRuntimeErrors({ + onError: () => {}, + filename: `/commons.js`, +}) +ErrorOverlay.setEditorHandler(errorLocation => + window.fetch( + `/__open-stack-frame-in-editor?fileName=` + + window.encodeURIComponent(errorLocation.fileName) + + `&lineNumber=` + + window.encodeURIComponent(errorLocation.lineNumber || 1) + ) +) + +const errorMap = {} + +const handleErrorOverlay = () => { + const errors = Object.values(errorMap) + if (errors.length > 0) { + const errorMsg = errors.join(`\n\n`) + ErrorOverlay.reportBuildError(errorMsg) + } else { + ErrorOverlay.dismissBuildError() + } +} + +export const clearError = errorID => { + delete errorMap[errorID] + handleErrorOverlay() +} + +export const reportError = (errorID, error) => { + if (error) { + errorMap[errorID] = error + } + handleErrorOverlay() +} + +export { errorMap } diff --git a/packages/gatsby/cache-dir/root.js b/packages/gatsby/cache-dir/root.js index 40e4e6d87d81e..927c97add7841 100644 --- a/packages/gatsby/cache-dir/root.js +++ b/packages/gatsby/cache-dir/root.js @@ -13,34 +13,21 @@ import loader from "./loader" import JSONStore from "./json-store" import EnsureResources from "./ensure-resources" -import * as ErrorOverlay from "react-error-overlay" - -// Report runtime errors -ErrorOverlay.startReportingRuntimeErrors({ - onError: () => {}, - filename: `/commons.js`, -}) -ErrorOverlay.setEditorHandler(errorLocation => - window.fetch( - `/__open-stack-frame-in-editor?fileName=` + - window.encodeURIComponent(errorLocation.fileName) + - `&lineNumber=` + - window.encodeURIComponent(errorLocation.lineNumber || 1) - ) -) +import { reportError, clearError } from "./error-overlay-handler" if (window.__webpack_hot_middleware_reporter__ !== undefined) { + const overlayErrorID = `webpack` // Report build errors window.__webpack_hot_middleware_reporter__.useCustomOverlay({ showProblems(type, obj) { if (type !== `errors`) { - ErrorOverlay.dismissBuildError() + clearError(overlayErrorID) return } - ErrorOverlay.reportBuildError(obj[0]) + reportError(overlayErrorID, obj[0]) }, clear() { - ErrorOverlay.dismissBuildError() + clearError(overlayErrorID) }, }) } diff --git a/packages/gatsby/cache-dir/socketIo.js b/packages/gatsby/cache-dir/socketIo.js index 799b08cb2abe7..978f7db8966d1 100644 --- a/packages/gatsby/cache-dir/socketIo.js +++ b/packages/gatsby/cache-dir/socketIo.js @@ -1,3 +1,5 @@ +import { reportError, clearError } from "./error-overlay-handler" + let socket = null let staticQueryData = {} @@ -29,14 +31,19 @@ export default function socketIo() { [msg.payload.id]: msg.payload.result, } } - } - if (msg.type === `pageQueryResult`) { + } else if (msg.type === `pageQueryResult`) { if (didDataChange(msg, pageQueryData)) { pageQueryData = { ...pageQueryData, [msg.payload.id]: msg.payload.result, } } + } else if (msg.type === `overlayError`) { + if (msg.payload.message) { + reportError(msg.payload.id, msg.payload.message) + } else { + clearError(msg.payload.id) + } } if (msg.type && msg.payload) { ___emitter.emit(msg.type, msg.payload) diff --git a/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js b/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js index 6821940280239..e37e85e3829ab 100644 --- a/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js +++ b/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js @@ -20,6 +20,7 @@ import { multipleRootQueriesError, } from "./graphql-errors" import report from "gatsby-cli/lib/reporter" +const websocketManager = require(`../../utils/websocket-manager`) import type { DocumentNode, GraphQLSchema } from "graphql" @@ -60,9 +61,13 @@ const validationRules = [ VariablesInAllowedPositionRule, ] +let lastRunHadErrors = null +const overlayErrorID = `graphql-compiler` + class Runner { baseDir: string schema: GraphQLSchema + errors: string[] fragmentsDir: string constructor(baseDir: string, fragmentsDir: string, schema: GraphQLSchema) { @@ -72,10 +77,11 @@ class Runner { } reportError(message) { - if (process.env.NODE_ENV === `production`) { - report.panic(`${report.format.red(`GraphQL Error`)} ${message}`) - } else { - report.log(`${report.format.red(`GraphQL Error`)} ${message}`) + const queryErrorMessage = `${report.format.red(`GraphQL Error`)} ${message}` + report.panicOnBuild(queryErrorMessage) + if (process.env.gatsby_executing_command === `develop`) { + websocketManager.emitError(overlayErrorID, queryErrorMessage) + lastRunHadErrors = true } } @@ -200,6 +206,14 @@ class Runner { compiledNodes.set(filePath, query) }) + if ( + process.env.gatsby_executing_command === `develop` && + lastRunHadErrors + ) { + websocketManager.emitError(overlayErrorID, null) + lastRunHadErrors = false + } + return compiledNodes } } diff --git a/packages/gatsby/src/internal-plugins/query-runner/query-runner.js b/packages/gatsby/src/internal-plugins/query-runner/query-runner.js index c8b40a52b4118..1e3197efc43f4 100644 --- a/packages/gatsby/src/internal-plugins/query-runner/query-runner.js +++ b/packages/gatsby/src/internal-plugins/query-runner/query-runner.js @@ -32,7 +32,6 @@ module.exports = async (queryJob: QueryJob, component: Any) => { // Run query let result - // Nothing to do if the query doesn't exist. if (!queryJob.query || queryJob.query === ``) { result = {} @@ -97,9 +96,7 @@ ${formatErrorDetails(errorDetails)}`) dataPath = queryJob.hash } - const programType = program._[0] - - if (programType === `develop`) { + if (process.env.gatsby_executing_command === `develop`) { if (queryJob.isPage) { websocketManager.emitPageData({ result, diff --git a/packages/gatsby/src/utils/websocket-manager.js b/packages/gatsby/src/utils/websocket-manager.js index 4441d2a7cf825..483b063593aac 100644 --- a/packages/gatsby/src/utils/websocket-manager.js +++ b/packages/gatsby/src/utils/websocket-manager.js @@ -92,6 +92,7 @@ const getRoomNameFromPath = (path: string): string => `path-${path}` class WebsocketManager { pageResults: QueryResultsMap staticQueryResults: QueryResultsMap + errors: Map isInitialised: boolean activePaths: Set programDir: string @@ -101,6 +102,7 @@ class WebsocketManager { this.activePaths = new Set() this.pageResults = new Map() this.staticQueryResults = new Map() + this.errors = new Map() this.websocket this.programDir @@ -108,6 +110,7 @@ class WebsocketManager { this.getSocket = this.getSocket.bind(this) this.emitPageData = this.emitPageData.bind(this) this.emitStaticQueryData = this.emitStaticQueryData.bind(this) + this.emitError = this.emitError.bind(this) } init({ server, directory }) { @@ -133,6 +136,15 @@ class WebsocketManager { payload: result, }) }) + this.errors.forEach((message, errorID) => { + this.websocket.send({ + type: `overlayError`, + payload: { + id: errorID, + message, + }, + }) + }) const leaveRoom = path => { s.leave(getRoomNameFromPath(path)) @@ -194,10 +206,21 @@ class WebsocketManager { } emitPageData(data: QueryResult) { + this.pageResults.set(data.id, data) if (this.isInitialised) { this.websocket.send({ type: `pageQueryResult`, payload: data }) } - this.pageResults.set(data.id, data) + } + emitError(id: string, message?: string) { + if (message) { + this.errors.set(id, message) + } else { + this.errors.delete(id) + } + + if (this.isInitialised) { + this.websocket.send({ type: `overlayError`, payload: { id, message } }) + } } }