Skip to content

Commit a732de6

Browse files
committed
unstable_suspense -> suspense, add concurrent tests
1 parent 7624414 commit a732de6

File tree

9 files changed

+109
-46
lines changed

9 files changed

+109
-46
lines changed

packages/next/shared/lib/dynamic.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export type LoadableBaseOptions<P = {}> = LoadableGeneratedOptions & {
3434
| React.LazyExoticComponent<React.ComponentType<P>>
3535
loadableGenerated?: LoadableGeneratedOptions
3636
ssr?: boolean
37-
unstable_suspense?: boolean
37+
suspense?: boolean
3838
}
3939

4040
export type LoadableOptions<P = {}> = LoadableBaseOptions<P>
@@ -115,25 +115,25 @@ export default function dynamic<P = {}>(
115115
if (!process.env.__NEXT_REACT_ROOT) {
116116
if (
117117
process.env.NODE_ENV !== 'production' &&
118-
loadableOptions.unstable_suspense &&
118+
loadableOptions.suspense &&
119119
!isServerSide
120120
) {
121121
console.warn(
122122
`Enable experimental.reactRoot or use React version above 18 to use suspense option`
123123
)
124124
}
125-
loadableOptions.unstable_suspense = false
125+
loadableOptions.suspense = false
126126
}
127127

128-
const { unstable_suspense, loader } = loadableOptions
129-
// If unstable_suspense is enabled, delegate rendering to unstable_suspense
130-
if (unstable_suspense) {
128+
const { suspense, loader } = loadableOptions
129+
// If suspense is enabled, delegate rendering to suspense
130+
if (suspense) {
131131
delete loadableOptions.loadableGenerated
132132
delete loadableOptions.loading
133133
delete loadableOptions.ssr
134134
}
135135

136-
if (typeof loadableOptions.loader === 'function' && unstable_suspense) {
136+
if (typeof loadableOptions.loader === 'function' && suspense) {
137137
loadableOptions.loader = React.lazy(
138138
loader as () => Promise<{
139139
default: React.ComponentType<P>
@@ -150,7 +150,7 @@ export default function dynamic<P = {}>(
150150
}
151151

152152
// support for disabling server side rendering, eg: dynamic(import('../hello-world'), {ssr: false})
153-
if (loadableOptions.ssr === false && !unstable_suspense) {
153+
if (loadableOptions.ssr === false && !suspense) {
154154
return noSSR(loadableFn, loadableOptions)
155155
}
156156

packages/next/shared/lib/loadable.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,11 @@ function createLoadableComponent(loadFn, options) {
6666
timeout: null,
6767
webpack: null,
6868
modules: null,
69-
unstable_suspense: false,
69+
suspense: false,
7070
},
7171
options
7272
)
7373

74-
opts.suspense = opts.unstable_suspense
75-
delete opts.unstable_suspense
76-
7774
let subscription = null
7875
function init() {
7976
if (opts.suspense) {

test/integration/react-18/prerelease/components/dynamic-suspense.js renamed to test/integration/react-18/prerelease/components/dynamic-hello.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { Suspense } from 'react'
22
import dynamic from 'next/dynamic'
33

4+
const ssr = false
5+
const suspense = false
6+
47
const Hello = dynamic(() => import('./hello'), {
5-
ssr: false,
6-
unstable_suspense: false,
8+
ssr,
9+
suspense: suspense,
710
})
811

9-
export default function SuspenseNoSSR({ thrown }) {
12+
export default function DynamicHello({ thrown }) {
1013
return (
1114
<Suspense fallback={'loading'}>
1215
<Hello thrown={thrown} />

test/integration/react-18/prerelease/next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module.exports = {
22
experimental: {
33
reactRoot: true,
4+
concurrentFeatures: false,
45
},
56
webpack(config) {
67
const { alias } = config.resolve
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import DynamicSuspense from '../../components/dynamic-suspense'
1+
import Hello from '../../components/dynamic-hello'
22

33
export default function NoThrown() {
4-
return <DynamicSuspense thrown={false} />
4+
return <Hello thrown={false} />
55
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import DynamicSuspense from '../../components/dynamic-suspense'
1+
import DynamicHello from '../../components/dynamic-hello'
22

33
export default function Thrown() {
4-
return <DynamicSuspense thrown />
4+
return <DynamicHello thrown />
55
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { join } from 'path'
2+
import webdriver from 'next-webdriver'
3+
import cheerio from 'cheerio'
4+
import { File, check } from 'next-test-utils'
5+
6+
export default (context, render) => {
7+
const dynamicHello = new File(
8+
join(context.appDir, 'components/dynamic-hello.js')
9+
)
10+
const nextConfig = new File(join(context.appDir, 'next.config.js'))
11+
12+
async function get$(path, query) {
13+
const html = await render(path, query)
14+
return cheerio.load(html)
15+
}
16+
17+
describe('concurrentFeatures is enabled', () => {
18+
beforeAll(() => {
19+
dynamicHello.replace('const suspense = false', `const suspense = true`)
20+
nextConfig.replace(
21+
'concurrentFeatures: false',
22+
'concurrentFeatures: true'
23+
)
24+
})
25+
afterAll(() => {
26+
dynamicHello.restore()
27+
nextConfig.restore()
28+
})
29+
30+
it('should render the fallback on server side if not suspended on server', async () => {
31+
const $ = await get$('/suspense/no-thrown')
32+
const html = $('body').html()
33+
expect(html).toContain('loading')
34+
expect(JSON.parse($('#__NEXT_DATA__').text()).dynamicIds).toBeUndefined()
35+
})
36+
37+
it('should render the fallback on server side if suspended on server', async () => {
38+
const $ = await get$('/suspense/thrown')
39+
const html = $('body').html()
40+
expect(html).toContain('loading')
41+
expect(JSON.parse($('#__NEXT_DATA__').text()).dynamicIds).toBeUndefined()
42+
})
43+
44+
it('should hydrate suspenses on client side if suspended on server', async () => {
45+
let browser
46+
try {
47+
browser = await webdriver(context.appPort, '/suspense/thrown')
48+
await check(() => browser.elementByCss('body').text(), /hello/)
49+
} finally {
50+
if (browser) {
51+
await browser.close()
52+
}
53+
}
54+
})
55+
})
56+
}

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

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,29 @@
1-
/* eslint-env jest */
21
import { join } from 'path'
32
import webdriver from 'next-webdriver'
43
import cheerio from 'cheerio'
5-
import { check } from 'next-test-utils'
6-
import { File } from 'next-test-utils'
4+
import { File, check } from 'next-test-utils'
75

8-
const appDir = join(__dirname, '../prerelease')
9-
const page = new File(join(appDir, 'components/dynamic-suspense.js'))
10-
11-
function writeDynamicTestComponent({ ssr, suspense = false }) {
12-
const content = `import { Suspense } from 'react'
13-
import dynamic from 'next/dynamic'
14-
15-
const Hello = dynamic(() => import('./hello'), {
16-
${typeof ssr !== 'undefined' ? `ssr: ${ssr},` : ''}
17-
unstable_suspense: ${suspense},
18-
})
6+
export default (context, render) => {
7+
const dynamicHello = new File(
8+
join(context.appDir, 'components/dynamic-hello.js')
9+
)
1910

20-
export default function SuspenseNoSSR({ thrown }) {
21-
return (
22-
<Suspense fallback={'loading'}>
23-
<Hello thrown={thrown} />
24-
</Suspense>
11+
function updateComponent({ ssr, suspense = false }) {
12+
dynamicHello.replace('const ssr = false', `const ssr = ${ssr + ''}`)
13+
dynamicHello.replace(
14+
'const suspense = false',
15+
`const suspense = ${suspense + ''}`
2516
)
26-
}`
27-
page.write(content)
28-
}
17+
}
2918

30-
export default (context, render) => {
3119
async function get$(path, query) {
3220
const html = await render(path, query)
3321
return cheerio.load(html)
3422
}
3523

3624
describe('suspense:true option', () => {
37-
beforeAll(() => writeDynamicTestComponent({ suspense: true }))
38-
afterAll(() => page.restore())
25+
beforeAll(() => updateComponent({ suspense: true }))
26+
afterAll(() => dynamicHello.restore())
3927

4028
describe('promise is thrown on server side', () => {
4129
// let `ssr` option be auto overridden

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
renderViaHTTP,
1616
} from 'next-test-utils'
1717
import dynamic from './dynamic'
18+
import concurrent from './concurrent'
1819

1920
jest.setTimeout(1000 * 60 * 5)
2021

@@ -145,13 +146,30 @@ describe('React 18 basics', () => {
145146
})
146147

147148
describe('Dynamic import', () => {
148-
const context = {}
149+
const context = { appDir }
149150
beforeEach(async () => {
150151
context.appPort = await findPort()
151-
context.server = await launchApp(appDir, context.appPort, { nodeArgs })
152+
context.server = await launchApp(context.appDir, context.appPort, {
153+
nodeArgs,
154+
})
152155
})
153156
afterEach(async () => {
154157
await killApp(context.server)
155158
})
156159
dynamic(context, (p, q) => renderViaHTTP(context.appPort, p, q))
157160
})
161+
162+
describe('Concurrent mode', () => {
163+
const context = { appDir }
164+
beforeEach(async () => {
165+
await nextBuild(context.appDir, [], { nodeArgs })
166+
context.appPort = await findPort()
167+
context.server = await nextStart(context.appDir, context.appPort, {
168+
nodeArgs,
169+
})
170+
})
171+
afterEach(async () => {
172+
await killApp(context.server)
173+
})
174+
concurrent(context, (p, q) => renderViaHTTP(context.appPort, p, q))
175+
})

0 commit comments

Comments
 (0)