Skip to content

Commit

Permalink
Add editor links to module import traces (#45257)
Browse files Browse the repository at this point in the history
Makes it possible to open the files in module import traces in your editor by clicking them.

Module not found:
![image](https://user-images.githubusercontent.com/25056922/214539916-a776fa6f-7645-49c8-9106-a2abd62a8447.png)

Parse error:
![image](https://user-images.githubusercontent.com/25056922/214539987-0e3fd312-7ade-44fc-a334-b95d73320775.png)

Fixes NEXT-422

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm build && pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
  • Loading branch information
hanneslund committed Feb 4, 2023
1 parent 0d7f765 commit e3ed2f2
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 67 deletions.
Expand Up @@ -116,9 +116,7 @@ export async function getNotFoundError(
)
if (moduleTrace.length === 0) return ''

return `\nImport trace for requested module:\n${moduleTrace.join(
'\n'
)}\n\n`
return `\nImport trace for requested module:\n${moduleTrace.join('\n')}`
}

let message =
Expand All @@ -127,8 +125,8 @@ export async function getNotFoundError(
'\n' +
frame +
(frame !== '' ? '\n' : '') +
importTrace() +
'\nhttps://nextjs.org/docs/messages/module-not-found'
'\nhttps://nextjs.org/docs/messages/module-not-found\n' +
importTrace()

let formattedFileName: string = chalk.cyan(fileName)
if (lineNumber && column) {
Expand Down
Expand Up @@ -5,13 +5,19 @@ import { EditorLink } from './EditorLink'
export type TerminalProps = { content: string }

function getImportTraceFiles(content: string): [string, string[]] {
if (/ReactServerComponentsError:/.test(content)) {
if (
/ReactServerComponentsError:/.test(content) ||
/Import trace for requested module:/.test(content)
) {
// It's an RSC Build Error
const lines = content.split('\n')

// Grab the lines at the end containing the files
const files = []
while (/app\/.+\./.test(lines[lines.length - 1])) {
while (
/.+\..+/.test(lines[lines.length - 1]) &&
!lines[lines.length - 1].includes(':')
) {
const file = lines.pop()!.trim()
files.unshift(file)
}
Expand Down
Expand Up @@ -8,10 +8,10 @@ Module not found: Can't resolve 'b'
3 | return (
4 | <div>
Import trace for requested module:
./app/page.js
https://nextjs.org/docs/messages/module-not-found
https://nextjs.org/docs/messages/module-not-found"
Import trace for requested module:
./app/page.js"
`;
exports[`ReactRefreshLogBox app default Module not found empty import trace 1`] = `
Expand Down Expand Up @@ -44,9 +44,9 @@ exports[`ReactRefreshLogBox app default Node.js builtins 1`] = `
"./node_modules/my-package/index.js:2:0
Module not found: Can't resolve 'dns'
https://nextjs.org/docs/messages/module-not-found
Import trace for requested module:
./index.js
./app/page.js
https://nextjs.org/docs/messages/module-not-found"
./app/page.js"
`;
Expand Up @@ -4,21 +4,21 @@ exports[`ReactRefreshLogBox app default Can't resolve @import in CSS file 1`] =
"./app/styles2.css
Module not found: Can't resolve './boom.css'
Import trace for requested module:
./app/styles1.css
https://nextjs.org/docs/messages/module-not-found
https://nextjs.org/docs/messages/module-not-found"
Import trace for requested module:
./app/styles1.css"
`;

exports[`ReactRefreshLogBox app default Import trace when module not found in layout 1`] = `
"./app/module.js:1:0
Module not found: Can't resolve 'non-existing-module'
> 1 | import \\"non-existing-module\\"
Import trace for requested module:
./app/layout.js
https://nextjs.org/docs/messages/module-not-found
https://nextjs.org/docs/messages/module-not-found"
Import trace for requested module:
./app/layout.js"
`;

exports[`ReactRefreshLogBox app default Should not show __webpack_exports__ when exporting anonymous arrow function 1`] = `
Expand Down
148 changes: 148 additions & 0 deletions test/development/acceptance-app/editor-links.test.ts
@@ -0,0 +1,148 @@
import { check } from 'next-test-utils'
import { createNextDescribe, FileRef } from 'e2e-utils'
import path from 'path'
import { sandbox } from './helpers'

async function clickEditorLinks(browser: any) {
await browser.waitForElementByCss('[data-with-open-in-editor-link]')
const collapsedFrameworkGroups = await browser.elementsByCss(
'[data-with-open-in-editor-link]'
)
for (const collapsedFrameworkButton of collapsedFrameworkGroups) {
await collapsedFrameworkButton.click()
}
}

createNextDescribe(
'Error overlay - editor links',
{
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
skipStart: true,
},
({ next }) => {
it('should be possible to open files on RSC build error', async () => {
let editorRequestsCount = 0
const { session, browser, cleanup } = await sandbox(
next,
new Map([
[
'app/page.js',
`
import Component from '../index'
export default function Page() {
return <Component />
}
`,
],
]),
undefined,
{
beforePageLoad(page) {
page.route('**/__nextjs_launch-editor**', (route) => {
editorRequestsCount += 1
route.fulfill()
})
},
}
)

await session.patch(
'index.js',
`import { useState } from 'react'
export default () => 'hello world'`
)

expect(await session.hasRedbox(true)).toBe(true)
await clickEditorLinks(browser)
await check(() => editorRequestsCount, /2/)

await cleanup()
})

it('should be possible to open files on RSC parse error', async () => {
let editorRequestsCount = 0
const { session, browser, cleanup } = await sandbox(
next,
new Map([
[
'app/page.js',
`
import Component from '../index'
export default function Page() {
return <Component />
}
`,
],
['mod1.js', "import './mod2.js'"],
['mod2.js', '{{{{{'],
]),
undefined,
{
beforePageLoad(page) {
page.route('**/__nextjs_launch-editor**', (route) => {
editorRequestsCount += 1
route.fulfill()
})
},
}
)

await session.patch(
'index.js',
`import './mod1'
export default () => 'hello world'`
)

expect(await session.hasRedbox(true)).toBe(true)
await clickEditorLinks(browser)
await check(() => editorRequestsCount, /4/)

await cleanup()
})

it('should be possible to open files on module not found error', async () => {
let editorRequestsCount = 0
const { session, browser, cleanup } = await sandbox(
next,
new Map([
[
'app/page.js',
`
import Component from '../index'
export default function Page() {
return <Component />
}
`,
],
['mod1.js', "import './mod2.js'"],
['mod2.js', 'import "boom"'],
]),
undefined,
{
beforePageLoad(page) {
page.route('**/__nextjs_launch-editor**', (route) => {
editorRequestsCount += 1
route.fulfill()
})
},
}
)

await session.patch(
'index.js',
`import './mod1'
export default () => 'hello world'`
)

expect(await session.hasRedbox(true)).toBe(true)
await clickEditorLinks(browser)
await check(() => editorRequestsCount, /3/)

await cleanup()
})
}
)
42 changes: 0 additions & 42 deletions test/development/acceptance-app/rsc-build-errors.test.ts
@@ -1,4 +1,3 @@
import { check } from 'next-test-utils'
import { createNextDescribe, FileRef } from 'e2e-utils'
import path from 'path'
import { sandbox } from './helpers'
Expand Down Expand Up @@ -267,46 +266,5 @@ createNextDescribe(

await cleanup()
})

it('should be possible to open the import trace files in your editor', async () => {
let editorRequestsCount = 0
const { session, browser, cleanup } = await sandbox(
next,
undefined,
'/editor-links',
{
beforePageLoad(page) {
page.route('**/__nextjs_launch-editor**', (route) => {
editorRequestsCount += 1
route.fulfill()
})
},
}
)

const componentFile = 'app/editor-links/component.js'
const fileContent = await next.readFile(componentFile)

await session.patch(
componentFile,
fileContent.replace(
"// import { useState } from 'react'",
"import { useState } from 'react'"
)
)

expect(await session.hasRedbox(true)).toBe(true)
await browser.waitForElementByCss('[data-with-open-in-editor-link]')
const collapsedFrameworkGroups = await browser.elementsByCss(
'[data-with-open-in-editor-link]'
)
for (const collapsedFrameworkButton of collapsedFrameworkGroups) {
await collapsedFrameworkButton.click()
}

await check(() => editorRequestsCount, /2/)

await cleanup()
})
}
)
Expand Up @@ -32,19 +32,19 @@ Module not found: Can't resolve 'b'
3 | return (
4 | <div>
Import trace for requested module:
./pages/index.js
https://nextjs.org/docs/messages/module-not-found
https://nextjs.org/docs/messages/module-not-found"
Import trace for requested module:
./pages/index.js"
`;
exports[`ReactRefreshLogBox default Node.js builtins 1`] = `
"./node_modules/my-package/index.js:2:0
Module not found: Can't resolve 'dns'
https://nextjs.org/docs/messages/module-not-found
Import trace for requested module:
./index.js
./pages/index.js
https://nextjs.org/docs/messages/module-not-found"
./pages/index.js"
`;

0 comments on commit e3ed2f2

Please sign in to comment.