From 856ae4e8b0d98a54241c22e7ddabac66f4a86648 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Fri, 15 Sep 2023 20:28:45 -0400 Subject: [PATCH 1/5] Turbopack: Setup HMR for client-only changes in App dir --- .../next/src/client/app-next-dev-turbopack.ts | 1 + .../react-dev-overlay/hot-reloader-client.tsx | 26 ++++--- .../internal/helpers/use-websocket.ts | 68 +++++++++++++++++++ .../next/src/server/dev/hot-reloader-types.ts | 7 +- .../src/server/lib/router-utils/setup-dev.ts | 10 ++- 5 files changed, 97 insertions(+), 15 deletions(-) diff --git a/packages/next/src/client/app-next-dev-turbopack.ts b/packages/next/src/client/app-next-dev-turbopack.ts index 12bc07d9e3894..8af1e233b4ef2 100644 --- a/packages/next/src/client/app-next-dev-turbopack.ts +++ b/packages/next/src/client/app-next-dev-turbopack.ts @@ -3,6 +3,7 @@ import { appBootstrap } from './app-bootstrap' window.next.version += '-turbo' +;(self as any).__webpack_hash__ = '' appBootstrap(() => { require('./app-turbopack') diff --git a/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx b/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx index 84c3569834abb..b763da9d515ac 100644 --- a/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx +++ b/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react' +import { ReactNode, useRef } from 'react' import React, { useCallback, useEffect, @@ -30,6 +30,7 @@ import { } from './internal/helpers/use-error-handler' import { useSendMessage, + useTurbopack, useWebsocket, useWebsocketPing, } from './internal/helpers/use-websocket' @@ -38,6 +39,8 @@ import type { VersionInfo } from '../../../server/dev/parse-version-info' import { HMR_ACTIONS_SENT_TO_BROWSER, HMR_ACTION_TYPES, + TurbopackConnectedAction, + TurbopackMessageAction, } from '../../../server/dev/hot-reloader-types' interface Dispatcher { @@ -204,17 +207,12 @@ function tryApplyUpdates( } function processMessage( - e: any, + obj: HMR_ACTION_TYPES, sendMessage: any, router: ReturnType, dispatcher: Dispatcher ) { - let obj: HMR_ACTION_TYPES | undefined - try { - obj = JSON.parse(e.data) - } catch {} - - if (!obj || !('action' in obj)) { + if (!('action' in obj)) { return } @@ -475,13 +473,23 @@ export default function HotReload({ const webSocketRef = useWebsocket(assetPrefix) useWebsocketPing(webSocketRef) const sendMessage = useSendMessage(webSocketRef) + const processTurbopackMessage = useTurbopack(sendMessage) const router = useRouter() useEffect(() => { const handler = (event: MessageEvent) => { + // webpack's heartbeat event. + if (event.data === '\uD83D\uDC93') { + return + } + try { - processMessage(event, sendMessage, router, dispatcher) + const obj = JSON.parse(event.data) + const handledByTurbopack = processTurbopackMessage?.(obj) + if (!handledByTurbopack) { + processMessage(obj, sendMessage, router, dispatcher) + } } catch (err: any) { console.warn( '[HMR] Invalid message: ' + event.data + '\n' + (err?.stack ?? '') diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts index d37fce9851e91..ecb6a9149022e 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts @@ -1,6 +1,11 @@ import { useCallback, useContext, useEffect, useRef } from 'react' import { GlobalLayoutRouterContext } from '../../../../../shared/lib/app-router-context.shared-runtime' import { getSocketUrl } from './get-socket-url' +import type { + HMR_ACTION_TYPES, + TurbopackConnectedAction, + TurbopackMessageAction, +} from '../../../../../server/dev/hot-reloader-types' export function useWebsocket(assetPrefix: string) { const webSocketRef = useRef() @@ -32,6 +37,69 @@ export function useSendMessage(webSocketRef: ReturnType) { return sendMessage } +export function useTurbopack(sendMessage: ReturnType) { + console.log('useTurbopack') + const turbopackState = useRef<{ + init: boolean + queue: Array | undefined + callback: ((msg: HMR_ACTION_TYPES) => void) | undefined + }>({ + init: false, + // Until the dynamic import resolves, queue any turbopack messages which will be replayed. + queue: [], + callback: undefined, + }) + + const processTurbopackMessage = useCallback((msg: HMR_ACTION_TYPES) => { + if ('type' in msg && msg.type?.startsWith('turbopack-')) { + const { callback, queue } = turbopackState.current + if (callback) { + console.log('received', msg) + callback(msg) + } else { + console.log('queueing', msg) + queue!.push(msg) + } + return true + } + return false + }, []) + + useEffect(() => { + const { current } = turbopackState + // TODO: only install if `process.turbopack` set. + if (current.init) { + return + } + console.log('initializing turbopack hmr') + current.init = true + + import( + // @ts-expect-error requires "moduleResolution": "node16" in tsconfig.json and not .ts extension + '@vercel/turbopack-ecmascript-runtime/dev/client/hmr-client.ts' + ).then(({ connect }) => { + const { current } = turbopackState + console.log('imported turbopack hmr') + connect({ + addMessageListener(cb: (msg: HMR_ACTION_TYPES) => void) { + console.log('turbopack hmr setup') + current.callback = cb + + // Replay all Turbopack messages before we were able to establish the HMR client. + for (const msg of current.queue!) { + console.log('replaying', msg) + cb(msg) + } + current.queue = undefined + }, + sendMessage, + }) + }) + }, []) + + return processTurbopackMessage +} + export function useWebsocketPing( websocketRef: ReturnType ) { diff --git a/packages/next/src/server/dev/hot-reloader-types.ts b/packages/next/src/server/dev/hot-reloader-types.ts index 7428e426001ee..fb4f3f03e2470 100644 --- a/packages/next/src/server/dev/hot-reloader-types.ts +++ b/packages/next/src/server/dev/hot-reloader-types.ts @@ -28,7 +28,7 @@ interface ServerErrorAction { errorJSON: string } -interface TurboPackMessageAction { +export interface TurbopackMessageAction { type: HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_MESSAGE data: TurbopackUpdate | TurbopackUpdate[] } @@ -87,12 +87,13 @@ interface DevPagesManifestUpdateAction { ] } -export interface TurboPackConnectedAction { +export interface TurbopackConnectedAction { type: HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_CONNECTED } export type HMR_ACTION_TYPES = - | TurboPackMessageAction + | TurbopackMessageAction + | TurbopackConnectedAction | BuildingAction | SyncAction | BuiltAction diff --git a/packages/next/src/server/lib/router-utils/setup-dev.ts b/packages/next/src/server/lib/router-utils/setup-dev.ts index 8db318f8b2037..77c345b06ad02 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev.ts @@ -100,7 +100,7 @@ import { HMR_ACTION_TYPES, NextJsHotReloaderInterface, ReloadPageAction, - TurboPackConnectedAction, + TurbopackConnectedAction, } from '../../dev/hot-reloader-types' import type { Update as TurbopackUpdate } from '../../../build/swc' import { debounce } from '../../utils' @@ -356,10 +356,14 @@ async function startWatcher(opts: SetupOpts) { } hotReloader.send({ - action: HMR_ACTIONS_SENT_TO_BROWSER.BUILT, + action: HMR_ACTIONS_SENT_TO_BROWSER.SYNC, hash: String(++hmrHash), errors: [...errors.values()], warnings: [], + versionInfo: { + installed: '0.0.0', + staleness: 'unknown', + }, }) hmrBuilding = false @@ -984,7 +988,7 @@ async function startWatcher(opts: SetupOpts) { } }) - const turbopackConnected: TurboPackConnectedAction = { + const turbopackConnected: TurbopackConnectedAction = { type: HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_CONNECTED, } client.send(JSON.stringify(turbopackConnected)) From bea79589eca88a34fff10c6144259afa4cd9203d Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Fri, 15 Sep 2023 20:43:53 -0400 Subject: [PATCH 2/5] Remove logs, lint, add TODO issue --- .../components/react-dev-overlay/hot-reloader-client.tsx | 4 +--- .../react-dev-overlay/internal/helpers/use-websocket.ts | 9 +-------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx b/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx index b763da9d515ac..6306273572910 100644 --- a/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx +++ b/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useRef } from 'react' +import { ReactNode } from 'react' import React, { useCallback, useEffect, @@ -39,8 +39,6 @@ import type { VersionInfo } from '../../../server/dev/parse-version-info' import { HMR_ACTIONS_SENT_TO_BROWSER, HMR_ACTION_TYPES, - TurbopackConnectedAction, - TurbopackMessageAction, } from '../../../server/dev/hot-reloader-types' interface Dispatcher { diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts index ecb6a9149022e..afb26aa6fc6fa 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts @@ -38,7 +38,6 @@ export function useSendMessage(webSocketRef: ReturnType) { } export function useTurbopack(sendMessage: ReturnType) { - console.log('useTurbopack') const turbopackState = useRef<{ init: boolean queue: Array | undefined @@ -54,10 +53,8 @@ export function useTurbopack(sendMessage: ReturnType) { if ('type' in msg && msg.type?.startsWith('turbopack-')) { const { callback, queue } = turbopackState.current if (callback) { - console.log('received', msg) callback(msg) } else { - console.log('queueing', msg) queue!.push(msg) } return true @@ -67,11 +64,10 @@ export function useTurbopack(sendMessage: ReturnType) { useEffect(() => { const { current } = turbopackState - // TODO: only install if `process.turbopack` set. + // TODO(WEB-1589): only install if `process.turbopack` set. if (current.init) { return } - console.log('initializing turbopack hmr') current.init = true import( @@ -79,15 +75,12 @@ export function useTurbopack(sendMessage: ReturnType) { '@vercel/turbopack-ecmascript-runtime/dev/client/hmr-client.ts' ).then(({ connect }) => { const { current } = turbopackState - console.log('imported turbopack hmr') connect({ addMessageListener(cb: (msg: HMR_ACTION_TYPES) => void) { - console.log('turbopack hmr setup') current.callback = cb // Replay all Turbopack messages before we were able to establish the HMR client. for (const msg of current.queue!) { - console.log('replaying', msg) cb(msg) } current.queue = undefined From ebd15950af10259a623cacb3b2cac100d5ef8259 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Fri, 15 Sep 2023 20:56:33 -0400 Subject: [PATCH 3/5] Lint --- .../components/react-dev-overlay/hot-reloader-client.tsx | 2 +- .../react-dev-overlay/internal/helpers/use-websocket.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx b/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx index 6306273572910..15527575da3c0 100644 --- a/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx +++ b/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx @@ -501,7 +501,7 @@ export default function HotReload({ } return () => websocket && websocket.removeEventListener('message', handler) - }, [sendMessage, router, webSocketRef, dispatcher]) + }, [sendMessage, router, webSocketRef, dispatcher, processTurbopackMessage]) return ( diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts index afb26aa6fc6fa..f2c3188b9e6ff 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-websocket.ts @@ -63,12 +63,12 @@ export function useTurbopack(sendMessage: ReturnType) { }, []) useEffect(() => { - const { current } = turbopackState + const { current: initCurrent } = turbopackState // TODO(WEB-1589): only install if `process.turbopack` set. - if (current.init) { + if (initCurrent.init) { return } - current.init = true + initCurrent.init = true import( // @ts-expect-error requires "moduleResolution": "node16" in tsconfig.json and not .ts extension @@ -88,7 +88,7 @@ export function useTurbopack(sendMessage: ReturnType) { sendMessage, }) }) - }, []) + }, [sendMessage]) return processTurbopackMessage } From 8bb278291f93932bbdec41464fad12ad18a41bee Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Fri, 15 Sep 2023 21:59:04 -0400 Subject: [PATCH 4/5] Add noop connect impl for webpack builds --- packages/next/src/build/webpack-config.ts | 4 ++++ packages/next/src/client/dev/noop-turbopack-hmr.ts | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 packages/next/src/client/dev/noop-turbopack-hmr.ts diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index aa80e4c6fbff4..9878809347361 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -477,6 +477,10 @@ function createRSCAliases( ] = `next/dist/compiled/scheduler${bundledReactChannel}/tracing-profiling` } + alias[ + '@vercel/turbopack-ecmascript-runtime/dev/client/hmr-client.ts' + ] = `next/dist/client/dev/noop-turbopack-hmr` + return alias } diff --git a/packages/next/src/client/dev/noop-turbopack-hmr.ts b/packages/next/src/client/dev/noop-turbopack-hmr.ts new file mode 100644 index 0000000000000..6b81e0b7deea2 --- /dev/null +++ b/packages/next/src/client/dev/noop-turbopack-hmr.ts @@ -0,0 +1,3 @@ +// The Turbopack HMR client can't be properly omitted at the moment (WEB-1589), +// so instead we remap its import to this file in webpack builds. +export function connect() {} From e2e1ae9874351610fd0698f93eba7bc66b18fae7 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Sat, 16 Sep 2023 16:18:12 -0400 Subject: [PATCH 5/5] Remove webpack heartbeat check --- .../components/react-dev-overlay/hot-reloader-client.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx b/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx index 15527575da3c0..04f8c4ecbce63 100644 --- a/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx +++ b/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx @@ -477,11 +477,6 @@ export default function HotReload({ useEffect(() => { const handler = (event: MessageEvent) => { - // webpack's heartbeat event. - if (event.data === '\uD83D\uDC93') { - return - } - try { const obj = JSON.parse(event.data) const handledByTurbopack = processTurbopackMessage?.(obj)