Skip to content

Commit 597ade8

Browse files
committed
Merge branch 'canary' into x-use-zen-observable
2 parents 31e2077 + 7ea7c23 commit 597ade8

File tree

10 files changed

+156
-17
lines changed

10 files changed

+156
-17
lines changed

docs/advanced-features/dynamic-import.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,25 @@ function Home() {
156156

157157
export default Home
158158
```
159+
160+
## With suspense
161+
162+
Option `suspense` allows you to lazy-load a component, similar to `React.lazy` and `<Suspense>` with React 18. Note that it only works on client-side or server-side with `fallback`. Full SSR support in concurrent mode is still a work-in-progress.
163+
164+
```jsx
165+
import dynamic from 'next/dynamic'
166+
167+
const DynamicLazyComponent = dynamic(() => import('../components/hello4'), {
168+
suspense: true,
169+
})
170+
171+
function Home() {
172+
return (
173+
<div>
174+
<Suspense fallback={`loading`}>
175+
<DynamicLazyComponent />
176+
</Suspense>
177+
</div>
178+
)
179+
}
180+
```

errors/invalid-dynamic-suspense.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Invalid Usage of `suspense` Option of `next/dynamic`
2+
3+
#### Why This Error Occurred
4+
5+
`<Suspense>` is not allowed under legacy render mode when using React older than v18.
6+
7+
#### Possible Ways to Fix It
8+
9+
Remove `suspense: true` option in `next/dynamic` usages.
10+
11+
### Useful Links
12+
13+
- [Dynamic Import Suspense Usage](https://nextjs.org/docs/advanced-features/dynamic-import#with-suspense)

errors/manifest.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@
151151
"title": "invalid-assetprefix",
152152
"path": "/errors/invalid-assetprefix.md"
153153
},
154+
{
155+
"title": "invalid-dynamic-suspense",
156+
"path": "/errors/invalid-dynamic-suspense.md"
157+
},
154158
{
155159
"title": "invalid-external-rewrite",
156160
"path": "/errors/invalid-external-rewrite.md"
Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { promises as fs } from 'fs'
22
import os from 'os'
33
import path from 'path'
4+
import { fileExists } from '../file-exists'
45

56
export async function writeAppTypeDeclarations(
67
baseDir: string,
@@ -9,19 +10,27 @@ export async function writeAppTypeDeclarations(
910
// Reference `next` types
1011
const appTypeDeclarations = path.join(baseDir, 'next-env.d.ts')
1112

12-
await fs.writeFile(
13-
appTypeDeclarations,
13+
const content =
1414
'/// <reference types="next" />' +
15-
os.EOL +
16-
'/// <reference types="next/types/global" />' +
17-
os.EOL +
18-
(imageImportsEnabled
19-
? '/// <reference types="next/image-types/global" />' + os.EOL
20-
: '') +
21-
os.EOL +
22-
'// NOTE: This file should not be edited' +
23-
os.EOL +
24-
'// see https://nextjs.org/docs/basic-features/typescript for more information.' +
25-
os.EOL
26-
)
15+
os.EOL +
16+
'/// <reference types="next/types/global" />' +
17+
os.EOL +
18+
(imageImportsEnabled
19+
? '/// <reference types="next/image-types/global" />' + os.EOL
20+
: '') +
21+
os.EOL +
22+
'// NOTE: This file should not be edited' +
23+
os.EOL +
24+
'// see https://nextjs.org/docs/basic-features/typescript for more information.' +
25+
os.EOL
26+
27+
// Avoids a write for read-only filesystems
28+
if (
29+
(await fileExists(appTypeDeclarations)) &&
30+
(await fs.readFile(appTypeDeclarations, 'utf8')) === content
31+
) {
32+
return
33+
}
34+
35+
await fs.writeFile(appTypeDeclarations, content)
2736
}

packages/next/shared/lib/dynamic.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export default function dynamic<P = {}>(
119119
if (!process.env.__NEXT_REACT_ROOT && suspenseOptions.suspense) {
120120
// TODO: add error doc when this feature is stable
121121
throw new Error(
122-
`Disallowed suspense option usage with next/dynamic in blocking mode`
122+
`Invalid suspense option usage in next/dynamic. Read more: https://nextjs.org/docs/messages/invalid-dynamic-suspense`
123123
)
124124
}
125125
}

test/integration/react-18/test/index.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe('React 18 Support', () => {
8787
})
8888
expect(code).toBe(1)
8989
expect(stderr).toContain(
90-
'Disallowed suspense option usage with next/dynamic'
90+
'Invalid suspense option usage in next/dynamic. Read more: https://nextjs.org/docs/messages/invalid-dynamic-suspense'
9191
)
9292
})
9393
})
@@ -130,7 +130,7 @@ describe('Basics', () => {
130130
const html = await renderViaHTTP(appPort, '/suspense/unwrapped')
131131
unwrappedPage.restore()
132132
await killApp(app)
133-
// expect(html).toContain('Disallowed suspense option usage with next/dynamic')
133+
134134
expect(html).toContain(
135135
'A React component suspended while rendering, but no fallback UI was specified'
136136
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/types/global" />
3+
/// <reference types="next/image-types/global" />
4+
5+
// NOTE: This file should not be edited
6+
// see https://nextjs.org/docs/basic-features/typescript for more information.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Index() {
2+
return <div />
3+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* eslint-env jest */
2+
3+
import { join } from 'path'
4+
import { findPort, launchApp, killApp } from 'next-test-utils'
5+
import { promises as fs } from 'fs'
6+
7+
jest.setTimeout(1000 * 60 * 2)
8+
9+
const appDir = join(__dirname, '..')
10+
const appTypeDeclarations = join(appDir, 'next-env.d.ts')
11+
12+
describe('TypeScript App Type Declarations', () => {
13+
it('should write a new next-env.d.ts if none exist', async () => {
14+
const prevContent = await fs.readFile(appTypeDeclarations, 'utf8')
15+
try {
16+
await fs.unlink(appTypeDeclarations)
17+
const appPort = await findPort()
18+
let app
19+
try {
20+
app = await launchApp(appDir, appPort, {})
21+
const content = await fs.readFile(appTypeDeclarations, 'utf8')
22+
expect(content).toEqual(prevContent)
23+
} finally {
24+
await killApp(app)
25+
}
26+
} finally {
27+
await fs.writeFile(appTypeDeclarations, prevContent)
28+
}
29+
})
30+
31+
it('should overwrite next-env.d.ts if an incorrect one exists', async () => {
32+
const prevContent = await fs.readFile(appTypeDeclarations, 'utf8')
33+
try {
34+
await fs.writeFile(appTypeDeclarations, prevContent + 'modification')
35+
const appPort = await findPort()
36+
let app
37+
try {
38+
app = await launchApp(appDir, appPort, {})
39+
const content = await fs.readFile(appTypeDeclarations, 'utf8')
40+
expect(content).toEqual(prevContent)
41+
} finally {
42+
await killApp(app)
43+
}
44+
} finally {
45+
await fs.writeFile(appTypeDeclarations, prevContent)
46+
}
47+
})
48+
49+
it('should not touch an existing correct next-env.d.ts', async () => {
50+
const prevStat = await fs.stat(appTypeDeclarations)
51+
const appPort = await findPort()
52+
let app
53+
try {
54+
app = await launchApp(appDir, appPort, {})
55+
const stat = await fs.stat(appTypeDeclarations)
56+
expect(stat.mtime).toEqual(prevStat.mtime)
57+
} finally {
58+
await killApp(app)
59+
}
60+
})
61+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": ".",
4+
"esModuleInterop": true,
5+
"module": "esnext",
6+
"jsx": "preserve",
7+
"target": "es5",
8+
"lib": ["dom", "dom.iterable", "esnext"],
9+
"allowJs": true,
10+
"skipLibCheck": true,
11+
"strict": true,
12+
"forceConsistentCasingInFileNames": true,
13+
"noEmit": true,
14+
"incremental": true,
15+
"moduleResolution": "node",
16+
"resolveJsonModule": true,
17+
"isolatedModules": true
18+
},
19+
"exclude": ["node_modules"],
20+
"include": ["next-env.d.ts", "components", "pages"]
21+
}

0 commit comments

Comments
 (0)