mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Fix recursive hydration of next/dynamic (#6326)
Fixes #5347 The main issue is that we were waiting only 1 level of dynamic imports, so the dynamic imports nested inside other dynamic import files were not awaited. This would cause either a flash of loading states or you wouldn't see the loading state (because of preload) but it would then show a hydration warning in development. Thanks to @arthens for providing the reproduction that I modelled the tests after.
This commit is contained in:
parent
5cef35b811
commit
dd9811b206
|
@ -25,7 +25,7 @@ import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
const ALL_INITIALIZERS = []
|
const ALL_INITIALIZERS = []
|
||||||
const READY_INITIALIZERS = new Map()
|
const READY_INITIALIZERS = []
|
||||||
let initialized = false
|
let initialized = false
|
||||||
|
|
||||||
function load (loader) {
|
function load (loader) {
|
||||||
|
@ -138,12 +138,14 @@ function createLoadableComponent (loadFn, options) {
|
||||||
// Client only
|
// Client only
|
||||||
if (!initialized && typeof window !== 'undefined' && typeof opts.webpack === 'function') {
|
if (!initialized && typeof window !== 'undefined' && typeof opts.webpack === 'function') {
|
||||||
const moduleIds = opts.webpack()
|
const moduleIds = opts.webpack()
|
||||||
|
READY_INITIALIZERS.push((ids) => {
|
||||||
for (const moduleId of moduleIds) {
|
for (const moduleId of moduleIds) {
|
||||||
READY_INITIALIZERS.set(moduleId, () => {
|
if (ids.indexOf(moduleId) !== -1) {
|
||||||
return init()
|
return init()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return class LoadableComponent extends React.Component {
|
return class LoadableComponent extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
|
@ -273,17 +275,17 @@ function LoadableMap (opts) {
|
||||||
|
|
||||||
Loadable.Map = LoadableMap
|
Loadable.Map = LoadableMap
|
||||||
|
|
||||||
function flushInitializers (initializers) {
|
function flushInitializers (initializers, ids) {
|
||||||
let promises = []
|
let promises = []
|
||||||
|
|
||||||
while (initializers.length) {
|
while (initializers.length) {
|
||||||
let init = initializers.pop()
|
let init = initializers.pop()
|
||||||
promises.push(init())
|
promises.push(init(ids))
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then(() => {
|
return Promise.all(promises).then(() => {
|
||||||
if (initializers.length) {
|
if (initializers.length) {
|
||||||
return flushInitializers(initializers)
|
return flushInitializers(initializers, ids)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -294,24 +296,14 @@ Loadable.preloadAll = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Loadable.preloadReady = (webpackIds) => {
|
Loadable.preloadReady = (ids) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
const initializers = webpackIds.reduce((allInitalizers, moduleId) => {
|
const res = () => {
|
||||||
const initializer = READY_INITIALIZERS.get(moduleId)
|
|
||||||
if (!initializer) {
|
|
||||||
return allInitalizers
|
|
||||||
}
|
|
||||||
|
|
||||||
allInitalizers.push(initializer)
|
|
||||||
return allInitalizers
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
initialized = true
|
initialized = true
|
||||||
// Make sure the object is cleared
|
return resolve()
|
||||||
READY_INITIALIZERS.clear()
|
}
|
||||||
|
|
||||||
// We always will resolve, errors should be handled within loading UIs.
|
// We always will resolve, errors should be handled within loading UIs.
|
||||||
flushInitializers(initializers).then(resolve, resolve)
|
flushInitializers(READY_INITIALIZERS, ids).then(res, res)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
8
test/integration/basic/components/nested1.js
Normal file
8
test/integration/basic/components/nested1.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
|
||||||
|
const Nested2 = dynamic(() => import('./nested2'))
|
||||||
|
|
||||||
|
export default () => <div>
|
||||||
|
Nested 1
|
||||||
|
<Nested2 />
|
||||||
|
</div>
|
12
test/integration/basic/components/nested2.js
Normal file
12
test/integration/basic/components/nested2.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
|
||||||
|
const BrowserLoaded = dynamic(async () => () => <div>Browser hydrated</div>, {
|
||||||
|
ssr: false
|
||||||
|
})
|
||||||
|
|
||||||
|
export default () => <div>
|
||||||
|
<div>
|
||||||
|
Nested 2
|
||||||
|
</div>
|
||||||
|
<BrowserLoaded />
|
||||||
|
</div>
|
5
test/integration/basic/pages/dynamic/nested.js
Normal file
5
test/integration/basic/pages/dynamic/nested.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
|
||||||
|
const DynamicComponent = dynamic(() => import('../../components/nested1'))
|
||||||
|
|
||||||
|
export default DynamicComponent
|
|
@ -37,6 +37,26 @@ export default (context, render) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should hydrate nested chunks', async () => {
|
||||||
|
let browser
|
||||||
|
try {
|
||||||
|
browser = await webdriver(context.appPort, '/dynamic/nested')
|
||||||
|
await check(() => browser.elementByCss('body').text(), /Nested 1/)
|
||||||
|
await check(() => browser.elementByCss('body').text(), /Nested 2/)
|
||||||
|
await check(() => browser.elementByCss('body').text(), /Browser hydrated/)
|
||||||
|
|
||||||
|
const logs = await browser.log('browser')
|
||||||
|
|
||||||
|
logs.forEach(logItem => {
|
||||||
|
expect(logItem.message).not.toMatch(/Expected server HTML to contain/)
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
if (browser) {
|
||||||
|
browser.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('should render the component Head content', async () => {
|
it('should render the component Head content', async () => {
|
||||||
let browser
|
let browser
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue