Skip to content

Commit

Permalink
unstable_suspense -> suspense, add concurrent tests
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Aug 11, 2021
1 parent 7624414 commit a732de6
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 46 deletions.
16 changes: 8 additions & 8 deletions packages/next/shared/lib/dynamic.tsx
Expand Up @@ -34,7 +34,7 @@ export type LoadableBaseOptions<P = {}> = LoadableGeneratedOptions & {
| React.LazyExoticComponent<React.ComponentType<P>>
loadableGenerated?: LoadableGeneratedOptions
ssr?: boolean
unstable_suspense?: boolean
suspense?: boolean
}

export type LoadableOptions<P = {}> = LoadableBaseOptions<P>
Expand Down Expand Up @@ -115,25 +115,25 @@ export default function dynamic<P = {}>(
if (!process.env.__NEXT_REACT_ROOT) {
if (
process.env.NODE_ENV !== 'production' &&
loadableOptions.unstable_suspense &&
loadableOptions.suspense &&
!isServerSide
) {
console.warn(
`Enable experimental.reactRoot or use React version above 18 to use suspense option`
)
}
loadableOptions.unstable_suspense = false
loadableOptions.suspense = false
}

const { unstable_suspense, loader } = loadableOptions
// If unstable_suspense is enabled, delegate rendering to unstable_suspense
if (unstable_suspense) {
const { suspense, loader } = loadableOptions
// If suspense is enabled, delegate rendering to suspense
if (suspense) {
delete loadableOptions.loadableGenerated
delete loadableOptions.loading
delete loadableOptions.ssr
}

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

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

Expand Down
5 changes: 1 addition & 4 deletions packages/next/shared/lib/loadable.js
Expand Up @@ -66,14 +66,11 @@ function createLoadableComponent(loadFn, options) {
timeout: null,
webpack: null,
modules: null,
unstable_suspense: false,
suspense: false,
},
options
)

opts.suspense = opts.unstable_suspense
delete opts.unstable_suspense

let subscription = null
function init() {
if (opts.suspense) {
Expand Down
@@ -1,12 +1,15 @@
import { Suspense } from 'react'
import dynamic from 'next/dynamic'

const ssr = false
const suspense = false

const Hello = dynamic(() => import('./hello'), {
ssr: false,
unstable_suspense: false,
ssr,
suspense: suspense,
})

export default function SuspenseNoSSR({ thrown }) {
export default function DynamicHello({ thrown }) {
return (
<Suspense fallback={'loading'}>
<Hello thrown={thrown} />
Expand Down
1 change: 1 addition & 0 deletions test/integration/react-18/prerelease/next.config.js
@@ -1,6 +1,7 @@
module.exports = {
experimental: {
reactRoot: true,
concurrentFeatures: false,
},
webpack(config) {
const { alias } = config.resolve
Expand Down
@@ -1,5 +1,5 @@
import DynamicSuspense from '../../components/dynamic-suspense'
import Hello from '../../components/dynamic-hello'

export default function NoThrown() {
return <DynamicSuspense thrown={false} />
return <Hello thrown={false} />
}
4 changes: 2 additions & 2 deletions test/integration/react-18/prerelease/pages/suspense/thrown.js
@@ -1,5 +1,5 @@
import DynamicSuspense from '../../components/dynamic-suspense'
import DynamicHello from '../../components/dynamic-hello'

export default function Thrown() {
return <DynamicSuspense thrown />
return <DynamicHello thrown />
}
56 changes: 56 additions & 0 deletions test/integration/react-18/test/concurrent.js
@@ -0,0 +1,56 @@
import { join } from 'path'
import webdriver from 'next-webdriver'
import cheerio from 'cheerio'
import { File, check } from 'next-test-utils'

export default (context, render) => {
const dynamicHello = new File(
join(context.appDir, 'components/dynamic-hello.js')
)
const nextConfig = new File(join(context.appDir, 'next.config.js'))

async function get$(path, query) {
const html = await render(path, query)
return cheerio.load(html)
}

describe('concurrentFeatures is enabled', () => {
beforeAll(() => {
dynamicHello.replace('const suspense = false', `const suspense = true`)
nextConfig.replace(
'concurrentFeatures: false',
'concurrentFeatures: true'
)
})
afterAll(() => {
dynamicHello.restore()
nextConfig.restore()
})

it('should render the fallback on server side if not suspended on server', async () => {
const $ = await get$('/suspense/no-thrown')
const html = $('body').html()
expect(html).toContain('loading')
expect(JSON.parse($('#__NEXT_DATA__').text()).dynamicIds).toBeUndefined()
})

it('should render the fallback on server side if suspended on server', async () => {
const $ = await get$('/suspense/thrown')
const html = $('body').html()
expect(html).toContain('loading')
expect(JSON.parse($('#__NEXT_DATA__').text()).dynamicIds).toBeUndefined()
})

it('should hydrate suspenses on client side if suspended on server', async () => {
let browser
try {
browser = await webdriver(context.appPort, '/suspense/thrown')
await check(() => browser.elementByCss('body').text(), /hello/)
} finally {
if (browser) {
await browser.close()
}
}
})
})
}
38 changes: 13 additions & 25 deletions test/integration/react-18/test/dynamic.js
@@ -1,41 +1,29 @@
/* eslint-env jest */
import { join } from 'path'
import webdriver from 'next-webdriver'
import cheerio from 'cheerio'
import { check } from 'next-test-utils'
import { File } from 'next-test-utils'
import { File, check } from 'next-test-utils'

const appDir = join(__dirname, '../prerelease')
const page = new File(join(appDir, 'components/dynamic-suspense.js'))

function writeDynamicTestComponent({ ssr, suspense = false }) {
const content = `import { Suspense } from 'react'
import dynamic from 'next/dynamic'
const Hello = dynamic(() => import('./hello'), {
${typeof ssr !== 'undefined' ? `ssr: ${ssr},` : ''}
unstable_suspense: ${suspense},
})
export default (context, render) => {
const dynamicHello = new File(
join(context.appDir, 'components/dynamic-hello.js')
)

export default function SuspenseNoSSR({ thrown }) {
return (
<Suspense fallback={'loading'}>
<Hello thrown={thrown} />
</Suspense>
function updateComponent({ ssr, suspense = false }) {
dynamicHello.replace('const ssr = false', `const ssr = ${ssr + ''}`)
dynamicHello.replace(
'const suspense = false',
`const suspense = ${suspense + ''}`
)
}`
page.write(content)
}
}

export default (context, render) => {
async function get$(path, query) {
const html = await render(path, query)
return cheerio.load(html)
}

describe('suspense:true option', () => {
beforeAll(() => writeDynamicTestComponent({ suspense: true }))
afterAll(() => page.restore())
beforeAll(() => updateComponent({ suspense: true }))
afterAll(() => dynamicHello.restore())

describe('promise is thrown on server side', () => {
// let `ssr` option be auto overridden
Expand Down
22 changes: 20 additions & 2 deletions test/integration/react-18/test/index.test.js
Expand Up @@ -15,6 +15,7 @@ import {
renderViaHTTP,
} from 'next-test-utils'
import dynamic from './dynamic'
import concurrent from './concurrent'

jest.setTimeout(1000 * 60 * 5)

Expand Down Expand Up @@ -145,13 +146,30 @@ describe('React 18 basics', () => {
})

describe('Dynamic import', () => {
const context = {}
const context = { appDir }
beforeEach(async () => {
context.appPort = await findPort()
context.server = await launchApp(appDir, context.appPort, { nodeArgs })
context.server = await launchApp(context.appDir, context.appPort, {
nodeArgs,
})
})
afterEach(async () => {
await killApp(context.server)
})
dynamic(context, (p, q) => renderViaHTTP(context.appPort, p, q))
})

describe('Concurrent mode', () => {
const context = { appDir }
beforeEach(async () => {
await nextBuild(context.appDir, [], { nodeArgs })
context.appPort = await findPort()
context.server = await nextStart(context.appDir, context.appPort, {
nodeArgs,
})
})
afterEach(async () => {
await killApp(context.server)
})
concurrent(context, (p, q) => renderViaHTTP(context.appPort, p, q))
})

0 comments on commit a732de6

Please sign in to comment.