Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 4 additions & 5 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
"pages/**",
"server/**",
"some-layer/**"
],
"ignoreDependencies": [
"@nuxt/test-utils",
"nuxt"
]
},
"packages/nuxt-cli": {
"entry": [
"src/index.ts",
"test/fixtures/*"
],
"ignoreDependencies": [
"c12",
"clipboardy",
Expand All @@ -34,7 +34,6 @@
"h3",
"httpxy",
"jiti",
"listhen",
"nypm",
"ofetch",
"ohash",
Expand Down
1 change: 1 addition & 0 deletions packages/nuxi/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const command = defineCommand({
url: devProxy.listener.url,
urls,
https: devProxy.listener.https,
addr: devProxy.listener.address,
},
// if running with nuxt v4 or `NUXT_SOCKET=1`, we use the socket listener
// otherwise pass 'true' to listen on a random port instead
Expand Down
52 changes: 40 additions & 12 deletions packages/nuxi/src/dev/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { NuxtConfig } from '@nuxt/schema'
import type { ListenOptions } from 'listhen'
import type { NuxtDevContext, NuxtDevIPCMessage, NuxtDevServer, NuxtParentIPCMessage } from './utils'
import type { NuxtDevContext, NuxtDevIPCMessage, NuxtParentIPCMessage } from './utils'

import process from 'node:process'
import defu from 'defu'
import { createNuxtDevServer, resolveDevServerDefaults, resolveDevServerOverrides } from './utils'
import { listen } from 'listhen'
import { createSocketListener } from './socket'
import { NuxtDevServer, resolveDevServerDefaults, resolveDevServerOverrides } from './utils'

const start = Date.now()

Expand Down Expand Up @@ -52,22 +54,48 @@ export async function initialize(devContext: NuxtDevContext, ctx: InitializeOpti
https: devContext.proxy?.https,
}, devContext.publicURLs)

// _PORT is used by `@nuxt/test-utils` to launch the dev server on a specific port
const listenOptions = _listenOptions === true || process.env._PORT
? { port: process.env._PORT ?? 0, hostname: '127.0.0.1', showURL: false }
: _listenOptions

// Init Nuxt dev
const devServer = await createNuxtDevServer({
// Initialize dev server
const devServer = new NuxtDevServer({
cwd: devContext.cwd,
overrides: defu(ctx.data?.overrides, devServerOverrides),
overrides: defu(
ctx.data?.overrides,
({ extends: devContext.args.extends } satisfies NuxtConfig) as NuxtConfig,
devServerOverrides,
),
defaults: devServerDefaults,
logLevel: devContext.args.logLevel as 'silent' | 'info' | 'verbose',
clear: !!devContext.args.clear,
dotenv: { cwd: devContext.cwd, fileName: devContext.args.dotenv },
envName: devContext.args.envName,
devContext,
}, listenOptions)
devContext: {
proxy: devContext.proxy,
},
})

// _PORT is used by `@nuxt/test-utils` to launch the dev server on a specific port
const listenOptions = _listenOptions === true || process.env._PORT
? { port: process.env._PORT ?? 0, hostname: '127.0.0.1', showURL: false }
: _listenOptions

// Attach internal listener
devServer.listener = listenOptions
? await listen(devServer.handler, listenOptions)
: await createSocketListener(devServer.handler, devContext.proxy?.addr)

if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.debug(`Using ${listenOptions ? 'network' : 'socket'} listener for Nuxt dev server.`)
}

// Merge interface with public context
devServer.listener._url = devServer.listener.url
if (devContext.proxy?.url) {
devServer.listener.url = devContext.proxy.url
}
if (devContext.proxy?.urls) {
const _getURLs = devServer.listener.getURLs.bind(devServer.listener)
devServer.listener.getURLs = async () => Array.from(new Set([...devContext.proxy?.urls || [], ...(await _getURLs())]))
}

let address: string

Expand Down
11 changes: 4 additions & 7 deletions packages/nuxi/src/dev/socket.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { RequestListener } from 'node:http'
import type { AddressInfo } from 'node:net'
import { existsSync, unlinkSync } from 'node:fs'
import { Server } from 'node:http'
import os from 'node:os'
Expand Down Expand Up @@ -43,7 +44,7 @@ export function parseSocketURL(url: string): { socketPath: string, protocol: 'ht
return { socketPath, protocol: ssl ? 'https' : 'http' }
}

export async function createSocketListener(handler: RequestListener, ssl = false) {
export async function createSocketListener(handler: RequestListener, proxyAddress?: AddressInfo) {
const socketPath = generateSocketPath('nuxt-dev')
const server = new Server(handler)

Expand All @@ -56,14 +57,10 @@ export async function createSocketListener(handler: RequestListener, ssl = false
}
}
await new Promise<void>(resolve => server.listen({ path: socketPath }, resolve))
const url = formatSocketURL(socketPath, ssl)
const url = formatSocketURL(socketPath)
return {
url,
address: {
socketPath,
address: 'localhost',
port: 3000,
},
address: { address: 'localhost', port: 3000, ...proxyAddress, socketPath },
async close() {
try {
server.removeAllListeners()
Expand Down
43 changes: 5 additions & 38 deletions packages/nuxi/src/dev/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { pathToFileURL } from 'node:url'
import defu from 'defu'
import { resolveModulePath } from 'exsolve'
import { toNodeListener } from 'h3'
import { listen } from 'listhen'
import { resolve } from 'pathe'
import { debounce } from 'perfect-debounce'
import { provider } from 'std-env'
Expand All @@ -26,7 +25,7 @@ import { loadKit } from '../utils/kit'

import { loadNuxtManifest, resolveNuxtManifest, writeNuxtManifest } from '../utils/nuxt'
import { renderError } from './error'
import { createSocketListener, formatSocketURL } from './socket'
import { formatSocketURL } from './socket'

export type NuxtParentIPCMessage
= | { type: 'nuxt:internal:dev:context', context: NuxtDevContext, socket?: boolean }
Expand Down Expand Up @@ -55,6 +54,7 @@ export interface NuxtDevContext {
url?: string
urls?: ListenURL[]
https?: boolean | HTTPSOptions
addr?: AddressInfo
}
}

Expand All @@ -66,38 +66,8 @@ interface NuxtDevServerOptions {
clear?: boolean
defaults: NuxtConfig
overrides: NuxtConfig
port?: string | number
loadingTemplate?: ({ loading }: { loading: string }) => string
devContext: NuxtDevContext
}

export async function createNuxtDevServer(options: NuxtDevServerOptions, listenOptions?: true | Partial<ListenOptions>) {
// Initialize dev server
const devServer = new NuxtDevServer(options)

// Attach internal listener
devServer.listener = listenOptions
? await listen(devServer.handler, typeof listenOptions === 'object'
? listenOptions
: { port: options.port ?? 0, hostname: '127.0.0.1', showURL: false })
: await createSocketListener(devServer.handler)

if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.debug(`Using ${listenOptions ? 'network' : 'socket'} listener for Nuxt dev server.`)
}

// Merge interface with public context
devServer.listener._url = devServer.listener.url
if (options.devContext.proxy?.url) {
devServer.listener.url = options.devContext.proxy.url
}
if (options.devContext.proxy?.urls) {
const _getURLs = devServer.listener.getURLs.bind(devServer.listener)
devServer.listener.getURLs = async () => Array.from(new Set([...options.devContext.proxy?.urls || [], ...(await _getURLs())]))
}

return devServer
devContext: Pick<NuxtDevContext, 'proxy'>
}

// https://regex101.com/r/7HkR5c/1
Expand Down Expand Up @@ -125,7 +95,7 @@ export class NuxtDevServer extends EventEmitter<DevServerEventMap> {
handler: RequestListener
listener: Pick<Listener, 'server' | 'getURLs' | 'https' | 'url' | 'close'> & {
_url?: string
address: { socketPath: string, port: number, address: string } | AddressInfo
address: Omit<AddressInfo, 'family'> & { socketPath: string } | AddressInfo
}

constructor(private options: NuxtDevServerOptions) {
Expand Down Expand Up @@ -236,7 +206,6 @@ export class NuxtDevServer extends EventEmitter<DevServerEventMap> {
defaults: defu(this.options.defaults, devServerDefaults),
overrides: {
logLevel: this.options.logLevel as 'silent' | 'info' | 'verbose',
...(this.options.devContext.args.extends && { extends: this.options.devContext.args.extends }),
...this.options.overrides,
vite: {
clearScreen: this.options.clear,
Expand Down Expand Up @@ -319,9 +288,7 @@ export class NuxtDevServer extends EventEmitter<DevServerEventMap> {
const addr = this.listener.address
this._currentNuxt.options.devServer.host = addr.address
this._currentNuxt.options.devServer.port = addr.port
this._currentNuxt.options.devServer.url = 'socketPath' in addr
? this.options.devContext.proxy?.url || getAddressURL(addr, !!this.listener.https)
: getAddressURL(addr, !!this.listener.https)
this._currentNuxt.options.devServer.url = getAddressURL(addr, !!this.listener.https)
this._currentNuxt.options.devServer.https = this.options.devContext.proxy?.https as boolean | { key: string, cert: string }

if (this.listener.https && !process.env.NODE_TLS_REJECT_UNAUTHORIZED) {
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"youch": "^4.1.0-beta.10"
},
"devDependencies": {
"@nuxt/kit": "^4.0.0",
"@nuxt/schema": "^4.0.0",
"@types/node": "^22.16.3",
"get-port-please": "^3.2.0",
Expand Down
56 changes: 56 additions & 0 deletions packages/nuxt-cli/test/e2e/dev.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { readFile, rm } from 'node:fs/promises'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { getPort } from 'get-port-please'
import { describe, expect, it } from 'vitest'
import { runCommand } from '../../src'

const fixtureDir = fileURLToPath(new URL('../fixtures/dev', import.meta.url))

describe('dev server', () => {
it('should expose dev server address to nuxt options', { timeout: 50_000 }, async () => {
await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true })
const host = '127.0.0.1'
const port = await getPort({ host, port: 3031 })
const { result: { close } } = await runCommand('dev', [`--host=${host}`, `--port=${port}`, `--cwd=${fixtureDir}`], {
overrides: {
modules: [
fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)),
],
},
}) as any
await close()
const options = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse)
expect(options).toMatchObject({
https: false,
host,
port,
url: `http://${host}:${port}/`,
})
})

it('should respect configured devServer options', { timeout: 50_000 }, async () => {
await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true })
const host = '127.0.0.1'
const port = await getPort({ host, port: 3050 })
const { result: { close } } = await runCommand('dev', [`--cwd=${fixtureDir}`], {
overrides: {
devServer: {
host,
port,
},
modules: [
fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)),
],
},
}) as any
await close()
const options = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse)
expect(options).toMatchObject({
https: false,
host,
port,
url: `http://${host}:${port}/`,
})
})
})
12 changes: 12 additions & 0 deletions packages/nuxt-cli/test/fixtures/dev/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "fixtures-dev",
"version": "1.0.0",
"private": true,
"scripts": {
"dev:prepare": "nuxt prepare",
"test": "vitest"
},
"dependencies": {
"nuxt": "^4.0.0"
}
}
18 changes: 18 additions & 0 deletions packages/nuxt-cli/test/fixtures/log-dev-server-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { mkdir, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { defineNuxtModule, useNuxt } from '@nuxt/kit'

export default defineNuxtModule({
meta: {
name: 'nuxt-cli-test-module',
},
setup() {
const nuxt = useNuxt()

nuxt.hook('build:before', async () => {
await mkdir('.nuxt', { recursive: true })
await writeFile(join(nuxt.options.rootDir, '.nuxt/dev-server.json'), JSON.stringify(nuxt.options.devServer))
await nuxt.close()
})
},
})
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
packages:
- packages/*
- packages/nuxt-cli/test/fixtures/*
- playground
Loading