[Fast Refresh] Click to open in editor (#12397)
This commit is contained in:
parent
eab41abada
commit
37e3ec8d17
7 changed files with 581 additions and 6 deletions
|
@ -18,7 +18,9 @@
|
|||
"dependencies": {
|
||||
"@babel/code-frame": "7.8.3",
|
||||
"anser": "1.4.9",
|
||||
"chalk": "4.0.0",
|
||||
"classnames": "2.2.6",
|
||||
"shell-quote": "1.7.2",
|
||||
"source-map": "0.8.0-beta.0",
|
||||
"stacktrace-parser": "0.1.9",
|
||||
"strip-ansi": "6.0.0"
|
||||
|
|
|
@ -40,11 +40,40 @@ export const CodeFrame: React.FC<CodeFrameProps> = function CodeFrame({
|
|||
})
|
||||
}, [formattedFrame])
|
||||
|
||||
const open = React.useCallback(() => {
|
||||
const params = new URLSearchParams()
|
||||
for (const key in stackFrame) {
|
||||
params.append(key, (stackFrame[key] ?? '').toString())
|
||||
}
|
||||
|
||||
self.fetch(`/__nextjs_launch-editor?${params.toString()}`).then(
|
||||
() => {},
|
||||
() => {
|
||||
// TODO: report error
|
||||
}
|
||||
)
|
||||
}, [stackFrame])
|
||||
|
||||
// TODO: make the caret absolute
|
||||
return (
|
||||
<div data-nextjs-codeframe>
|
||||
<p>
|
||||
{getFrameSource(stackFrame)} @ {stackFrame.methodName}
|
||||
<p role="link" onClick={open} tabIndex={0}>
|
||||
<span>
|
||||
{getFrameSource(stackFrame)} @ {stackFrame.methodName}
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
</p>
|
||||
<hr />
|
||||
<pre>
|
||||
|
|
|
@ -33,6 +33,21 @@ const styles = css`
|
|||
border-bottom-width: 1px;
|
||||
border-color: var(--color-ansi-fg);
|
||||
}
|
||||
|
||||
[data-nextjs-codeframe] > p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-nextjs-codeframe] > p:hover {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
[data-nextjs-codeframe] > p > svg {
|
||||
width: auto;
|
||||
height: 1em;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
`
|
||||
|
||||
export { styles }
|
||||
|
|
|
@ -14,10 +14,48 @@ const CallStackFrame: React.FC<{
|
|||
// TODO: render error or external indicator
|
||||
|
||||
const f: StackFrame = frame.originalStackFrame ?? frame.sourceStackFrame
|
||||
const hasSource = Boolean(frame.originalStackFrame?.file)
|
||||
|
||||
const open = React.useCallback(() => {
|
||||
if (!hasSource) return
|
||||
|
||||
const params = new URLSearchParams()
|
||||
for (const key in f) {
|
||||
params.append(key, (f[key] ?? '').toString())
|
||||
}
|
||||
|
||||
self.fetch(`/__nextjs_launch-editor?${params.toString()}`).then(
|
||||
() => {},
|
||||
() => {
|
||||
// TODO: report error
|
||||
}
|
||||
)
|
||||
}, [hasSource, f])
|
||||
|
||||
return (
|
||||
<div data-nextjs-call-stack-frame>
|
||||
<h6>{f.methodName}</h6>
|
||||
<p>{getFrameSource(f)}</p>
|
||||
<div
|
||||
data-has-source={hasSource ? 'true' : undefined}
|
||||
tabIndex={hasSource ? 0 : undefined}
|
||||
role={hasSource ? 'link' : undefined}
|
||||
onClick={open}
|
||||
>
|
||||
<span>{getFrameSource(f)}</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -98,11 +136,31 @@ export const styles = css`
|
|||
font-family: var(--font-stack-monospace);
|
||||
color: rgba(25, 25, 25, 1);
|
||||
}
|
||||
[data-nextjs-call-stack-frame] > p {
|
||||
[data-nextjs-call-stack-frame] > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
color: rgba(25, 25, 25, 0.5);
|
||||
}
|
||||
[data-nextjs-call-stack-frame] > div > svg {
|
||||
width: auto;
|
||||
height: 0.875rem;
|
||||
margin-left: 0.5rem;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-nextjs-call-stack-frame] > div[data-has-source] {
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-nextjs-call-stack-frame] > div[data-has-source]:hover {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
[data-nextjs-call-stack-frame] > div[data-has-source] > svg {
|
||||
display: unset;
|
||||
}
|
||||
`
|
||||
|
||||
export { RuntimeError }
|
||||
|
|
427
packages/react-dev-overlay/src/internal/helpers/launchEditor.ts
Normal file
427
packages/react-dev-overlay/src/internal/helpers/launchEditor.ts
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
|||
import { codeFrameColumns } from '@babel/code-frame'
|
||||
import { promises as fs } from 'fs'
|
||||
import { constants as FS, promises as fs } from 'fs'
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import path from 'path'
|
||||
import { NullableMappedPosition, SourceMapConsumer } from 'source-map'
|
||||
|
@ -7,8 +7,9 @@ import { StackFrame } from 'stacktrace-parser'
|
|||
import url from 'url'
|
||||
import webpack from 'webpack'
|
||||
import { OriginalSource } from 'webpack-sources'
|
||||
import { launchEditor } from './internal/helpers/launchEditor'
|
||||
|
||||
type OverlayMiddlewareOptions = {
|
||||
export type OverlayMiddlewareOptions = {
|
||||
rootDirectory: string
|
||||
stats(): webpack.Stats
|
||||
}
|
||||
|
@ -122,6 +123,41 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) {
|
|||
res.statusCode = 400
|
||||
res.write('Bad Request')
|
||||
return res.end()
|
||||
} else if (pathname === '/__nextjs_launch-editor') {
|
||||
const frame = (query as unknown) as StackFrame
|
||||
|
||||
const frameFile = frame.file?.toString() || null
|
||||
if (frameFile == null) {
|
||||
res.statusCode = 400
|
||||
res.write('Bad Request')
|
||||
return res.end()
|
||||
}
|
||||
|
||||
const filePath = path.resolve(options.rootDirectory, frameFile)
|
||||
const fileExists = await fs.access(filePath, FS.F_OK).then(
|
||||
() => true,
|
||||
() => false
|
||||
)
|
||||
if (!fileExists) {
|
||||
res.statusCode = 404
|
||||
res.write('Not Found')
|
||||
return res.end()
|
||||
}
|
||||
|
||||
const frameLine = parseInt(frame.lineNumber?.toString() ?? '', 10) || 1
|
||||
const frameColumn = parseInt(frame.column?.toString() ?? '', 10) || 1
|
||||
|
||||
try {
|
||||
await launchEditor(filePath, frameLine, frameColumn)
|
||||
} catch (err) {
|
||||
console.log('Failed to launch editor:', err)
|
||||
res.statusCode = 500
|
||||
res.write('Internal Server Error')
|
||||
return res.end()
|
||||
}
|
||||
|
||||
res.statusCode = 204
|
||||
return res.end()
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
|
|
@ -4443,6 +4443,14 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.
|
|||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72"
|
||||
integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||
|
|
Loading…
Reference in a new issue