Skip to content

Commit

Permalink
NODE_OPTIONS updates (#65006)
Browse files Browse the repository at this point in the history
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

### Improving Documentation

- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide

### Adding or Updating Examples

- The "examples guidelines" are followed from our contributing doc
https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See
https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

### Adding a feature

- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md


## For Maintainers

- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Closes NEXT-
Fixes #

-->

### What?

Previously, parsing and managing the `NODE_OPTIONS` was performed using
a series of regular expressions. These were prone to bugs, and have
already caused a few issues. This moves us over to the standard
`parseArgs`
([docs](https://nodejs.org/docs/latest/api/util.html#utilparseargsconfig)):

```js
import { parseArgs } from "node:utils"
```

### Why?

This simplifies the argument parser dramatically, removing the need for
any special patterns or accommodations. No need to maintain all these
patterns when there's a lightweight built-in parser already available.

Fixes #53127
Fixes #53757
Fixes #47083
Fixes #50489
Closes #60919 
Closes #59410
Closes NEXT-3219
  • Loading branch information
wyattjoh committed Apr 25, 2024
1 parent a6dfe82 commit ab86fcf
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 77 deletions.
32 changes: 18 additions & 14 deletions packages/next/src/cli/next-dev.ts
Expand Up @@ -4,11 +4,13 @@ import '../server/lib/cpu-profile'
import type { StartServerOptions } from '../server/lib/start-server'
import {
RESTART_EXIT_CODE,
checkNodeDebugType,
getDebugPort,
getNodeDebugType,
getParsedDebugAddress,
getMaxOldSpaceSize,
getNodeOptionsWithoutInspect,
getParsedNodeOptionsWithoutInspect,
printAndExit,
formatNodeOptions,
formatDebugAddress,
} from '../server/lib/utils'
import * as Log from '../build/output/log'
import { getProjectDir } from '../lib/get-project-dir'
Expand Down Expand Up @@ -225,23 +227,25 @@ const nextDev = async (
let resolved = false
const defaultEnv = (initialEnv || process.env) as typeof process.env

let NODE_OPTIONS = getNodeOptionsWithoutInspect()
let nodeDebugType = checkNodeDebugType()

const maxOldSpaceSize = getMaxOldSpaceSize()
const nodeOptions = getParsedNodeOptionsWithoutInspect()
const nodeDebugType = getNodeDebugType()

let maxOldSpaceSize: string | number | undefined = getMaxOldSpaceSize()
if (!maxOldSpaceSize && !process.env.NEXT_DISABLE_MEM_OVERRIDE) {
const totalMem = os.totalmem()
const totalMemInMB = Math.floor(totalMem / 1024 / 1024)
NODE_OPTIONS = `${NODE_OPTIONS} --max-old-space-size=${Math.floor(
totalMemInMB * 0.5
)}`
maxOldSpaceSize = Math.floor(totalMemInMB * 0.5).toString()

nodeOptions['max-old-space-size'] = maxOldSpaceSize

// Ensure the max_old_space_size is not also set.
delete nodeOptions['max_old_space_size']
}

if (nodeDebugType) {
NODE_OPTIONS = `${NODE_OPTIONS} --${nodeDebugType}=${
getDebugPort() + 1
}`
const address = getParsedDebugAddress()
address.port = address.port + 1
nodeOptions[nodeDebugType] = formatDebugAddress(address)
}

child = fork(startServerPath, {
Expand All @@ -253,7 +257,7 @@ const nextDev = async (
NODE_EXTRA_CA_CERTS: startServerOptions.selfSignedCertificate
? startServerOptions.selfSignedCertificate.rootCA
: defaultEnv.NODE_EXTRA_CA_CERTS,
NODE_OPTIONS,
NODE_OPTIONS: formatNodeOptions(nodeOptions),
},
})

Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/lib/helpers/get-registry.ts
@@ -1,6 +1,6 @@
import { execSync } from 'child_process'
import { getPkgManager } from './get-pkg-manager'
import { getNodeOptionsWithoutInspect } from '../../server/lib/utils'
import { getFormattedNodeOptionsWithoutInspect } from '../../server/lib/utils'

/**
* Returns the package registry using the user's package manager.
Expand All @@ -14,7 +14,7 @@ export function getRegistry(baseDir: string = process.cwd()) {
const output = execSync(`${pkgManager} config get registry`, {
env: {
...process.env,
NODE_OPTIONS: getNodeOptionsWithoutInspect(),
NODE_OPTIONS: getFormattedNodeOptionsWithoutInspect(),
},
})
.toString()
Expand Down
17 changes: 11 additions & 6 deletions packages/next/src/lib/worker.ts
@@ -1,6 +1,9 @@
import type { ChildProcess } from 'child_process'
import { Worker as JestWorker } from 'next/dist/compiled/jest-worker'
import { getNodeOptionsWithoutInspect } from '../server/lib/utils'
import {
getParsedNodeOptionsWithoutInspect,
formatNodeOptions,
} from '../server/lib/utils'
type FarmOptions = ConstructorParameters<typeof JestWorker>[1]

const RESTARTED = Symbol('restarted')
Expand Down Expand Up @@ -35,18 +38,20 @@ export class Worker {
this._worker = undefined

const createWorker = () => {
// Get the node options without inspect and also remove the
// --max-old-space-size flag as it can cause memory issues.
const nodeOptions = getParsedNodeOptionsWithoutInspect()
delete nodeOptions['max-old-space-size']
delete nodeOptions['max_old_space_size']

this._worker = new JestWorker(workerPath, {
...farmOptions,
forkOptions: {
...farmOptions.forkOptions,
env: {
...((farmOptions.forkOptions?.env || {}) as any),
...process.env,
// we don't pass down NODE_OPTIONS as it can
// extra memory usage
NODE_OPTIONS: getNodeOptionsWithoutInspect()
.replace(/--max-old-space-size=[\d]{1,}/, '')
.trim(),
NODE_OPTIONS: formatNodeOptions(nodeOptions),
} as any,
},
}) as JestWorker
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/server/dev/next-dev-server.ts
Expand Up @@ -42,7 +42,7 @@ import { removePathPrefix } from '../../shared/lib/router/utils/remove-path-pref
import { Telemetry } from '../../telemetry/storage'
import { type Span, setGlobal, trace } from '../../trace'
import { findPageFile } from '../lib/find-page-file'
import { getNodeOptionsWithoutInspect } from '../lib/utils'
import { getFormattedNodeOptionsWithoutInspect } from '../lib/utils'
import { withCoalescedInvoke } from '../../lib/coalesced-function'
import { loadDefaultErrorComponents } from '../load-default-error-components'
import { DecodeError, MiddlewareNotFoundError } from '../../shared/lib/utils'
Expand Down Expand Up @@ -133,7 +133,7 @@ export default class DevServer extends Server {
// would be started if user launch Next.js in debugging mode. The number of debuggers is linked to
// the number of workers Next.js tries to launch. The only worker users are interested in debugging
// is the main Next.js one
NODE_OPTIONS: getNodeOptionsWithoutInspect(),
NODE_OPTIONS: getFormattedNodeOptionsWithoutInspect(),
},
},
}) as Worker & {
Expand Down
14 changes: 9 additions & 5 deletions packages/next/src/server/lib/start-server.ts
Expand Up @@ -17,7 +17,11 @@ import os from 'os'
import Watchpack from 'next/dist/compiled/watchpack'
import * as Log from '../../build/output/log'
import setupDebug from 'next/dist/compiled/debug'
import { RESTART_EXIT_CODE, checkNodeDebugType, getDebugPort } from './utils'
import {
RESTART_EXIT_CODE,
getFormattedDebugAddress,
getNodeDebugType,
} from './utils'
import { formatHostname } from './format-hostname'
import { initialize } from './router-server'
import { CONFIG_FILES } from '../../shared/lib/constants'
Expand Down Expand Up @@ -211,10 +215,10 @@ export async function startServer(
}
})

const nodeDebugType = checkNodeDebugType()

await new Promise<void>((resolve) => {
server.on('listening', async () => {
const nodeDebugType = getNodeDebugType()

const addr = server.address()
const actualHostname = formatHostname(
typeof addr === 'object'
Expand All @@ -236,9 +240,9 @@ export async function startServer(
}://${formattedHostname}:${port}`

if (nodeDebugType) {
const debugPort = getDebugPort()
const formattedDebugAddress = getFormattedDebugAddress()
Log.info(
`the --${nodeDebugType} option was detected, the Next.js router server should be inspected at port ${debugPort}.`
`the --${nodeDebugType} option was detected, the Next.js router server should be inspected at ${formattedDebugAddress}.`
)
}

Expand Down
@@ -1,52 +1,68 @@
/* eslint-env jest */
import { getNodeOptionsWithoutInspect } from 'next/dist/server/lib/utils'
import {
getFormattedNodeOptionsWithoutInspect,
getParsedDebugAddress,
} from './utils'

const originalNodeOptions = process.env.NODE_OPTIONS

afterAll(() => {
process.env.NODE_OPTIONS = originalNodeOptions
})

describe('getNodeOptionsWithoutInspect', () => {
describe('getParsedDebugAddress', () => {
it('supports the flag with an equal sign', () => {
process.env.NODE_OPTIONS = '--inspect=1234'
const result = getParsedDebugAddress()
expect(result).toEqual({ host: undefined, port: 1234 })
})

it('supports the flag without an equal sign', () => {
process.env.NODE_OPTIONS = '--inspect 1234'
const result = getParsedDebugAddress()
expect(result).toEqual({ host: undefined, port: 1234 })
})
})

describe('getFormattedNodeOptionsWithoutInspect', () => {
it('removes --inspect option', () => {
process.env.NODE_OPTIONS = '--other --inspect --additional'
const result = getNodeOptionsWithoutInspect()
const result = getFormattedNodeOptionsWithoutInspect()

expect(result).toBe('--other --additional')
})

it('removes --inspect option at end of line', () => {
process.env.NODE_OPTIONS = '--other --inspect'
const result = getNodeOptionsWithoutInspect()
const result = getFormattedNodeOptionsWithoutInspect()

expect(result).toBe('--other ')
expect(result).toBe('--other')
})

it('removes --inspect option with parameters', () => {
process.env.NODE_OPTIONS = '--other --inspect=0.0.0.0:1234 --additional'
const result = getNodeOptionsWithoutInspect()
const result = getFormattedNodeOptionsWithoutInspect()

expect(result).toBe('--other --additional')
})

it('removes --inspect-brk option', () => {
process.env.NODE_OPTIONS = '--other --inspect-brk --additional'
const result = getNodeOptionsWithoutInspect()
const result = getFormattedNodeOptionsWithoutInspect()

expect(result).toBe('--other --additional')
})

it('removes --inspect-brk option with parameters', () => {
process.env.NODE_OPTIONS = '--other --inspect-brk=0.0.0.0:1234 --additional'
const result = getNodeOptionsWithoutInspect()
const result = getFormattedNodeOptionsWithoutInspect()

expect(result).toBe('--other --additional')
})

it('ignores unrelated options starting with --inspect-', () => {
process.env.NODE_OPTIONS =
'--other --inspect-port=0.0.0.0:1234 --additional'
const result = getNodeOptionsWithoutInspect()
const result = getFormattedNodeOptionsWithoutInspect()

expect(result).toBe('--other --inspect-port=0.0.0.0:1234 --additional')
})
Expand Down

0 comments on commit ab86fcf

Please sign in to comment.