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'
|
||||
|
||||
const ALL_INITIALIZERS = []
|
||||
const READY_INITIALIZERS = new Map()
|
||||
const READY_INITIALIZERS = []
|
||||
let initialized = false
|
||||
|
||||
function load (loader) {
|
||||
|
@ -138,12 +138,14 @@ function createLoadableComponent (loadFn, options) {
|
|||
// Client only
|
||||
if (!initialized && typeof window !== 'undefined' && typeof opts.webpack === 'function') {
|
||||
const moduleIds = opts.webpack()
|
||||
READY_INITIALIZERS.push((ids) => {
|
||||
for (const moduleId of moduleIds) {
|
||||
READY_INITIALIZERS.set(moduleId, () => {
|
||||
if (ids.indexOf(moduleId) !== -1) {
|
||||
return init()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return class LoadableComponent extends React.Component {
|
||||
constructor (props) {
|
||||
|
@ -273,17 +275,17 @@ function LoadableMap (opts) {
|
|||
|
||||
Loadable.Map = LoadableMap
|
||||
|
||||
function flushInitializers (initializers) {
|
||||
function flushInitializers (initializers, ids) {
|
||||
let promises = []
|
||||
|
||||
while (initializers.length) {
|
||||
let init = initializers.pop()
|
||||
promises.push(init())
|
||||
promises.push(init(ids))
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
if (initializers.length) {
|
||||
return flushInitializers(initializers)
|
||||
return flushInitializers(initializers, ids)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -294,24 +296,14 @@ Loadable.preloadAll = () => {
|
|||
})
|
||||
}
|
||||
|
||||
Loadable.preloadReady = (webpackIds) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const initializers = webpackIds.reduce((allInitalizers, moduleId) => {
|
||||
const initializer = READY_INITIALIZERS.get(moduleId)
|
||||
if (!initializer) {
|
||||
return allInitalizers
|
||||
}
|
||||
|
||||
allInitalizers.push(initializer)
|
||||
return allInitalizers
|
||||
}, [])
|
||||
|
||||
Loadable.preloadReady = (ids) => {
|
||||
return new Promise((resolve) => {
|
||||
const res = () => {
|
||||
initialized = true
|
||||
// Make sure the object is cleared
|
||||
READY_INITIALIZERS.clear()
|
||||
|
||||
return resolve()
|
||||
}
|
||||
// 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 () => {
|
||||
let browser
|
||||
try {
|
||||
|
|
Loading…
Reference in a new issue