parseArgs
tokens compatibility and more high-performance parser
- ✅ High performance
- ✅
util.parseArgs
token compatibility - ✅ ES Modules and modern JavaScript
- ✅ Type safe
- ✅ Zero dependencies
- ✅ Universal runtime
- Although Node.js
parseArgs
can return tokens, that the short options are not in the format I expect. Of course, I recoginize the background of this issue. parseArgs
gives the command line args parser a useful util, so the resolution of the options values and the parsing of the tokens are tightly coupled. As a result, Performance is sacrificed. Of course, I recoginize that's the trade-off.
With mitata:
pnpm bench:mitata
> args-tokens@0.0.0 bench:mitata /path/to/projects/args-tokens
> node --expose-gc bench/mitata.js
clk: ~2.87 GHz
cpu: Apple M1 Max
runtime: node 18.19.1 (arm64-darwin)
benchmark avg (min … max) p75 / p99 (min … top 1%)
--------------------------------------------------------------- -------------------------------
util.parseArgs 4.16 µs/iter 4.20 µs █
(4.09 µs … 4.29 µs) 4.28 µs ██ ▅▅▅ ▅
( 1.36 kb … 1.52 kb) 1.37 kb ██▁████▅▅█▅▁██▁▁▅▁█▅█
args-tokens parse (equivalent to util.parseArgs) 1.65 µs/iter 1.66 µs █
(1.61 µs … 1.80 µs) 1.79 µs ▅▃ █▂ ▄
( 1.95 kb … 2.66 kb) 1.97 kb █████▆█▄▃▃▅▃▁▃▃▁▄▁▁▁▂
args-tokens parseArgs 729.56 ns/iter 734.11 ns █
(697.43 ns … 797.08 ns) 774.93 ns ▂█▅▂
( 2.87 kb … 3.54 kb) 3.11 kb ▂▂▃▇▆▅▆████▃▃▄▂▂▂▂▂▁▂
args-tokens resolveArgs 886.78 ns/iter 887.70 ns █
(853.96 ns … 978.89 ns) 957.24 ns █
( 2.51 kb … 2.87 kb) 2.79 kb ▂▃█▃▄▅█▄▃▂▂▃▃▂▂▂▂▂▁▁▁
┌ ┐
util.parseArgs ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 4.16 µs
args-tokens parse (equivalent to util.parseArgs) ┤■■■■■■■■■ 1.65 µs
args-tokens parseArgs ┤ 729.56 ns
args-tokens resolveArgs ┤■■ 886.78 ns
└ ┘
With vitest:
pnpm bench:vitest
> args-tokens@0.0.0 bench:vitest /path/to/projects/args-tokens
> vitest bench --run
Benchmarking is an experimental feature.
Breaking changes might not follow SemVer, please pin Vitest's version when using it.
RUN v3.0.5 /path/to/projects/args-tokens
✓ bench/vitest.bench.js > parse and resolve 1350ms
name hz min max mean p75 p99 p995 p999 rme samples
· util.parseArgs 221,285.36 0.0041 0.2700 0.0045 0.0044 0.0054 0.0063 0.0629 ±0.38% 110643
· args-tokens parse 527,127.11 0.0017 0.2153 0.0019 0.0019 0.0023 0.0027 0.0055 ±0.38% 263564 fastest
✓ bench/vitest.bench.js > parseArgs 1434ms
name hz min max mean p75 p99 p995 p999 rme samples
· node:util 235,217.05 0.0039 0.2665 0.0043 0.0042 0.0048 0.0058 0.0139 ±0.43% 117609
· args-tokens 1,307,135.24 0.0006 0.1737 0.0008 0.0008 0.0009 0.0010 0.0016 ±0.43% 653568 fastest
BENCH Summary
args-tokens parse - bench/vitest.bench.js > parse and resolve
2.38x faster than util.parseArgs
args-tokens - bench/vitest.bench.js > parseArgs
5.56x faster than node:util
The token output for the short option -x=v
is different:
import { parseArgs as parseArgsNode } from 'node:util'
import { parseArgs } from 'args-tokens'
// Node.js parseArgs tokens
const { tokens: tokensNode } = parseArgsNode({
allowPositionals: true,
strict: false,
args: ['-a=1'],
tokens: true
})
console.log(tokensNode)
// ({
// kind: 'option',
// name: 'a',
// rawName: '-a',
// index: 0,
// value: undefined,
// inlineValue: undefined
// },
// {
// kind: 'option',
// name: '=',
// rawName: '-=',
// index: 0,
// value: undefined,
// inlineValue: undefined
// },
// {
// kind: 'option',
// name: '1',
// rawName: '-1',
// index: 0,
// value: undefined,
// inlineValue: undefined
// })
// ]
// args-tokens parseArgs tokens
const tokens = parseArgs(['-a=1'])
console.log(tokens)
// [
// {
// kind: 'option',
// name: 'a',
// rawName: '-a',
// index: 0,
// value: undefined,
// inlineValue: undefined
// },
// { kind: 'option', index: 0, value: '1', inlineValue: true }
// ]
# npm
npm install --save args-tokens
## yarn
yarn add args-tokens
## pnpm
pnpm add args-tokens
deno add jsr:@kazupon/args-tokens
bun add args-tokens
parseArgs
will transform arguments into tokens. This function is useful if you want to analyze arguments yourself based on the tokens. It's faster than parseArgs
of node:util
because it only focuses on token transformation.
import { parseArgs } from 'args-tokens' // for Node.js and Bun
// import { parseArgs } from 'jsr:@kazupon/args-tokens' // for Deno
const tokens = parseArgs(['--foo', 'bar', '-x', '--bar=baz'])
// do something with using tokens
// ...
console.log('tokens:', tokens)
resolveArgs
is a useful function when you want to resolve values from the tokens obtained by parseArgs
.
import { parseArgs, resolveArgs } from 'args-tokens' // for Node.js and Bun
// import { parseArgs, resolveArgs } from 'jsr:@kazupon/args-tokens' // for Deno
const args = ['dev', '-p=9131', '--host=example.com', '--mode=production']
const tokens = parseArgs(args)
const { values, positionals } = resolveArgs(
{
help: {
type: 'boolean',
short: 'h'
},
version: {
type: 'boolean',
short: 'v'
},
port: {
type: 'number',
short: 'p',
default: 8080
},
mode: {
type: 'string',
short: 'm'
},
host: {
type: 'string',
short: 'o',
required: true
}
},
tokens
)
console.log('values:', values)
console.log('positionals:', positionals)
Using the parse
you can transform the arguments into tokens and resolve the argument values once:
import { parse } from 'args-tokens' // for Node.js and Bun
// import { parse } from 'jsr:@kazupon/args-tokens' // for Deno
const args = ['dev', '-p=9131', '--host=example.com', '--mode=production']
const { values, positionals } = parse(args, {
options: {
help: {
type: 'boolean',
short: 'h'
},
version: {
type: 'boolean',
short: 'v'
},
port: {
type: 'number',
short: 'p',
default: 8080
},
mode: {
type: 'string',
short: 'm'
},
host: {
type: 'string',
short: 'o',
required: true
}
}
})
console.log('values:', values)
console.log('positionals:', positionals)
If you want to use the same short options tokens as returned Node.js parseArgs
, you can use allowCompatible
parse option on parseArgs
:
import { parseArgs as parseArgsNode } from 'node:util'
import { parseArgs } from 'args-tokens'
import { deepStrictEqual } from 'node:assert'
const args = ['-a=1', '2']
// Node.js parseArgs tokens
const { tokens: tokensNode } = parseArgsNode({
allowPositionals: true,
strict: false,
args,
tokens: true
})
// args-tokens parseArgs tokens
const tokens = parseArgs(['-a=1'], { allowCompatible: true }) // add `allowCompatible` option
// validate
deepStrictEqual(tokensNode, tokens)
The ArgSchema
interface defines the configuration for command-line arguments. This schema is similar to Node.js util.parseArgs
but with extended features.
Type of the argument value:
'string'
: Text value (default if not specified)'boolean'
: True/false flag (can be negatable with--no-
prefix)'number'
: Numeric value (parsed as integer or float)'enum'
: One of predefined string values (requireschoices
property)'positional'
: Non-option argument by position'custom'
: Custom parsing with user-definedparse
function
{
name: { type: 'string' }, // --name value
verbose: { type: 'boolean' }, // --verbose or --no-verbose
port: { type: 'number' }, // --port 3000
level: { type: 'enum', choices: ['debug', 'info'] },
file: { type: 'positional' }, // first positional arg
config: { type: 'custom', parse: JSON.parse }
}
Single character alias for the long option name. Allows users to use -x
instead of --extended-option
.
{
verbose: {
type: 'boolean',
short: 'v' // Enables both --verbose and -v
},
port: {
type: 'number',
short: 'p' // Enables both --port 3000 and -p 3000
}
}
Human-readable description used for help text generation and documentation.
{
config: {
type: 'string',
description: 'Path to configuration file'
},
timeout: {
type: 'number',
description: 'Request timeout in milliseconds'
}
}
Marks the argument as required. When true
, the argument must be provided or an ArgResolveError
will be thrown.
{
input: {
type: 'string',
required: true, // Must be provided: --input file.txt
description: 'Input file path'
},
source: {
type: 'positional',
required: true // First positional argument must exist
}
}
Allows the argument to accept multiple values. The resolved value becomes an array.
- For options: can be specified multiple times (
--tag foo --tag bar
) - For positional: collects remaining positional arguments
{
tags: {
type: 'string',
multiple: true, // --tags foo --tags bar → ['foo', 'bar']
description: 'Tags to apply'
},
files: {
type: 'positional',
multiple: true // Collects all remaining positional args
}
}
Enables negation for boolean arguments using --no-
prefix. Only applicable to type: 'boolean'
.
{
color: {
type: 'boolean',
negatable: true,
default: true,
description: 'Enable colorized output'
}
// Usage: --color (true), --no-color (false)
}
Array of allowed string values for enum-type arguments. Required when type: 'enum'
.
{
logLevel: {
type: 'enum',
choices: ['debug', 'info', 'warn', 'error'],
default: 'info',
description: 'Logging verbosity level'
},
format: {
type: 'enum',
choices: ['json', 'yaml', 'toml'],
description: 'Output format'
}
}
Default value used when the argument is not provided. The type must match the argument's type
property.
{
host: {
type: 'string',
default: 'localhost' // string default
},
verbose: {
type: 'boolean',
default: false // boolean default
},
port: {
type: 'number',
default: 8080 // number default
},
level: {
type: 'enum',
choices: ['low', 'high'],
default: 'low' // must be in choices
}
}
Converts the argument name from camelCase to kebab-case for CLI usage. A property like maxCount
becomes available as --max-count
.
{
maxRetries: {
type: 'number',
toKebab: true, // Accessible as --max-retries
description: 'Maximum retry attempts'
},
enableLogging: {
type: 'boolean',
toKebab: true // Accessible as --enable-logging
}
}
Custom parsing function for type: 'custom'
arguments. Required when type: 'custom'
. Should throw an Error if parsing fails.
{
config: {
type: 'custom',
parse: (value) => {
try {
return JSON.parse(value) // Parse JSON config
} catch {
throw new Error('Invalid JSON configuration')
}
},
description: 'JSON configuration object'
},
date: {
type: 'custom',
parse: (value) => {
const date = new Date(value)
if (isNaN(date.getTime())) {
throw new Error('Invalid date format')
}
return date
}
}
}
Specifies other options that cannot be used together with this option. When conflicting options are provided together, an ArgResolveError
will be thrown.
Conflicts only need to be defined on one side - if option A defines a conflict with option B, the conflict is automatically detected when both are used.
{
// Single conflict
port: {
type: 'number',
conflicts: 'socket' // Cannot use --port with --socket
},
socket: {
type: 'string'
// No need to define conflicts: 'port' here
}
}
// Multiple conflicts (mutually exclusive options)
{
tcp: {
type: 'number',
conflicts: ['udp', 'unix'] // Cannot use with --udp or --unix
},
udp: {
type: 'number',
conflicts: ['tcp', 'unix']
},
unix: {
type: 'string',
conflicts: ['tcp', 'udp']
}
}
If you are interested in contributing to args-tokens
, I highly recommend checking out the contributing guidelines here. You'll find all the relevant information such as how to make a PR, how to setup development) etc., there.
This project is inspired by:
util.parseArgs
, created by Node.js contributors and OpenJS Foundationpkgjs/parseargs
, created by Node.js CLI package maintainers and Node.js community.
The development of Gunish is supported by my OSS sponsors!