Skip to content
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

Turbopack: Setup HMR for client-only changes in App dir #55464

Merged
merged 5 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/next/src/client/app-next-dev-turbopack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { appBootstrap } from './app-bootstrap'

window.next.version += '-turbo'
;(self as any).__webpack_hash__ = ''

appBootstrap(() => {
require('./app-turbopack')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactNode } from 'react'
import { ReactNode, useRef } from 'react'
import React, {
useCallback,
useEffect,
Expand Down Expand Up @@ -30,6 +30,7 @@ import {
} from './internal/helpers/use-error-handler'
import {
useSendMessage,
useTurbopack,
useWebsocket,
useWebsocketPing,
} from './internal/helpers/use-websocket'
Expand All @@ -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 {
Expand Down Expand Up @@ -204,17 +207,12 @@ function tryApplyUpdates(
}

function processMessage(
e: any,
obj: HMR_ACTION_TYPES,
sendMessage: any,
router: ReturnType<typeof useRouter>,
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
}

Expand Down Expand Up @@ -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<any>) => {
// webpack's heartbeat event.
if (event.data === '\uD83D\uDC93') {
return
jridgewell marked this conversation as resolved.
Show resolved Hide resolved
}

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 ?? '')
Expand Down
Original file line number Diff line number Diff line change
@@ -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<WebSocket>()
Expand Down Expand Up @@ -32,6 +37,69 @@ export function useSendMessage(webSocketRef: ReturnType<typeof useWebsocket>) {
return sendMessage
}

export function useTurbopack(sendMessage: ReturnType<typeof useSendMessage>) {
console.log('useTurbopack')
const turbopackState = useRef<{
init: boolean
queue: Array<TurbopackConnectedAction | TurbopackMessageAction> | 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<typeof useWebsocket>
) {
Expand Down
7 changes: 4 additions & 3 deletions packages/next/src/server/dev/hot-reloader-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface ServerErrorAction {
errorJSON: string
}

interface TurboPackMessageAction {
export interface TurbopackMessageAction {
type: HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_MESSAGE
data: TurbopackUpdate | TurbopackUpdate[]
}
Expand Down Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions packages/next/src/server/lib/router-utils/setup-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might affect pages too. Need to test it again...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They behave the same in code 1 2 3 4, with the only difference being that BUILT is treated as a "hot update" and tries to apply webpack HMR stuff 1 2. We don't want the webpack HMR stuff, so it works fine in both App and Pages.

hash: String(++hmrHash),
errors: [...errors.values()],
warnings: [],
versionInfo: {
installed: '0.0.0',
staleness: 'unknown',
},
})
hmrBuilding = false

Expand Down Expand Up @@ -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))
Expand Down
Loading