mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
Previously, we created chunk list register chunks for every reachable chunk in the chunk graph on a page. Now, we only create one that subscribes to all recursively reachable assets. This has to pass around an explicit list of client references chunks as those cannot be discovered via the chunk graph alone. This results in a significant performance improvement when loading pages with the dev server, improving performance of a 60s cold build in a large app by about 10s.
310 lines
11 KiB
TypeScript
310 lines
11 KiB
TypeScript
import { join } from 'path'
|
|
import { getBrowserBodyText, retry, waitFor } from 'next-test-utils'
|
|
import { nextTestSetup } from 'e2e-utils'
|
|
|
|
export function runHotModuleReloadHmrTest(nextConfig: {
|
|
basePath: string
|
|
assetPrefix: string
|
|
}) {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
nextConfig,
|
|
patchFileDelay: 500,
|
|
})
|
|
const { basePath } = nextConfig
|
|
|
|
describe('delete a page and add it back', () => {
|
|
it('should load the page properly', async () => {
|
|
const contactPagePath = join('pages', 'hmr', 'contact.js')
|
|
const newContactPagePath = join('pages', 'hmr', '_contact.js')
|
|
const browser = await next.browser(basePath + '/hmr/contact')
|
|
try {
|
|
const text = await browser.elementByCss('p').text()
|
|
expect(text).toBe('This is the contact page.')
|
|
|
|
expect(next.cliOutput).toMatch(/GET .*\/hmr\/contact 200/)
|
|
let cliOutputLength = next.cliOutput.length
|
|
|
|
// Rename the file to mimic a deleted page
|
|
await next.renameFile(contactPagePath, newContactPagePath)
|
|
|
|
await retry(async () => {
|
|
expect(await getBrowserBodyText(browser)).toMatch(
|
|
/This page could not be found/
|
|
)
|
|
})
|
|
expect(next.cliOutput.slice(cliOutputLength)).toMatch(
|
|
/GET .*\/hmr\/contact 404/
|
|
)
|
|
cliOutputLength = next.cliOutput.length
|
|
|
|
// Rename the file back to the original filename
|
|
await next.renameFile(newContactPagePath, contactPagePath)
|
|
|
|
// wait until the page comes back
|
|
await retry(async () => {
|
|
expect(await getBrowserBodyText(browser)).toMatch(
|
|
/This is the contact page/
|
|
)
|
|
})
|
|
expect(next.cliOutput.slice(cliOutputLength)).toMatch(
|
|
/GET .*\/hmr\/contact 200/
|
|
)
|
|
} finally {
|
|
await next
|
|
.renameFile(newContactPagePath, contactPagePath)
|
|
.catch(() => {})
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('editing a page', () => {
|
|
it('should detect the changes and display it', async () => {
|
|
const browser = await next.browser(basePath + '/hmr/about')
|
|
const text = await browser.elementByCss('p').text()
|
|
expect(text).toBe('This is the about page.')
|
|
|
|
const aboutPagePath = join('pages', 'hmr', 'about.js')
|
|
|
|
const originalContent = await next.readFile(aboutPagePath)
|
|
const editedContent = originalContent.replace(
|
|
'This is the about page',
|
|
'COOL page'
|
|
)
|
|
|
|
// change the content
|
|
try {
|
|
await next.patchFile(aboutPagePath, editedContent)
|
|
await retry(async () => {
|
|
expect(await getBrowserBodyText(browser)).toMatch(/COOL page/)
|
|
})
|
|
} finally {
|
|
// add the original content
|
|
await next.patchFile(aboutPagePath, originalContent)
|
|
}
|
|
|
|
await retry(async () => {
|
|
expect(await getBrowserBodyText(browser)).toMatch(
|
|
/This is the about page/
|
|
)
|
|
})
|
|
})
|
|
|
|
it('should not reload unrelated pages', async () => {
|
|
const browser = await next.browser(basePath + '/hmr/counter')
|
|
const text = await browser
|
|
.elementByCss('button')
|
|
.click()
|
|
.elementByCss('button')
|
|
.click()
|
|
.elementByCss('p')
|
|
.text()
|
|
expect(text).toBe('COUNT: 2')
|
|
|
|
const aboutPagePath = join('pages', 'hmr', 'about.js')
|
|
|
|
const originalContent = await next.readFile(aboutPagePath)
|
|
const editedContent = originalContent.replace(
|
|
'This is the about page',
|
|
'COOL page'
|
|
)
|
|
|
|
try {
|
|
// Change the about.js page
|
|
await next.patchFile(aboutPagePath, editedContent)
|
|
|
|
// Check whether the this page has reloaded or not.
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('p').text()).toMatch(/COUNT: 2/)
|
|
})
|
|
} finally {
|
|
// restore the about page content.
|
|
await next.patchFile(aboutPagePath, originalContent)
|
|
}
|
|
})
|
|
|
|
// Added because of a regression in react-hot-loader, see issues: #4246 #4273
|
|
// Also: https://github.com/vercel/styled-jsx/issues/425
|
|
it('should update styles correctly', async () => {
|
|
const browser = await next.browser(basePath + '/hmr/style')
|
|
const pTag = await browser.elementByCss('.hmr-style-page p')
|
|
const initialFontSize = await pTag.getComputedCss('font-size')
|
|
|
|
expect(initialFontSize).toBe('100px')
|
|
|
|
const pagePath = join('pages', 'hmr', 'style.js')
|
|
|
|
const originalContent = await next.readFile(pagePath)
|
|
const editedContent = originalContent.replace('100px', '200px')
|
|
|
|
// Change the page
|
|
await next.patchFile(pagePath, editedContent)
|
|
|
|
try {
|
|
// Check whether the this page has reloaded or not.
|
|
await retry(async () => {
|
|
const editedPTag = await browser.elementByCss('.hmr-style-page p')
|
|
expect(await editedPTag.getComputedCss('font-size')).toBe('200px')
|
|
})
|
|
} finally {
|
|
// Finally is used so that we revert the content back to the original regardless of the test outcome
|
|
// restore the about page content.
|
|
await next.patchFile(pagePath, originalContent)
|
|
}
|
|
})
|
|
|
|
// Added because of a regression in react-hot-loader, see issues: #4246 #4273
|
|
// Also: https://github.com/vercel/styled-jsx/issues/425
|
|
it('should update styles in a stateful component correctly', async () => {
|
|
const browser = await next.browser(
|
|
basePath + '/hmr/style-stateful-component'
|
|
)
|
|
const pagePath = join('pages', 'hmr', 'style-stateful-component.js')
|
|
const originalContent = await next.readFile(pagePath)
|
|
try {
|
|
const pTag = await browser.elementByCss('.hmr-style-page p')
|
|
const initialFontSize = await pTag.getComputedCss('font-size')
|
|
|
|
expect(initialFontSize).toBe('100px')
|
|
const editedContent = originalContent.replace('100px', '200px')
|
|
|
|
// Change the page
|
|
await next.patchFile(pagePath, editedContent)
|
|
|
|
// Check whether the this page has reloaded or not.
|
|
await retry(async () => {
|
|
const editedPTag = await browser.elementByCss('.hmr-style-page p')
|
|
expect(await editedPTag.getComputedCss('font-size')).toBe('200px')
|
|
})
|
|
} finally {
|
|
await next.patchFile(pagePath, originalContent)
|
|
}
|
|
})
|
|
|
|
// Added because of a regression in react-hot-loader, see issues: #4246 #4273
|
|
// Also: https://github.com/vercel/styled-jsx/issues/425
|
|
it('should update styles in a dynamic component correctly', async () => {
|
|
const browser = await next.browser(basePath + '/hmr/dynamic-component')
|
|
const secondBrowser = await next.browser(
|
|
basePath + '/hmr/dynamic-component'
|
|
)
|
|
const pagePath = join('components', 'hmr', 'dynamic.js')
|
|
const originalContent = await next.readFile(pagePath)
|
|
try {
|
|
const div = await browser.elementByCss('#dynamic-component')
|
|
const initialClientClassName = await div.getAttribute('class')
|
|
const initialFontSize = await div.getComputedCss('font-size')
|
|
|
|
expect(initialFontSize).toBe('100px')
|
|
|
|
const initialHtml = await next.render(
|
|
basePath + '/hmr/dynamic-component'
|
|
)
|
|
expect(initialHtml.includes('100px')).toBeTruthy()
|
|
|
|
const $initialHtml = await next.render$(
|
|
basePath + '/hmr/dynamic-component'
|
|
)
|
|
const initialServerClassName =
|
|
$initialHtml('#dynamic-component').attr('class')
|
|
|
|
expect(initialClientClassName === initialServerClassName).toBeTruthy()
|
|
|
|
const editedContent = originalContent.replace('100px', '200px')
|
|
|
|
// Change the page
|
|
await next.patchFile(pagePath, editedContent)
|
|
|
|
// wait for 5 seconds
|
|
await waitFor(5000)
|
|
|
|
// Check whether the this page has reloaded or not.
|
|
const editedDiv = await secondBrowser.elementByCss('#dynamic-component')
|
|
const editedClientClassName = await editedDiv.getAttribute('class')
|
|
const editedFontSize = await editedDiv.getComputedCss('font-size')
|
|
const browserHtml = await secondBrowser.eval(
|
|
'document.documentElement.innerHTML'
|
|
)
|
|
|
|
expect(editedFontSize).toBe('200px')
|
|
expect(browserHtml.includes('font-size:200px')).toBe(true)
|
|
expect(browserHtml.includes('font-size:100px')).toBe(false)
|
|
|
|
const editedHtml = await next.render(
|
|
basePath + '/hmr/dynamic-component'
|
|
)
|
|
expect(editedHtml.includes('200px')).toBeTruthy()
|
|
const $editedHtml = await next.render$(
|
|
basePath + '/hmr/dynamic-component'
|
|
)
|
|
const editedServerClassName =
|
|
$editedHtml('#dynamic-component').attr('class')
|
|
|
|
expect(editedClientClassName === editedServerClassName).toBe(true)
|
|
} finally {
|
|
// Finally is used so that we revert the content back to the original regardless of the test outcome
|
|
// restore the about page content.
|
|
await next.patchFile(pagePath, originalContent)
|
|
}
|
|
})
|
|
|
|
it('should update text in a dynamic component correctly', async () => {
|
|
const browser = await next.browser(basePath + '/hmr/dynamic-component')
|
|
const pagePath = join('components', 'hmr', 'dynamic.js')
|
|
const originalContent = await next.readFile(pagePath)
|
|
try {
|
|
const div = await browser.elementByCss('#dynamic-component')
|
|
const initialText = await div.text()
|
|
expect(initialText).toContain('Dynamic Component')
|
|
|
|
const timeOrigin = await browser.eval('performance.timeOrigin')
|
|
|
|
const editedContent = originalContent.replace(
|
|
'Dynamic Component',
|
|
'Dynamic Component UPDATED'
|
|
)
|
|
|
|
// Change the component text
|
|
await next.patchFile(pagePath, editedContent)
|
|
|
|
// Wait for HMR to propagate the text update
|
|
await retry(async () => {
|
|
const editedDiv = await browser.elementByCss('#dynamic-component')
|
|
const editedText = await editedDiv.text()
|
|
expect(editedText).toContain('Dynamic Component UPDATED')
|
|
})
|
|
|
|
// Ensure the page was updated via HMR and not a full reload
|
|
expect(await browser.eval('performance.timeOrigin')).toEqual(timeOrigin)
|
|
} finally {
|
|
// Restore the original content
|
|
await next.patchFile(pagePath, originalContent)
|
|
}
|
|
})
|
|
|
|
it('should not full reload when nonlatin characters are used', async () => {
|
|
const browser = await next.browser(basePath + '/hmr/nonlatin')
|
|
const pagePath = join('pages', 'hmr', 'nonlatin.js')
|
|
const originalContent = await next.readFile(pagePath)
|
|
try {
|
|
const timeOrigin = await browser.eval('performance.timeOrigin')
|
|
const editedContent = originalContent.replace(
|
|
'<div>テスト</div>',
|
|
'<div class="updated">テスト</div>'
|
|
)
|
|
|
|
// Change the page
|
|
await next.patchFile(pagePath, editedContent)
|
|
|
|
await browser.waitForElementByCss('.updated')
|
|
|
|
expect(await browser.eval('performance.timeOrigin')).toEqual(timeOrigin)
|
|
} finally {
|
|
// Finally is used so that we revert the content back to the original regardless of the test outcome
|
|
// restore the about page content.
|
|
await next.patchFile(pagePath, originalContent)
|
|
}
|
|
})
|
|
})
|
|
}
|