func 应用本身的体积并不高,通常适合绝大多数场景,但如果用户处于 Node.js 兼容环境中并希望使用极低的体积进行快速原型测试,或是应用逻辑简单暂时不需要学习 func 的思维模型,那么 func/parser 会更合适。
func/parser 是微型应用的极佳选择,本身只携带少量语法,适合一小段命令式代码可表达的 CLI 项目。如包初始化器、仓库维护脚本、已有程序的薄包装器,通常只需要解析几个选项,调用一个业务函数等。
func/parser 实现是复用 func 的核心 argv 编译与解析能力,不会加载完整的装饰器应用模型,也不会接管命令执行;parse() 返回数据后,分支、帮助、输出、退出码和业务调用需要开发者自行实现。
何时适合使用
同时满足大部分条件时,func/parser 通常比完整应用模型更省事:
- CLI 只有根选项,或少量单层命令;
- 选项只需要 Boolean、String、非常有限的参数数量;
- 位置输入可以作为字符串数组交给业务函数,不需要声明式校验;
- 帮助文本很短,手写后不容易和实现失去同步;
- 业务依赖较少,通常可以在单文件内直接处理,不需要依赖注入或资源生命周期。
典型场景包括 create-* 初始化器、单用途代码生成器、CI 辅助脚本、Git hook 入口,以及只增加一层默认配置的进程包装器。
如果项目已经需要嵌套命令、共享服务、结构化帮助、统一错误输出或可靠的调用级测试,直接使用 func 可以减少之后的二次建模。
定义并调用 parser
从 func/parser 导入 createParser,传入根选项和可选的命令表。parser 会从定义推导结果类型。
import { ParserError, createParser } from 'func/parser'
const parser = createParser({
options: {
help: { alias: 'h', type: Boolean },
},
commands: {
build: {
options: {
outDir: { alias: 'o', name: 'out-dir', type: String },
tags: { alias: 't', multiple: true, name: 'tag', type: String },
},
},
},
})
export const main = async (argv = process.argv.slice(2)) => {
try {
const invocation = parser.parse(argv)
if (invocation.options.help) {
printHelp(invocation.command)
return
}
if (invocation.command === 'build') {
await build({
entries: invocation.inputs,
outDir: invocation.options.outDir,
tags: invocation.options.tags,
})
return
}
printHelp()
} catch (error) {
if (!(error instanceof ParserError)) throw error
console.error(error.message)
process.exitCode = 1
}
}上面的定义接受以下形式:
tool --help
tool build entry.ts --out-dir dist --tag next --tag latest
tool build entry.ts -o dist -t nextparse() 返回四个字段:
| 字段 | 内容 |
|---|---|
command | 选中的单层命令名称;没有命令时为 undefined。 |
inputs | parser 未消费的位置输入,保留原顺序。 |
options | 以定义键名为属性的类型化值。 |
supplied | 用户显式提供过的定义键名;可区分省略选项和显式的 --no-<name>。 |
根选项会对每个命令可见,也可以写在命令名称前后。普通值接受 --name value 与 --name=value;已知的短布尔选项可以组合。未提供时,Boolean 为 false,单值为 undefined,重复值为空数组。后出现的单值覆盖先前值,重复值按输入顺序累积。
选项定义
简单形式直接使用构造函数:
const parser = createParser({
options: {
color: Boolean,
port: Number,
profile: String,
},
})对象形式可以调整公开名称与解析行为:
| 属性 | 作用 |
|---|---|
type | 必填;只能是 Boolean、String 或 Number。 |
name | --<name> 使用的公开长名称;默认采用对象中的键。 |
alias | 一个非数字的单字符短别名。 |
aliases | 多个单字符短别名;会与 alias 合并并去重。 |
multiple | 只适用于 String / Number,把重复输入收集为只读数组。 |
negatable | 只适用于 Boolean,额外接受 --no-<name>。 |
未知命令、未知选项和无效值会抛出 ParserError。code 分别为 unknown-command、unknown-option 或 invalid-argument;定义本身无效时,createParser() 会以 invalid-definition 失败。
包装其他程序
命令设置 passthrough: true 后,从命令后的第一个位置输入或 -- 开始,剩余 token 不再被 parser 解释,适合转交给子进程:
import { createParser } from 'func/parser'
const parser = createParser({
commands: {
run: {
options: {
file: { alias: 'f', type: String },
},
passthrough: true,
},
},
})
const invocation = parser.parse(['run', '-f', 'worker.ts', '--', '--inspect'])
// invocation.options.file === 'worker.ts'
// invocation.inputs === ['--inspect']建议在文档示例中保留 --。它清楚地区分包装器选项和被包装程序的参数,也避免下游新增选项后改变原有调用的含义。
能力边界
func/parser 刻意只解决 argv 到类型化数据的转换。以下需求需要应用自行实现,或改用完整的 func:
| 需求 | func/parser 功能 |
|---|---|
| 命令结构 | 仅单层命令表;没有嵌套 path、命令 alias、Handler 或缺失命令。 |
| 位置输入 | 只返回 string[];没有名称、数量、类型或必填规则。 |
| 默认值与校验 | 只有固定空值;没有自定义默认值、required、enum、依赖、互斥或校验器。 |
| 帮助与发现 | 不保存 description,也不生成帮助、版本、弃用提示或 Shell 补全。 |
| 应用执行 | 不分派业务函数,不管理 stdin/stdout、退出码、取消信号或进程错误。 |
| 代码组织 | 不提供 Module、依赖注入、Provider 可见性和初始化或清理生命周期。 |
| 测试 | 可以直接测试 parse(argv),但没有调用级依赖覆盖、IO 捕获或执行结果。 |
parser 会严格拒绝未知选项,不提供 allowUnknown 模式。需要透传时,应为对应命令显式启用 passthrough,并确定从哪里开始把输入交给下游。
何时迁移到 func
出现以下任一信号时,完整应用模型通常开始比手写控制流更简单:
- 命令或动作超过一层,或者分支代码不断重复解析后的类型判断;
- 手写帮助、实际选项和测试需要同时修改,已经发生过不一致;
- 多个命令需要共享配置、客户端、缓存或其他有清理需求的资源;
- 需要 required、enum、跨字段约束、默认值或更明确的位置语义;
- 需要统一 IO、错误格式、退出码、取消信号、日志、配置或 Shell 补全;
- 测试需要执行完整命令,同时替换 Provider 并捕获 stdout / stderr。
迁移不要求重写业务逻辑。可以尝试按下述对应方案把命令层搬到 func:
func/parser | func |
|---|---|
commands.build | @Command('build') |
Boolean | @Flag() |
String / Number | 带有 string / number 属性的 @Value() |
{ multiple: true, type: String / Number } | @ArrayString() / @ArrayNumber() |
inputs | @Args() 的 args.inputs |
if (command === ...) | @Handler() 或有 path 的 Handler |
| 根选项 | 根 @Option();帮助等独立动作使用 @OptionCommand() 或能力包 |
手写 try/catch 与清理 | Command / Module 的 onError、onInit 与 onDispose |
下面是前面 build 命令的等价起点。业务 build() 保持不变,但命令选择、字段绑定和帮助入口使用 func 实现:
import { Args, ArrayString, Command, Handler, Module, Value, createApp } from 'func'
import type { Args as CommandArgs } from 'func'
import { HelpOption } from 'func/help'
@Command({
path: 'build',
description: 'Build one or more entries',
})
class BuildCommand {
@Value({ alias: 'o', name: 'out-dir', description: 'Output directory' })
outDir?: string
@ArrayString({ alias: 't', name: 'tag', description: 'Release tag' })
tags: string[] = []
@Handler()
run(@Args() args: CommandArgs) {
return build({
entries: args.inputs,
outDir: this.outDir,
tags: this.tags,
})
}
}
@Module({
commands: [BuildCommand],
options: [HelpOption],
})
class AppModule {}
const app = createApp(AppModule, { appName: 'tool' })
void app.bootstrap()