-
Notifications
You must be signed in to change notification settings - Fork 28.8k
misc: tweak fetch patch restoration timing during HMR to allow for userland fetch patching #68193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1acc3a1
53ef550
8bc5262
08fc011
9883426
7faf8f5
fa5eae8
6f3b8f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -249,6 +249,7 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { | |
private pagesMapping: { [key: string]: string } = {} | ||
private appDir?: string | ||
private telemetry: Telemetry | ||
private resetFetch: () => void | ||
private versionInfo: VersionInfo = { | ||
staleness: 'unknown', | ||
installed: '0.0.0', | ||
|
@@ -274,6 +275,7 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { | |
rewrites, | ||
appDir, | ||
telemetry, | ||
resetFetch, | ||
}: { | ||
config: NextConfigComplete | ||
pagesDir?: string | ||
|
@@ -284,6 +286,7 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { | |
rewrites: CustomRoutes['rewrites'] | ||
appDir?: string | ||
telemetry: Telemetry | ||
resetFetch: () => void | ||
} | ||
) { | ||
this.hasAmpEntrypoints = false | ||
|
@@ -301,6 +304,7 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { | |
this.edgeServerStats = null | ||
this.serverPrevDocumentHash = null | ||
this.telemetry = telemetry | ||
this.resetFetch = resetFetch | ||
|
||
this.config = config | ||
this.previewProps = previewProps | ||
|
@@ -1365,6 +1369,7 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { | |
changedCSSImportPages.size || | ||
reloadAfterInvalidation | ||
) { | ||
this.resetFetch() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we have some comments about why we only conditionally reset fetch instead of doing somewhere will always get running? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. because this branch checks for server components changes. I can add a comment tho |
||
this.refreshServerComponents() | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import React from 'react' | ||
import { ReactNode } from 'react' | ||
|
||
const magicNumber = Math.random() | ||
const originalFetch = globalThis.fetch | ||
|
||
if (originalFetch.name === 'monkeyPatchedFetch') { | ||
throw new Error( | ||
'Patching over already patched fetch. This creates a memory leak.' | ||
) | ||
} | ||
|
||
globalThis.fetch = async function monkeyPatchedFetch( | ||
resource: URL | RequestInfo, | ||
options?: RequestInit | ||
) { | ||
const request = new Request(resource) | ||
|
||
if (request.url === 'http://fake.url/secret') { | ||
return new Response('monkey patching is fun') | ||
} | ||
|
||
if (request.url === 'http://fake.url/magic-number') { | ||
return new Response(magicNumber.toString()) | ||
} | ||
|
||
return originalFetch(resource, options) | ||
} | ||
|
||
export default function Root({ children }: { children: ReactNode }) { | ||
return ( | ||
<html> | ||
<body>{children}</body> | ||
</html> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export default async function Page() { | ||
const secret = (await fetch('http://fake.url/secret').then((res) => | ||
res.text() | ||
)) as any | ||
const magicNumber = (await fetch('http://fake.url/magic-number').then((res) => | ||
res.text() | ||
)) as any | ||
|
||
return ( | ||
<> | ||
<div id="update">touch to trigger HMR</div> | ||
<div id="secret">{secret}</div> | ||
<div id="magic-number">{magicNumber}</div> | ||
</> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { nextTestSetup } from 'e2e-utils' | ||
import { retry } from 'next-test-utils' | ||
|
||
import cheerio from 'cheerio' | ||
|
||
describe('dev-fetch-hmr', () => { | ||
const { next } = nextTestSetup({ | ||
files: __dirname, | ||
}) | ||
|
||
it('should retain module level fetch patching', async () => { | ||
const html = await next.render('/') | ||
expect(html).toContain('monkey patching is fun') | ||
|
||
const magicNumber = cheerio.load(html)('#magic-number').text() | ||
|
||
const html2 = await next.render('/') | ||
expect(html2).toContain('monkey patching is fun') | ||
const magicNumber2 = cheerio.load(html2)('#magic-number').text() | ||
// Module was not re-evaluated | ||
expect(magicNumber2).toBe(magicNumber) | ||
const update = cheerio.load(html2)('#update').text() | ||
expect(update).toBe('touch to trigger HMR') | ||
|
||
// trigger HMR | ||
await next.patchFile('app/page.tsx', (content) => | ||
unstubbable marked this conversation as resolved.
Show resolved
Hide resolved
|
||
content.replace('touch to trigger HMR', 'touch to trigger HMR 2') | ||
) | ||
|
||
await retry(async () => { | ||
const html3 = await next.render('/') | ||
const update2 = cheerio.load(html3)('#update').text() | ||
expect(update2).toBe('touch to trigger HMR 2') | ||
const magicNumber3 = cheerio.load(html3)('#magic-number').text() | ||
expect(html3).toContain('monkey patching is fun') | ||
// Module was re-evaluated | ||
expect(magicNumber3).not.toEqual(magicNumber) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** | ||
* @type {import('next').NextConfig} | ||
*/ | ||
const nextConfig = {} | ||
|
||
module.exports = nextConfig |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ES2017", | ||
"lib": ["dom", "dom.iterable", "esnext"], | ||
"allowJs": true, | ||
"skipLibCheck": true, | ||
"strict": false, | ||
"noEmit": true, | ||
"incremental": true, | ||
"module": "esnext", | ||
"esModuleInterop": true, | ||
"moduleResolution": "node", | ||
"resolveJsonModule": true, | ||
"isolatedModules": true, | ||
"jsx": "preserve", | ||
"plugins": [ | ||
{ | ||
"name": "next" | ||
} | ||
] | ||
}, | ||
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], | ||
"exclude": ["node_modules"] | ||
} |
Uh oh!
There was an error while loading. Please reload this page.