From 11816537c33cbaf06f2cf4f046c1a7ddbcc1b67a Mon Sep 17 00:00:00 2001 From: HaNdTriX Date: Fri, 24 Aug 2018 16:30:41 +0200 Subject: [PATCH] Open editor from error-overlay (minor) (#4979) This PR adds links to the [react-error-overlay](https://www.npmjs.com/package/react-error-overlay). This allows a developer to open a stack trace in its own editor. ![codelinking](https://user-images.githubusercontent.com/1265681/44278860-a63e0a80-a24f-11e8-9c69-c5365c026c58.gif) Closes #4813 --- client/dev-error-overlay/hot-dev-client.js | 12 +++++++- package.json | 1 + server/hot-reloader.js | 2 ++ server/lib/error-overlay-middleware.js | 14 +++++++++ test/integration/basic/test/error-recovery.js | 29 ++++++++++++++++++ test/unit/loadGetInitialProps.test.js | 2 +- yarn.lock | 30 ++++++++++++++++++- 7 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 server/lib/error-overlay-middleware.js diff --git a/client/dev-error-overlay/hot-dev-client.js b/client/dev-error-overlay/hot-dev-client.js index 1a3f56b8..29b65e74 100644 --- a/client/dev-error-overlay/hot-dev-client.js +++ b/client/dev-error-overlay/hot-dev-client.js @@ -6,9 +6,9 @@ import {getEventSourceWrapper} from './eventsource' import formatWebpackMessages from './format-webpack-messages' import * as ErrorOverlay from 'react-error-overlay' -// import url from 'url' import stripAnsi from 'strip-ansi' import {rewriteStacktrace} from '../source-map-support' +import fetch from 'unfetch' const { distDir @@ -30,6 +30,16 @@ const { let hadRuntimeError = false let customHmrEventHandler export default function connect (options) { + // Open stack traces in an editor. + ErrorOverlay.setEditorHandler(function editorHandler ({ fileName, lineNumber, colNumber }) { + fetch( + '/_next/development/open-stack-frame-in-editor' + + `?fileName=${window.encodeURIComponent(fileName)}` + + `&lineNumber=${lineNumber || 1}` + + `&colNumber=${colNumber || 1}` + ) + }) + // We need to keep track of if there has been a runtime error. // Essentially, we cannot guarantee application state was not corrupted by the // runtime error. To prevent confusing behavior, we forcibly reload the entire diff --git a/package.json b/package.json index 360b772a..7c5e5cf8 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "htmlescape": "1.1.1", "http-errors": "1.6.2", "http-status": "1.0.1", + "launch-editor": "2.2.1", "loader-utils": "1.1.0", "minimist": "1.2.0", "mkdirp-then": "1.2.0", diff --git a/server/hot-reloader.js b/server/hot-reloader.js index 21d9c228..9f9d06b3 100644 --- a/server/hot-reloader.js +++ b/server/hot-reloader.js @@ -1,6 +1,7 @@ import { join, relative, sep, normalize } from 'path' import WebpackDevMiddleware from 'webpack-dev-middleware' import WebpackHotMiddleware from 'webpack-hot-middleware' +import errorOverlayMiddleware from './lib/error-overlay-middleware' import del from 'del' import onDemandEntryHandler, {normalizePage} from './on-demand-entry-handler' import webpack from 'webpack' @@ -186,6 +187,7 @@ export default class HotReloader { this.middlewares = [ webpackDevMiddleware, webpackHotMiddleware, + errorOverlayMiddleware, onDemandEntries.middleware() ] } diff --git a/server/lib/error-overlay-middleware.js b/server/lib/error-overlay-middleware.js new file mode 100644 index 00000000..bd790a12 --- /dev/null +++ b/server/lib/error-overlay-middleware.js @@ -0,0 +1,14 @@ +import url from 'url' +import launchEditor from 'launch-editor' + +export default function errorOverlayMiddleware (req, res, next) { + if (req.url.startsWith('/_next/development/open-stack-frame-in-editor')) { + const query = url.parse(req.url, true).query + const lineNumber = parseInt(query.lineNumber, 10) || 1 + const colNumber = parseInt(query.colNumber, 10) || 1 + launchEditor(`${query.fileName}:${lineNumber}:${colNumber}`) + res.end() + } else { + next() + } +} diff --git a/test/integration/basic/test/error-recovery.js b/test/integration/basic/test/error-recovery.js index a96a9839..12a9da06 100644 --- a/test/integration/basic/test/error-recovery.js +++ b/test/integration/basic/test/error-recovery.js @@ -5,6 +5,35 @@ import { check, File, waitFor, getReactErrorOverlayContent } from 'next-test-uti export default (context, render) => { describe('Error Recovery', () => { + it('should have installed the react-overlay-editor editor handler', async () => { + let browser + const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js')) + aboutPage.replace('', 'div') + + try { + browser = await webdriver(context.appPort, '/hmr/about') + + // Wait for react-error-overlay + await browser.waitForElementByCss('iframe', 2000) + + // react-error-overlay uses the following inline style if an editorHandler is installed + expect(await getReactErrorOverlayContent(browser)).toMatch(/style="cursor: pointer;"/) + + aboutPage.restore() + + await check( + () => browser.elementByCss('body').text(), + /This is the about page/ + ) + } finally { + aboutPage.restore() + + if (browser) { + browser.close() + } + } + }) + it('should detect syntax errors and recover', async () => { let browser const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js')) diff --git a/test/unit/loadGetInitialProps.test.js b/test/unit/loadGetInitialProps.test.js index b878e38b..0c3087a2 100644 --- a/test/unit/loadGetInitialProps.test.js +++ b/test/unit/loadGetInitialProps.test.js @@ -7,7 +7,7 @@ describe('loadGetInitialProps', () => { getInitialProps () {} } const rejectPromise = loadGetInitialProps(TestComponent, {}) - const error = new Error('"TestComponent.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-inital-props-as-an-instance-method for more information.') + const error = new Error('"TestComponent.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.') return expect(rejectPromise).rejects.toEqual(error) }) diff --git a/yarn.lock b/yarn.lock index 19427045..7c07ae25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1074,6 +1074,10 @@ array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" +array-filter@~0.0.0: + version "0.0.1" + resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" + array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -1089,6 +1093,14 @@ array-includes@^3.0.3: define-properties "^1.1.2" es-abstract "^1.7.0" +array-map@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" + +array-reduce@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" + array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -1778,7 +1790,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.0, chalk@^2.4.1: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.0, chalk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: @@ -4693,6 +4705,13 @@ kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" +launch-editor@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.2.1.tgz#871b5a3ee39d6680fcc26d37930b6eeda89db0ca" + dependencies: + chalk "^2.3.0" + shell-quote "^1.6.1" + lazy-cache@^0.2.3: version "0.2.7" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" @@ -7058,6 +7077,15 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" +shell-quote@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" + dependencies: + array-filter "~0.0.0" + array-map "~0.0.0" + array-reduce "~0.0.0" + jsonify "~0.0.0" + shellwords@^0.1.0, shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"