Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 331fed8

Browse files
timneutkensstyflekodiakhq[bot]
authored
Add Import trace for requested module when it fails to resolve (vercel#27840)
Co-authored-by: Steven <[email protected]> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 0608f49 commit 331fed8

File tree

3 files changed

+295
-15
lines changed

3 files changed

+295
-15
lines changed

errors/module-not-found.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Module Not Found
2+
3+
#### Why This Error Occurred
4+
5+
A module not found error can occur for many different reasons:
6+
7+
- The module you're trying to import is not installed in your dependencies
8+
- The module you're trying to import is in a different directory
9+
- The module you're trying to import has a different casing
10+
- The module you're trying to import uses Node.js specific modules, for example `dns`, outside of `getStaticProps` / `getStaticPaths` / `getServerSideProps`
11+
12+
#### Possible Ways to Fix It
13+
14+
##### The module you're trying to import is not installed in your dependencies
15+
16+
When importing a module from [npm](https://npmjs.com) this module has to be installed locally.
17+
18+
For example when importing the `swr` package:
19+
20+
```js
21+
import useSWR from 'swr'
22+
```
23+
24+
The `swr` module has to be installed using a package manager.
25+
26+
- When using `npm`: `npm install swr`
27+
- When using `yarn`: `yarn add swr`
28+
29+
##### The module you're trying to import is in a different directory
30+
31+
Make sure that the path you're importing refers to the right directory and file.
32+
33+
##### The module you're trying to import has a different casing
34+
35+
Make sure the casing of the file is correct.
36+
37+
Example:
38+
39+
```js
40+
// components/MyComponent.js
41+
export default function MyComponent() {
42+
return <h1>Hello</h1>
43+
}
44+
```
45+
46+
```js
47+
// pages/index.js
48+
// Note how `components/MyComponent` exists but `Mycomponent` without the capital `c` is imported
49+
import MyComponent from '../components/Mycomponent'
50+
```
51+
52+
Incorrect casing will lead to build failures on case-sensitive environments like most Linux-based continuous integration and can cause issues with Fast Refresh.
53+
54+
##### The module you're trying to import uses Node.js specific modules
55+
56+
`getStaticProps`, `getStaticPaths`, and `getServerSideProps` allow for using modules that can only run in the Node.js environment. This allows you to do direct database queries or reading data from Redis to name a few examples.
57+
58+
The tree shaking only runs on top level pages, so it can't be relied on in separate React components.
59+
60+
You can verify the tree shaking on [next-code-elimination.vercel.app](https://next-code-elimination.vercel.app/).
61+
62+
Example of correctly tree shaken code:
63+
64+
```js
65+
// lib/redis.js
66+
import Redis from 'ioredis'
67+
68+
const redis = new Redis(process.env.REDIS_URL)
69+
70+
export default redis
71+
```
72+
73+
```js
74+
// pages/index.js
75+
import redis from '../lib/redis'
76+
77+
export async function getStaticProps() {
78+
const message = await redis.get('message')
79+
return {
80+
message,
81+
}
82+
}
83+
84+
export default function Home({ message }) {
85+
return <h1>{message}</h1>
86+
}
87+
```
88+
89+
Example of code that would break:
90+
91+
```js
92+
// lib/redis.js
93+
import Redis from 'ioredis'
94+
95+
const redis = new Redis(process.env.REDIS_URL)
96+
97+
export default redis
98+
```
99+
100+
```js
101+
// pages/index.js
102+
// Redis is a Node.js specific library that can't run in the browser
103+
// Trying to use it in code that runs on both Node.js and the browser will result in a module not found error for modules that ioredis relies on
104+
// If you run into such an error it's recommended to move the code to `getStaticProps` or `getServerSideProps` as those methods guarantee that the code is only run in Node.js.
105+
import redis from '../lib/redis'
106+
import { useEffect, useState } from 'react'
107+
108+
export default function Home() {
109+
const [message, setMessage] = useState()
110+
useEffect(() => {
111+
redis.get('message').then((result) => {
112+
setMessage(result)
113+
})
114+
}, [])
115+
return <h1>{message}</h1>
116+
}
117+
```
118+
119+
Example of code that would break:
120+
121+
```js
122+
// lib/redis.js
123+
import Redis from 'ioredis'
124+
125+
// Modules that hold Node.js-only code can't also export React components
126+
// Tree shaking of getStaticProps/getStaticPaths/getServerSideProps is ran only on page files
127+
const redis = new Redis(process.env.REDIS_URL)
128+
129+
export function MyComponent() {
130+
return <h1>Hello</h1>
131+
}
132+
133+
export default redis
134+
```
135+
136+
```js
137+
// pages/index.js
138+
// In practice you'll want to refactor the `MyComponent` to be a separate file so that tree shaking ensures that specific import is not included for the browser compilation
139+
import redis, { MyComponent } from '../lib/redis'
140+
141+
export async function getStaticProps() {
142+
const message = await redis.get('message')
143+
return {
144+
message,
145+
}
146+
}
147+
148+
export default function Home() {
149+
return <MyComponent />
150+
}
151+
```

packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,51 @@
11
import Chalk from 'chalk'
22
import { SimpleWebpackError } from './simpleWebpackError'
33
import { createOriginalStackFrame } from '@next/react-dev-overlay/lib/middleware'
4+
import { isWebpack5 } from 'next/dist/compiled/webpack/webpack'
5+
import path from 'path'
46

57
const chalk = new Chalk.constructor({ enabled: true })
68

9+
// Based on https://github.com/webpack/webpack/blob/fcdd04a833943394bbb0a9eeb54a962a24cc7e41/lib/stats/DefaultStatsFactoryPlugin.js#L422-L431
10+
/*
11+
Copyright JS Foundation and other contributors
12+
13+
Permission is hereby granted, free of charge, to any person obtaining
14+
a copy of this software and associated documentation files (the
15+
'Software'), to deal in the Software without restriction, including
16+
without limitation the rights to use, copy, modify, merge, publish,
17+
distribute, sublicense, and/or sell copies of the Software, and to
18+
permit persons to whom the Software is furnished to do so, subject to
19+
the following conditions:
20+
21+
The above copyright notice and this permission notice shall be
22+
included in all copies or substantial portions of the Software.
23+
24+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
25+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
27+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
28+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
29+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
30+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31+
32+
*/
33+
function getModuleTrace(input: any, compilation: any) {
34+
const visitedModules = new Set()
35+
const moduleTrace = []
36+
let current = input.module
37+
while (current) {
38+
if (visitedModules.has(current)) break // circular (technically impossible, but who knows)
39+
visitedModules.add(current)
40+
const origin = compilation.moduleGraph.getIssuer(current)
41+
if (!origin) break
42+
moduleTrace.push({ origin, module: current })
43+
current = origin
44+
}
45+
46+
return moduleTrace
47+
}
48+
749
export async function getNotFoundError(
850
compilation: any,
951
input: any,
@@ -36,11 +78,38 @@ export async function getNotFoundError(
3678
.replace(/ in '.*?'/, '')
3779
.replace(/Can't resolve '(.*)'/, `Can't resolve '${chalk.green('$1')}'`)
3880

81+
const importTrace = () => {
82+
if (!isWebpack5) {
83+
return ''
84+
}
85+
86+
let importTraceLine = '\nImport trace for requested module:\n'
87+
const moduleTrace = getModuleTrace(input, compilation)
88+
89+
for (const { origin } of moduleTrace) {
90+
if (!origin.resource) {
91+
continue
92+
}
93+
const filePath = path.relative(
94+
compilation.options.context,
95+
origin.resource
96+
)
97+
importTraceLine += `./${filePath}\n`
98+
}
99+
100+
return importTraceLine + '\n'
101+
}
102+
103+
const frame = result.originalCodeFrame ?? ''
104+
39105
const message =
40106
chalk.red.bold('Module not found') +
41107
`: ${errorMessage}` +
42108
'\n' +
43-
result.originalCodeFrame
109+
frame +
110+
(frame !== '' ? '\n' : '') +
111+
importTrace() +
112+
'\nhttps://nextjs.org/docs/messages/module-not-found'
44113

45114
return new SimpleWebpackError(
46115
`${chalk.cyan(fileName)}:${chalk.yellow(

test/acceptance/ReactRefreshLogBox.dev.test.js

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -705,12 +705,14 @@ test('unterminated JSX', async () => {
705705
await cleanup()
706706
})
707707

708-
test('Module not found', async () => {
709-
const [session, cleanup] = await sandbox()
710-
711-
await session.patch(
712-
'index.js',
713-
`import Comp from 'b'
708+
// Module trace is only available with webpack 5
709+
if (!process.env.NEXT_PRIVATE_TEST_WEBPACK4_MODE) {
710+
test('Module not found', async () => {
711+
const [session, cleanup] = await sandbox()
712+
713+
await session.patch(
714+
'index.js',
715+
`import Comp from 'b'
714716
export default function Oops() {
715717
return (
716718
<div>
@@ -719,23 +721,28 @@ test('Module not found', async () => {
719721
)
720722
}
721723
`
722-
)
724+
)
723725

724-
expect(await session.hasRedbox(true)).toBe(true)
726+
expect(await session.hasRedbox(true)).toBe(true)
725727

726-
const source = await session.getRedboxSource()
727-
expect(source).toMatchInlineSnapshot(`
728+
const source = await session.getRedboxSource()
729+
expect(source).toMatchInlineSnapshot(`
728730
"./index.js:1:0
729731
Module not found: Can't resolve 'b'
730732
> 1 | import Comp from 'b'
731733
2 | export default function Oops() {
732734
3 | return (
733-
4 | <div>"
734-
`)
735+
4 | <div>
736+
737+
Import trace for requested module:
738+
./pages/index.js
735739
736-
await cleanup()
737-
})
740+
https://nextjs.org/docs/messages/module-not-found"
741+
`)
738742

743+
await cleanup()
744+
})
745+
}
739746
test('conversion to class component (1)', async () => {
740747
const [session, cleanup] = await sandbox()
741748

@@ -1634,3 +1641,56 @@ test('_document syntax error shows logbox', async () => {
16341641
expect(await session.hasRedbox()).toBe(false)
16351642
await cleanup()
16361643
})
1644+
1645+
// Module trace is only available with webpack 5
1646+
if (!process.env.NEXT_PRIVATE_TEST_WEBPACK4_MODE) {
1647+
test('Node.js builtins', async () => {
1648+
const [session, cleanup] = await sandbox(
1649+
undefined,
1650+
new Map([
1651+
[
1652+
'node_modules/my-package/index.js',
1653+
`
1654+
const dns = require('dns')
1655+
module.exports = dns
1656+
`,
1657+
],
1658+
[
1659+
'node_modules/my-package/package.json',
1660+
`
1661+
{
1662+
"name": "my-package",
1663+
"version": "0.0.1"
1664+
}
1665+
`,
1666+
],
1667+
]),
1668+
undefined,
1669+
/ready - started server on/i
1670+
)
1671+
1672+
await session.patch(
1673+
'index.js',
1674+
`
1675+
import pkg from 'my-package'
1676+
1677+
export default function Hello() {
1678+
return (pkg ? <h1>Package loaded</h1> : <h1>Package did not load</h1>)
1679+
}
1680+
`
1681+
)
1682+
expect(await session.hasRedbox(true)).toBe(true)
1683+
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
1684+
"./node_modules/my-package/index.js:2:0
1685+
Module not found: Can't resolve 'dns'
1686+
1687+
Import trace for requested module:
1688+
./index.js
1689+
./pages/index.js
1690+
1691+
https://nextjs.org/docs/messages/module-not-found"
1692+
`)
1693+
1694+
await cleanup()
1695+
})
1696+
}

0 commit comments

Comments
 (0)