构建现代化的命令行工具

文章源于 lambdas.devgit


每当咱们想要建立一个基于 NodeJS 的命令行工具时,就会衍生出一堆问题须要解决,好比如何准备开发环境,如何打包转译代码,如何使代码在转译后保持可调用的状态同时尽量的压缩体积, 以及怎样设计项目分配 CommandOption 等等,这会浪费巨大的时间,并且并不是必定有成果。这时你能够注意到社区几乎全部的命令行工具都是自成一派, 并无严谨的框架或约定约束,也无所谓的最佳实践,这使想要特别是第一次想要开发命令行工具的开发者望而却步,或是几番努力最后却不尽如人意。github


举个例子来讲,腾讯的 omi 是一个有众多使用者的框架,但其命令行工具 omi / omi-cli 却让人贻笑大方。仅一些简单的下载和建立模板的任务,造出长篇大论的文件不说,下载 时依赖数千,包的体积巨大,总体项目毫无设计几乎是为所欲为、天马行空,这就是开发者自己并不擅长此道,只学会了 糊屎。(何谓 糊屎,参阅 JS 优雅指南 2npm

func 的出现就是为了解决这些问题。func 自己的实现参阅了社区内诸多基于 NodeJS 的命令行工具的优秀实现,与流行的框架设计思路相结合,以优雅的设计、小体积、高性能 等为目标, 同时关注开发者体验,大幅度的提高了命令行工具项目的可扩展性与可读性,几乎是现在 NodeJS 社区中开发命令行工具的最优解。咱们能够尝试使用 func 构建一个命令行工具。c#


构建项目

在之前流行的一些命令行参数解析的库中,咱们在构建项目前须要准备大量的脚本与配置,甚至还要解决文件权限、bin、代码转译等等问题,但使用 func,咱们能够仅经过一行命令 初始化项目:bash

npm init func
复制代码

项目初始化后进入文件夹,随机使用 npm installyarn 安装依赖,如今就能够正式开发了。 能够注意到,func 的项目模板中为咱们准备了 startbuild 2 个脚本,它们都是由 func-service 驱动的,帮助你一键切换开发与生产模式,咱们所要作的就是专一于 命令行逻辑自己,实现逻辑就够了。框架

实现逻辑

咱们能够随意的建立一个类,当它被加上 Command 注解时这就是一个命令,而被加上 Option 注解时就会转变为一个选项:异步

import { Command } from 'func'

@Command({ name: 'test' })
export class Test {
}
复制代码

在命令行中运行 <YOUR NAME> test 时,Test 类将会被调用。选项也是同理。你能够在 constructor 中开始本身的代码,这和你在任何地方写 NodeJS 没有什么 不一样,当你以为差很少的时候,运行 npm build 就能够将它打包,一切就是这么简单。ide

有时候,当咱们须要使用到命令行携带的参数时,好比处理 <NAME> something params -option 这样复杂的输入,你能够直接在类中注入命令行参数,对,就是像 IoC 那样:函数

@Command({ name: 'test' })
export class Test {
  constructor( private args: CommandArgsProvider, ) {
  }
}
复制代码

CommandArgsProvider 其实是一个 class 类型,当你标记一个参数为此类型时,func 会在运行时为你注入全部的命令参数, 一样的也支持 OptionArgsProviderRegisterProvider 等等,你能够在 官方的文档 阅读它们的具体类型。工具


打包与发布

运行 npm build 能够获得一个打包后的文件,这是由 ncc 编译后的文件,一般它只有一个 (若是携带 extra 可能会有多个,但它们会自动连接), 同时为你连接好了 bin,你要作的惟一一件事就是将包发布出去。为何 func 使用这种方式发布呢?

咱们知道当你在安装一个包或是使用 npx 执行包时 (这在使用命令行工具的人群中很常见),NPM 所花费的时间大约在 3 个部分,即对比包的依赖,下载包,执行。 首先咱们知道 func 的项目足够的小,可以大量介绍下载时间,同时也有足够好的性能,如今要解决的就是在大量依赖时的对比分析问题,将文件打包成单文件不依赖 外部环境时会极大的减小所需时间,若是你再将全部的依赖移入 devDependencies 中,几乎可以在一瞬间完成 分析 - 下载 - 运行 这三个步骤。这样的体验是不可思议的。 是的,这里推荐你把全部的依赖当作开发依赖处理,这彷佛违背了 NPM Package 的开发哲学,但在使用 func 构建命令行应用时这样作却大有裨益。

在运行 func build 完成的包时,咱们注意到几乎无需任何依赖,这是由于在单个文件中已经 bundle 了全部的所需资源,也就意味着用户在运行 .js 文件时, 堆栈中真的就只有 .js 文件内的内容,不会引用其余,不会加载任何可有可无的东西。此时咱们也就无需用户关心 dependencies,甚至能够移除它们,这样一来, 下载或即时运行时就直接跳过了 对比依赖版本 这一步,这其中省略了无数的请求也就会会极大的增长速度,npm init func 可以在 1 秒左右马上开始安装也是这样的道理。


优化与经验

如今你已经知道了怎样快速的构建一个合格且优雅的命令行工具,那怎样作的更好呢?一般来讲你须要遵循这几点:

  • 不由于小功能引入巨大的包,不引入依赖爆炸的包。

    举例来讲,download-git-repo 是一个很不错的包,它可以为你节约不少时间,但请注意它依赖了 download,若是你仅为了下载单个文件或只有不多的下载需求时, 这就显得有些大材小用,download 会为你增长约 450 kb 的重量,却只作了一件你 5 分钟能够搞定的事情。一样你的用户也会为此付出巨大的时间代价。

  • 不要显示错误堆栈信息。

    在多数状况下咱们都须要尽量的显示堆栈或是引用的错误信息便于 debug,可是在命令行工具中这样作只会使你的用户很是困惑。这主要归结于命令行中不能很好高亮 的显示代码块,大量的代码信息会使用户不知所措。建议你始终构建一个错误处理模块来解决问题,同时为用户提供良好的反馈,最后能够提供相似于 --debug 的 选项让开发者调试。

  • 不要太依赖同步操做。

    在 NodeJS 与其社区流行的 I/O 库中,咱们一般会有异步和同步函数两种选择,如 readFilereadFileSync,虽然同步函数能够为你节约一些开发时间, 但也会阻塞你的代码,不少状况下会有难以理解的问题。好比当你设置定时器显示一个 Loading 图标的同时操做了同步 API,那么你的 Loading 图标就会由于阻塞 而没法运动 (由于没法 render 到终端),或是你同时操做多个文件,同步的 API 会使你花费巨大的时间。

  • 不要发布无用信息。

    命令行工具不少时候的角色是充当复杂的脚本,性能和体验是相当重要的,发布无用的信息在你的 package 中会使下载时间更长。(使用 files 来约束发布的文件)

  • 不要修改临时文件夹与配置区之外的信息。

    对于命令行工具来讲,运行时的权限是巨大的,但不要所以弄脏用户的系统。你可使用 require('os').tmpdir() 获取用户操做系统的临时文件夹目录,不管什么时候, 你都拥有这里的写权限。

相关文章
相关标签/搜索