From 1acc3a18ae19f6a8dfca8514255cd9f99a424a95 Mon Sep 17 00:00:00 2001 From: Jimmy Lai Date: Fri, 26 Jul 2024 15:00:19 +0200 Subject: [PATCH 1/8] stuff --- .../src/server/dev/hot-reloader-turbopack.ts | 5 ++- .../src/server/dev/hot-reloader-webpack.ts | 5 +++ .../next/src/server/dev/next-dev-server.ts | 17 --------- packages/next/src/server/lib/patch-fetch.ts | 13 ++++--- .../lib/router-utils/setup-dev-bundler.ts | 9 ++++- .../app-dir/dev-fetch-hmr/app/layout.tsx | 36 +++++++++++++++++++ .../app-dir/dev-fetch-hmr/app/page.tsx | 11 ++++++ .../dev-fetch-hmr/dev-fetch-hmr.test.ts | 34 ++++++++++++++++++ .../app-dir/dev-fetch-hmr/next.config.js | 6 ++++ .../app-dir/dev-fetch-hmr/tsconfig.json | 24 +++++++++++++ 10 files changed, 134 insertions(+), 26 deletions(-) create mode 100644 test/development/app-dir/dev-fetch-hmr/app/layout.tsx create mode 100644 test/development/app-dir/dev-fetch-hmr/app/page.tsx create mode 100644 test/development/app-dir/dev-fetch-hmr/dev-fetch-hmr.test.ts create mode 100644 test/development/app-dir/dev-fetch-hmr/next.config.js create mode 100644 test/development/app-dir/dev-fetch-hmr/tsconfig.json diff --git a/packages/next/src/server/dev/hot-reloader-turbopack.ts b/packages/next/src/server/dev/hot-reloader-turbopack.ts index d5ffa3a8690788..c7b880f93271a6 100644 --- a/packages/next/src/server/dev/hot-reloader-turbopack.ts +++ b/packages/next/src/server/dev/hot-reloader-turbopack.ts @@ -92,7 +92,8 @@ const sessionId = Math.floor(Number.MAX_SAFE_INTEGER * Math.random()) export async function createHotReloaderTurbopack( opts: SetupOpts, serverFields: ServerFields, - distDir: string + distDir: string, + resetFetch: () => void ): Promise { const buildId = 'development' const { nextConfig, dir } = opts @@ -236,6 +237,8 @@ export async function createHotReloaderTurbopack( } } + resetFetch() + const hasAppPaths = writtenEndpoint.serverPaths.some(({ path: p }) => p.startsWith('server/app') ) diff --git a/packages/next/src/server/dev/hot-reloader-webpack.ts b/packages/next/src/server/dev/hot-reloader-webpack.ts index ab7081ef7e41b2..79d084cc94a719 100644 --- a/packages/next/src/server/dev/hot-reloader-webpack.ts +++ b/packages/next/src/server/dev/hot-reloader-webpack.ts @@ -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() this.refreshServerComponents() } diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 13be77b728bd33..763195fde72445 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -162,7 +162,6 @@ export default class DevServer extends Server { this.bundlerService = options.bundlerService this.startServerSpan = options.startServerSpan ?? trace('start-next-dev-server') - this.storeGlobals() this.renderOpts.dev = true this.renderOpts.ErrorDebug = ReactDevOverlay this.staticPathsCache = new LRUCache({ @@ -294,9 +293,6 @@ export default class DevServer extends Server { await super.prepareImpl() await this.matchers.reload() - // Store globals again to preserve changes made by the instrumentation hook. - this.storeGlobals() - this.ready?.resolve() this.ready = undefined @@ -825,14 +821,6 @@ export default class DevServer extends Server { return nextInvoke as NonNullable } - private storeGlobals(): void { - this.originalFetch = global.fetch - } - - private restorePatchedGlobals(): void { - global.fetch = this.originalFetch ?? global.fetch - } - protected async ensurePage(opts: { page: string clientOnly: boolean @@ -880,11 +868,6 @@ export default class DevServer extends Server { } this.nextFontManifest = super.getNextFontManifest() - // before we re-evaluate a route module, we want to restore globals that might - // have been patched previously to their original state so that we don't - // patch on top of the previous patch, which would keep the context of the previous - // patched global in memory, creating a memory leak. - this.restorePatchedGlobals() return await super.findPageComponents({ page, diff --git a/packages/next/src/server/lib/patch-fetch.ts b/packages/next/src/server/lib/patch-fetch.ts index fbb80c93adf2ec..44cac09a95dab8 100644 --- a/packages/next/src/server/lib/patch-fetch.ts +++ b/packages/next/src/server/lib/patch-fetch.ts @@ -34,11 +34,7 @@ type PatchedFetcher = Fetcher & { readonly _nextOriginalFetch: Fetcher } -function isPatchedFetch( - fetch: Fetcher | PatchedFetcher -): fetch is PatchedFetcher { - return '__nextPatched' in fetch && fetch.__nextPatched === true -} +let isFetchPatched = false export function validateRevalidate( revalidateVal: unknown, @@ -804,9 +800,13 @@ export function createPatchedFetcher( } // Attach the necessary properties to the patched fetch function. + // We don't use this to determine if the fetch function has been patched, + // but for external consumers to determine if the fetch function has been + // patched. patched.__nextPatched = true as const patched.__nextGetStaticStore = () => staticGenerationAsyncStorage patched._nextOriginalFetch = originFetch + isFetchPatched = true return patched } @@ -815,12 +815,11 @@ export function createPatchedFetcher( // determining if a page is static or not export function patchFetch(options: PatchableModule) { // If we've already patched fetch, we should not patch it again. - if (isPatchedFetch(globalThis.fetch)) return + if (isFetchPatched) return // Grab the original fetch function. We'll attach this so we can use it in // the patched fetch function. const original = createDedupeFetch(globalThis.fetch) - // Set the global fetch to the patched fetch. globalThis.fetch = createPatchedFetcher(original, options) } diff --git a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts index cf01443a6bb989..83e7a5c4ca2f69 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts @@ -122,6 +122,7 @@ export type ServerFields = { typeof import('./filesystem').buildCustomRoute >[] setAppIsrStatus?: (key: string, value: false | number | null) => void + resetFetch?: () => void } async function verifyTypeScript(opts: SetupOpts) { @@ -181,8 +182,13 @@ async function startWatcher(opts: SetupOpts) { logging: nextConfig.logging !== false, }) + const originalFetch = global.fetch + const resetFetch = () => { + global.fetch = originalFetch + } + const hotReloader: NextJsHotReloaderInterface = opts.turbo - ? await createHotReloaderTurbopack(opts, serverFields, distDir) + ? await createHotReloaderTurbopack(opts, serverFields, distDir, resetFetch) : new HotReloaderWebpack(opts.dir, { appDir, pagesDir, @@ -193,6 +199,7 @@ async function startWatcher(opts: SetupOpts) { telemetry: opts.telemetry, rewrites: opts.fsChecker.rewrites, previewProps: opts.fsChecker.prerenderManifest.preview, + resetFetch: resetFetch, }) await hotReloader.start() diff --git a/test/development/app-dir/dev-fetch-hmr/app/layout.tsx b/test/development/app-dir/dev-fetch-hmr/app/layout.tsx new file mode 100644 index 00000000000000..15cc07ddd341f9 --- /dev/null +++ b/test/development/app-dir/dev-fetch-hmr/app/layout.tsx @@ -0,0 +1,36 @@ +import React from 'react' +import { ReactNode } from 'react' + +const magicNumber = Math.random() +const originalFetch = globalThis.fetch + +// @ts-ignore +globalThis.fetch = async ( + resource: URL | RequestInfo, + options?: RequestInit +) => { + let url: string + if (typeof resource === 'string') { + url = resource + } else { + url = resource instanceof URL ? resource.href : resource.url + } + + if (url === 'secret') { + return 'monkey patching is fun' + } + + if (url === 'magic-number') { + return magicNumber + } + + return originalFetch(resource, options) +} + +export default function Root({ children }: { children: ReactNode }) { + return ( + + {children} + + ) +} diff --git a/test/development/app-dir/dev-fetch-hmr/app/page.tsx b/test/development/app-dir/dev-fetch-hmr/app/page.tsx new file mode 100644 index 00000000000000..54cb030ee92950 --- /dev/null +++ b/test/development/app-dir/dev-fetch-hmr/app/page.tsx @@ -0,0 +1,11 @@ +export default async function Page() { + // touch to trigger HMR + const secret = (await fetch('secret')) as any + const magicNumber = (await fetch('magic-number')) as any + return ( + <> +
{secret}
+
{magicNumber}
+ + ) +} diff --git a/test/development/app-dir/dev-fetch-hmr/dev-fetch-hmr.test.ts b/test/development/app-dir/dev-fetch-hmr/dev-fetch-hmr.test.ts new file mode 100644 index 00000000000000..456e0f4bd5a7ce --- /dev/null +++ b/test/development/app-dir/dev-fetch-hmr/dev-fetch-hmr.test.ts @@ -0,0 +1,34 @@ +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() + expect(magicNumber).toBe(magicNumber2) + + // trigger HMR + await next.patchFile('app/page.tsx', (content) => + content.replace('touch to trigger HMR', 'touch to trigger HMR 2') + ) + + await retry(async () => { + const html3 = await next.render('/') + const magicNumber3 = cheerio.load(html3)('#magic-number').text() + expect(html3).toContain('monkey patching is fun') + expect(magicNumber).not.toEqual(magicNumber3) + }) + }) +}) diff --git a/test/development/app-dir/dev-fetch-hmr/next.config.js b/test/development/app-dir/dev-fetch-hmr/next.config.js new file mode 100644 index 00000000000000..807126e4cf0bf5 --- /dev/null +++ b/test/development/app-dir/dev-fetch-hmr/next.config.js @@ -0,0 +1,6 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = {} + +module.exports = nextConfig diff --git a/test/development/app-dir/dev-fetch-hmr/tsconfig.json b/test/development/app-dir/dev-fetch-hmr/tsconfig.json new file mode 100644 index 00000000000000..1d4f624eff7d9c --- /dev/null +++ b/test/development/app-dir/dev-fetch-hmr/tsconfig.json @@ -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"] +} From 53ef550dfdaffc8fab5daa1c96b3cce391f1f799 Mon Sep 17 00:00:00 2001 From: Jimmy Lai Date: Fri, 26 Jul 2024 15:44:21 +0200 Subject: [PATCH 2/8] update test --- test/development/app-dir/dev-fetch-hmr/app/layout.tsx | 10 ++++++---- test/development/app-dir/dev-fetch-hmr/app/page.tsx | 9 +++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/test/development/app-dir/dev-fetch-hmr/app/layout.tsx b/test/development/app-dir/dev-fetch-hmr/app/layout.tsx index 15cc07ddd341f9..855ac5e51ca232 100644 --- a/test/development/app-dir/dev-fetch-hmr/app/layout.tsx +++ b/test/development/app-dir/dev-fetch-hmr/app/layout.tsx @@ -4,6 +4,8 @@ import { ReactNode } from 'react' const magicNumber = Math.random() const originalFetch = globalThis.fetch +console.log('monkey patching fetch') + // @ts-ignore globalThis.fetch = async ( resource: URL | RequestInfo, @@ -16,12 +18,12 @@ globalThis.fetch = async ( url = resource instanceof URL ? resource.href : resource.url } - if (url === 'secret') { - return 'monkey patching is fun' + if (url === 'http://fake.url/secret') { + return new Response('monkey patching is fun') } - if (url === 'magic-number') { - return magicNumber + if (url === 'http://fake.url/magic-number') { + return new Response(magicNumber.toString()) } return originalFetch(resource, options) diff --git a/test/development/app-dir/dev-fetch-hmr/app/page.tsx b/test/development/app-dir/dev-fetch-hmr/app/page.tsx index 54cb030ee92950..49f122af9bfe0e 100644 --- a/test/development/app-dir/dev-fetch-hmr/app/page.tsx +++ b/test/development/app-dir/dev-fetch-hmr/app/page.tsx @@ -1,7 +1,12 @@ export default async function Page() { // touch to trigger HMR - const secret = (await fetch('secret')) as any - const magicNumber = (await fetch('magic-number')) as any + 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 ( <>
{secret}
From 8bc526251bed270a65287f6c057d2343db71454f Mon Sep 17 00:00:00 2001 From: Jimmy Lai Date: Fri, 26 Jul 2024 15:57:11 +0200 Subject: [PATCH 3/8] trying something worst --- packages/next/src/server/lib/patch-fetch.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/next/src/server/lib/patch-fetch.ts b/packages/next/src/server/lib/patch-fetch.ts index 44cac09a95dab8..7510544e80cb19 100644 --- a/packages/next/src/server/lib/patch-fetch.ts +++ b/packages/next/src/server/lib/patch-fetch.ts @@ -34,7 +34,12 @@ type PatchedFetcher = Fetcher & { readonly _nextOriginalFetch: Fetcher } -let isFetchPatched = false +const nextPatchSymbol = Symbol.for('next-patch') + +function isFetchPatched() { + // @ts-ignore + return globalThis[nextPatchSymbol] === true +} export function validateRevalidate( revalidateVal: unknown, @@ -806,20 +811,21 @@ export function createPatchedFetcher( patched.__nextPatched = true as const patched.__nextGetStaticStore = () => staticGenerationAsyncStorage patched._nextOriginalFetch = originFetch - isFetchPatched = true + // @ts-ignore + globalThis[nextPatchSymbol] = true return patched } - // we patch fetch to collect cache information used for // determining if a page is static or not export function patchFetch(options: PatchableModule) { // If we've already patched fetch, we should not patch it again. - if (isFetchPatched) return + if (isFetchPatched()) return // Grab the original fetch function. We'll attach this so we can use it in // the patched fetch function. const original = createDedupeFetch(globalThis.fetch) + // Set the global fetch to the patched fetch. globalThis.fetch = createPatchedFetcher(original, options) } From 08fc0111e437875629d75b7ba8ff6bd3e766b473 Mon Sep 17 00:00:00 2001 From: Jimmy Lai Date: Fri, 26 Jul 2024 16:20:48 +0200 Subject: [PATCH 4/8] sorry --- packages/next/src/server/lib/patch-fetch.ts | 6 +++--- .../next/src/server/lib/router-utils/setup-dev-bundler.ts | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/next/src/server/lib/patch-fetch.ts b/packages/next/src/server/lib/patch-fetch.ts index 7510544e80cb19..885dacd9124d2d 100644 --- a/packages/next/src/server/lib/patch-fetch.ts +++ b/packages/next/src/server/lib/patch-fetch.ts @@ -34,11 +34,11 @@ type PatchedFetcher = Fetcher & { readonly _nextOriginalFetch: Fetcher } -const nextPatchSymbol = Symbol.for('next-patch') +export const NEXT_PATCH_SYMBOL = Symbol.for('next-patch') function isFetchPatched() { // @ts-ignore - return globalThis[nextPatchSymbol] === true + return globalThis[NEXT_PATCH_SYMBOL] === true } export function validateRevalidate( @@ -812,7 +812,7 @@ export function createPatchedFetcher( patched.__nextGetStaticStore = () => staticGenerationAsyncStorage patched._nextOriginalFetch = originFetch // @ts-ignore - globalThis[nextPatchSymbol] = true + globalThis[NEXT_PATCH_SYMBOL] = true return patched } diff --git a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts index 83e7a5c4ca2f69..09fddf90adccab 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts @@ -89,6 +89,7 @@ import { normalizeMetadataPageToRoute } from '../../../lib/metadata/get-metadata import { createEnvDefinitions } from '../experimental/create-env-definitions' import { JsConfigPathsPlugin } from '../../../build/webpack/plugins/jsconfig-paths-plugin' import { store as consoleStore } from '../../../build/output/store' +import { NEXT_PATCH_SYMBOL } from '../patch-fetch' export type SetupOpts = { renderServer: LazyRenderServerInstance @@ -185,6 +186,8 @@ async function startWatcher(opts: SetupOpts) { const originalFetch = global.fetch const resetFetch = () => { global.fetch = originalFetch + // @ts-ignore + global[NEXT_PATCH_SYMBOL] = false } const hotReloader: NextJsHotReloaderInterface = opts.turbo From 98834266756ffbd7e75e479f32d929f3d3fcbf66 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Tue, 6 Aug 2024 18:00:51 +0200 Subject: [PATCH 5/8] Lift `resetFetch` into `router-server` --- packages/next/src/server/lib/router-server.ts | 18 ++++++++++++++---- .../lib/router-utils/setup-dev-bundler.ts | 13 +++---------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/next/src/server/lib/router-server.ts b/packages/next/src/server/lib/router-server.ts index 01de5abfa5f39a..5b97f1ef15600f 100644 --- a/packages/next/src/server/lib/router-server.ts +++ b/packages/next/src/server/lib/router-server.ts @@ -46,6 +46,7 @@ import { type AppIsrManifestAction, } from '../dev/hot-reloader-types' import { normalizedAssetPrefix } from '../../shared/lib/normalized-asset-prefix' +import { NEXT_PATCH_SYMBOL } from './patch-fetch' const debug = setupDebug('next:router-server:main') const isNextFont = (pathname: string | null) => @@ -109,6 +110,8 @@ export async function initialize(opts: { let devBundlerService: DevBundlerService | undefined + let originalFetch = globalThis.fetch + if (opts.dev) { const { Telemetry } = require('../../telemetry/storage') as typeof import('../../telemetry/storage') @@ -121,6 +124,12 @@ export async function initialize(opts: { const { setupDevBundler } = require('./router-utils/setup-dev-bundler') as typeof import('./router-utils/setup-dev-bundler') + const resetFetch = () => { + global.fetch = originalFetch + // @ts-ignore + global[NEXT_PATCH_SYMBOL] = false + } + const setupDevBundlerSpan = opts.startServerSpan ? opts.startServerSpan.traceChild('setup-dev-bundler') : trace('setup-dev-bundler') @@ -138,6 +147,7 @@ export async function initialize(opts: { turbo: !!process.env.TURBOPACK, port: opts.port, onCleanup: opts.onCleanup, + resetFetch, }) ) @@ -591,12 +601,12 @@ export async function initialize(opts: { let requestHandler: WorkerRequestHandler = requestHandlerImpl if (config.experimental.testProxy) { // Intercept fetch and other testmode apis. - const { - wrapRequestHandlerWorker, - interceptTestApis, - } = require('next/dist/experimental/testmode/server') + const { wrapRequestHandlerWorker, interceptTestApis } = + require('next/dist/experimental/testmode/server') as typeof import('next/src/experimental/testmode/server') requestHandler = wrapRequestHandlerWorker(requestHandler) interceptTestApis() + // We treat the intercepted fetch as "original" fetch that should be reset to during HMR. + originalFetch = globalThis.fetch } requestHandlers[opts.dir] = requestHandler diff --git a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts index 09fddf90adccab..465de90fe81848 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts @@ -89,7 +89,6 @@ import { normalizeMetadataPageToRoute } from '../../../lib/metadata/get-metadata import { createEnvDefinitions } from '../experimental/create-env-definitions' import { JsConfigPathsPlugin } from '../../../build/webpack/plugins/jsconfig-paths-plugin' import { store as consoleStore } from '../../../build/output/store' -import { NEXT_PATCH_SYMBOL } from '../patch-fetch' export type SetupOpts = { renderServer: LazyRenderServerInstance @@ -105,6 +104,7 @@ export type SetupOpts = { nextConfig: NextConfigComplete port: number onCleanup: (listener: () => Promise) => void + resetFetch: () => void } export type ServerFields = { @@ -154,7 +154,7 @@ export async function propagateServerField( } async function startWatcher(opts: SetupOpts) { - const { nextConfig, appDir, pagesDir, dir } = opts + const { nextConfig, appDir, pagesDir, dir, resetFetch } = opts const { useFileSystemPublicRoutes } = nextConfig const usingTypeScript = await verifyTypeScript(opts) @@ -183,13 +183,6 @@ async function startWatcher(opts: SetupOpts) { logging: nextConfig.logging !== false, }) - const originalFetch = global.fetch - const resetFetch = () => { - global.fetch = originalFetch - // @ts-ignore - global[NEXT_PATCH_SYMBOL] = false - } - const hotReloader: NextJsHotReloaderInterface = opts.turbo ? await createHotReloaderTurbopack(opts, serverFields, distDir, resetFetch) : new HotReloaderWebpack(opts.dir, { @@ -202,7 +195,7 @@ async function startWatcher(opts: SetupOpts) { telemetry: opts.telemetry, rewrites: opts.fsChecker.rewrites, previewProps: opts.fsChecker.prerenderManifest.preview, - resetFetch: resetFetch, + resetFetch, }) await hotReloader.start() From 7faf8f55d3d53745c737b3d1b53da1fe7a507bb3 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 9 Aug 2024 09:50:13 +0200 Subject: [PATCH 6/8] Address review comments --- packages/next/src/server/lib/patch-fetch.ts | 6 ++---- packages/next/src/server/lib/router-server.ts | 5 ++--- .../app-dir/dev-fetch-hmr/app/layout.tsx | 14 +++----------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/packages/next/src/server/lib/patch-fetch.ts b/packages/next/src/server/lib/patch-fetch.ts index 885dacd9124d2d..cf4d5e67ea045a 100644 --- a/packages/next/src/server/lib/patch-fetch.ts +++ b/packages/next/src/server/lib/patch-fetch.ts @@ -37,8 +37,7 @@ type PatchedFetcher = Fetcher & { export const NEXT_PATCH_SYMBOL = Symbol.for('next-patch') function isFetchPatched() { - // @ts-ignore - return globalThis[NEXT_PATCH_SYMBOL] === true + return (globalThis as Record)[NEXT_PATCH_SYMBOL] === true } export function validateRevalidate( @@ -811,8 +810,7 @@ export function createPatchedFetcher( patched.__nextPatched = true as const patched.__nextGetStaticStore = () => staticGenerationAsyncStorage patched._nextOriginalFetch = originFetch - // @ts-ignore - globalThis[NEXT_PATCH_SYMBOL] = true + ;(globalThis as Record)[NEXT_PATCH_SYMBOL] = true return patched } diff --git a/packages/next/src/server/lib/router-server.ts b/packages/next/src/server/lib/router-server.ts index 5b97f1ef15600f..ed18f2378a71cf 100644 --- a/packages/next/src/server/lib/router-server.ts +++ b/packages/next/src/server/lib/router-server.ts @@ -125,9 +125,8 @@ export async function initialize(opts: { require('./router-utils/setup-dev-bundler') as typeof import('./router-utils/setup-dev-bundler') const resetFetch = () => { - global.fetch = originalFetch - // @ts-ignore - global[NEXT_PATCH_SYMBOL] = false + globalThis.fetch = originalFetch + ;(globalThis as Record)[NEXT_PATCH_SYMBOL] = false } const setupDevBundlerSpan = opts.startServerSpan diff --git a/test/development/app-dir/dev-fetch-hmr/app/layout.tsx b/test/development/app-dir/dev-fetch-hmr/app/layout.tsx index 855ac5e51ca232..bc8001d092e80b 100644 --- a/test/development/app-dir/dev-fetch-hmr/app/layout.tsx +++ b/test/development/app-dir/dev-fetch-hmr/app/layout.tsx @@ -4,25 +4,17 @@ import { ReactNode } from 'react' const magicNumber = Math.random() const originalFetch = globalThis.fetch -console.log('monkey patching fetch') - -// @ts-ignore globalThis.fetch = async ( resource: URL | RequestInfo, options?: RequestInit ) => { - let url: string - if (typeof resource === 'string') { - url = resource - } else { - url = resource instanceof URL ? resource.href : resource.url - } + const request = new Request(resource) - if (url === 'http://fake.url/secret') { + if (request.url === 'http://fake.url/secret') { return new Response('monkey patching is fun') } - if (url === 'http://fake.url/magic-number') { + if (request.url === 'http://fake.url/magic-number') { return new Response(magicNumber.toString()) } From fa5eae8cdbe9fb1645bc40822bb181113620fc12 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 9 Aug 2024 11:16:15 +0200 Subject: [PATCH 7/8] Prevent regression of resetting `fetch` --- test/development/app-dir/dev-fetch-hmr/app/layout.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/development/app-dir/dev-fetch-hmr/app/layout.tsx b/test/development/app-dir/dev-fetch-hmr/app/layout.tsx index bc8001d092e80b..2a034e34f48ca7 100644 --- a/test/development/app-dir/dev-fetch-hmr/app/layout.tsx +++ b/test/development/app-dir/dev-fetch-hmr/app/layout.tsx @@ -4,10 +4,16 @@ import { ReactNode } from 'react' const magicNumber = Math.random() const originalFetch = globalThis.fetch -globalThis.fetch = async ( +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') { From 6f3b8f77ed01fcf24363f90755f52056918ca310 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 20 Aug 2024 16:42:51 +0200 Subject: [PATCH 8/8] Ensure HMR was applied --- test/development/app-dir/dev-fetch-hmr/app/page.tsx | 2 +- .../app-dir/dev-fetch-hmr/dev-fetch-hmr.test.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/test/development/app-dir/dev-fetch-hmr/app/page.tsx b/test/development/app-dir/dev-fetch-hmr/app/page.tsx index 49f122af9bfe0e..ba0a22091d0f72 100644 --- a/test/development/app-dir/dev-fetch-hmr/app/page.tsx +++ b/test/development/app-dir/dev-fetch-hmr/app/page.tsx @@ -1,5 +1,4 @@ export default async function Page() { - // touch to trigger HMR const secret = (await fetch('http://fake.url/secret').then((res) => res.text() )) as any @@ -9,6 +8,7 @@ export default async function Page() { return ( <> +
touch to trigger HMR
{secret}
{magicNumber}
diff --git a/test/development/app-dir/dev-fetch-hmr/dev-fetch-hmr.test.ts b/test/development/app-dir/dev-fetch-hmr/dev-fetch-hmr.test.ts index 456e0f4bd5a7ce..1457e456d676f4 100644 --- a/test/development/app-dir/dev-fetch-hmr/dev-fetch-hmr.test.ts +++ b/test/development/app-dir/dev-fetch-hmr/dev-fetch-hmr.test.ts @@ -17,7 +17,10 @@ describe('dev-fetch-hmr', () => { const html2 = await next.render('/') expect(html2).toContain('monkey patching is fun') const magicNumber2 = cheerio.load(html2)('#magic-number').text() - expect(magicNumber).toBe(magicNumber2) + // 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) => @@ -26,9 +29,12 @@ describe('dev-fetch-hmr', () => { 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') - expect(magicNumber).not.toEqual(magicNumber3) + // Module was re-evaluated + expect(magicNumber3).not.toEqual(magicNumber) }) }) })