fix(dev): open nitro worker inspector from a plugin · nuxt/cli@54eebce · GitHub
Skip to content

Commit 54eebce

Browse files
committed
fix(dev): open nitro worker inspector from a plugin
1 parent aef3203 commit 54eebce

5 files changed

Lines changed: 79 additions & 98 deletions

File tree

‎packages/nuxt-cli/package.json‎

Lines changed: 1 addition & 0 deletions
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import inspector from 'node:inspector'
2+
import { createServer } from 'node:net'
3+
import process from 'node:process'
4+
import { isMainThread } from 'node:worker_threads'
5+
6+
/** Open the inspector in the Nitro dev worker once the previous worker releases the port. */
7+
export default function () {
8+
const target = process.env.__NUXT_DEV_INSPECT__
9+
if (isMainThread || !target || inspector.url()) {
10+
return
11+
}
12+
const { host, port } = JSON.parse(target)
13+
const deadline = Date.now() + 10_000
14+
const retry = () => {
15+
if (Date.now() < deadline) {
16+
setTimeout(attempt, 100).unref()
17+
}
18+
}
19+
const open = () => {
20+
try {
21+
inspector.open(port, host, false)
22+
}
23+
catch {}
24+
if (!inspector.url()) {
25+
retry()
26+
}
27+
}
28+
function attempt() {
29+
if (port === 0) {
30+
return open()
31+
}
32+
const probe = createServer()
33+
probe.unref()
34+
probe.once('error', (error) => {
35+
if (error?.code === 'EADDRINUSE') {
36+
return retry()
37+
}
38+
process.stderr.write(`Could not start the inspector on ${host}:${port}: ${error?.message ?? error}\n`)
39+
})
40+
probe.listen(port, host, () => probe.close(open))
41+
}
42+
attempt()
43+
}

‎packages/nuxt-cli/src/dev/inspect.ts‎

Lines changed: 3 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { Session } from 'node:inspector'
21
import process from 'node:process'
32
import { styleText } from 'node:util'
43
import { debug, logger } from '../utils/logger'
@@ -13,6 +12,8 @@ export interface InspectOptions {
1312
const DEFAULT_HOST = '127.0.0.1'
1413
const DEFAULT_PORT = 9229
1514

15+
export const INSPECT_ENV = '__NUXT_DEV_INSPECT__'
16+
1617
const INSPECT_ARG_RE = /^--inspect(-brk|-wait|-port)?(?:=(.*))?$/
1718

1819
/**
@@ -106,11 +107,9 @@ export async function openInspector(inspectOptions: InspectOptions): Promise<voi
106107
logger.warn(`Could not start the inspector on ${styleText('cyan', `${options.host}:${options.port}`)}: ${error instanceof Error ? error.message : error}`)
107108
}
108109

109-
inspectDevWorkers(inspector.Session, { ...inspectOptions, wait: false })
110+
process.env[INSPECT_ENV] = JSON.stringify({ host: inspectOptions.host, port: inspectOptions.port })
110111
}
111112

112-
let workerSession: Session | undefined
113-
114113
/**
115114
* Inspector address for the CLI process itself, which loads `nuxt.config` and
116115
* modules. Server code runs in a nitro worker thread on the requested port.
@@ -119,83 +118,9 @@ export function resolveProcessInspectOptions(options: InspectOptions): InspectOp
119118
return { ...options, port: options.port === 0 ? 0 : options.port + 1 }
120119
}
121120

122-
/**
123-
* Runs inside a worker thread, so it must be self-contained. Waits for the
124-
* port to be released by the worker it replaces before opening the inspector.
125-
*/
126-
function openWorkerInspector(host: string, port: number): void {
127-
// eslint-disable-next-line node/prefer-global/process
128-
const proc = (globalThis as any).process
129-
const { workerData } = proc.getBuiltinModule('node:worker_threads')
130-
if (!proc.env.NITRO_DEV_WORKER_ID && typeof workerData?.name !== 'string') {
131-
return
132-
}
133-
const inspector = proc.getBuiltinModule('node:inspector')
134-
const { createServer } = proc.getBuiltinModule('node:net')
135-
const deadline = Date.now() + 10_000
136-
const retry = (): void => {
137-
if (Date.now() < deadline) {
138-
setTimeout(attempt, 100).unref()
139-
}
140-
}
141-
const open = (): void => {
142-
try {
143-
inspector.open(port, host, false)
144-
}
145-
catch {}
146-
if (!inspector.url()) {
147-
retry()
148-
}
149-
}
150-
function attempt(): void {
151-
if (port === 0) {
152-
return open()
153-
}
154-
const probe = createServer()
155-
probe.unref()
156-
probe.once('error', (error: any) => {
157-
if (error?.code === 'EADDRINUSE') {
158-
return retry()
159-
}
160-
proc.stderr.write(`Could not start the inspector on ${host}:${port}: ${error?.message ?? error}\n`)
161-
})
162-
probe.listen(port, host, () => probe.close(open))
163-
}
164-
attempt()
165-
}
166-
167-
/** Open an inspector inside each nitro dev worker thread this process starts. */
168-
export function inspectDevWorkers(SessionConstructor: typeof Session, options: InspectOptions): void {
169-
try {
170-
workerSession?.disconnect()
171-
const session = new SessionConstructor()
172-
session.connect()
173-
const expression = `(${openWorkerInspector.toString()})(${JSON.stringify(options.host)}, ${options.port})`
174-
session.on('NodeWorker.attachedToWorker', ({ params }) => {
175-
const { sessionId } = params
176-
const message = JSON.stringify({ id: 1, method: 'Runtime.evaluate', params: { expression } })
177-
session.post('NodeWorker.sendMessageToWorker', { sessionId, message }, (error) => {
178-
if (error) {
179-
debug(`Could not open the inspector in a worker: ${error.message}`)
180-
}
181-
})
182-
})
183-
session.on('NodeWorker.receivedMessageFromWorker', ({ params }) => {
184-
session.post('NodeWorker.detach', { sessionId: params.sessionId }, () => {})
185-
})
186-
session.post('NodeWorker.enable', { waitForDebuggerOnStart: false }, () => {})
187-
workerSession = session
188-
}
189-
catch (error) {
190-
debug(`Could not watch worker threads for the inspector: ${error}`)
191-
}
192-
}
193-
194121
/** Release the inspector port so another process (a fork) can bind to it. */
195122
export async function closeInspector(): Promise<void> {
196123
try {
197-
workerSession?.disconnect()
198-
workerSession = undefined
199124
const inspector = await import('node:inspector')
200125
if (inspector.url()) {
201126
inspector.close()

‎packages/nuxt-cli/src/dev/utils.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { resolveServerBuild } from '../utils/server-build'
4141
import { createCliReport, DEFAULT_ERROR_CHANNEL, ERROR_CHANNEL_ENV, handleErrorChannelRequest, isErrorChannelRequest, isThreadRunner, openErrorBridge, publishCliProgress, renderErrorPage, resolveChannelPath, summariseReport, useErrorChannel, withErrorChannel } from './error-channel'
4242
import { sendErrorResponse } from './error-response'
4343
import { isAllowedHost, isLoopbackAddress } from './host-check'
44+
import { INSPECT_ENV } from './inspect'
4445
import { bindListener, createListener, matchesBoundTarget, openBrowser, resolveOpenURL } from './listen'
4546
import { RECOVERY_SCRIPT, withProgress } from './loading-page'
4647
import { resolveDefaultLoadingTemplate } from './loading-template'
@@ -72,13 +73,13 @@ function registerRequestContextPlugin(nitro: NitroConfigForHook, cwd: string): v
7273
}
7374
}
7475

75-
function registerCloseSocketsPlugin(nitro: NitroConfigForHook): void {
76+
function registerRuntimePlugin(nitro: NitroConfigForHook, name: 'dev-close-sockets' | 'dev-inspector'): void {
7677
try {
7778
nitro.plugins ||= []
78-
nitro.plugins.push(fileURLToPath(import.meta.resolve('@nuxt/cli/runtime/dev-close-sockets')))
79+
nitro.plugins.push(fileURLToPath(import.meta.resolve(`@nuxt/cli/runtime/${name}`)))
7980
}
8081
catch (error) {
81-
debug('Could not resolve the close sockets plugin:', error)
82+
debug(`Could not resolve the ${name} plugin:`, error)
8283
}
8384
}
8485

@@ -902,7 +903,10 @@ export class NuxtDevServer extends EventEmitter<DevServerEventMap> {
902903
hooks: {
903904
...this.options.overrides.hooks,
904905
'nitro:config': (nitro) => {
905-
registerCloseSocketsPlugin(nitro)
906+
registerRuntimePlugin(nitro, 'dev-close-sockets')
907+
if (process.env[INSPECT_ENV]) {
908+
registerRuntimePlugin(nitro, 'dev-inspector')
909+
}
906910
if (captureUIEvents) {
907911
registerRequestContextPlugin(nitro, this.options.cwd)
908912
}

‎packages/nuxt-cli/test/unit/inspect.spec.ts‎

Lines changed: 24 additions & 16 deletions

0 commit comments

Comments
 (0)