Better

Ethan的博客,欢迎访问交流

简单 CLI 工具编写

解决团队当前本地开发存在的痛点。

背景

当前端需要启多个服务时不太便捷,存在如下问题

  • 每个 service 都需要进行服务注册,代码冗余
  • 写死的 localhost 导致使用 ip 访问时会有问题
  • 写死的端口号,当动态分配端口号时,需要手动修改
  • 后端本地调试时,需要修改每个 service 的 .env 文件的 host 信息,麻烦且易出错

因此想实现一个 CLI 工具,支持一键拉起选择的多个服务,交互式的自定义 host、port 等参数。

解决方案

查看 react-script 源码发现内部通过 dotenv 和 dotenv-expand 包完成 env 文件的读取和环境变量设置。

  • dotenv 用于设置环境变量,但不能解析文件里的变量,必须是字符串
  • dotenv-expand 扩展了 dotenv 的功能,可在目标文件里引用已经存在的环境变量

dotenv 默认工作机制:当同名环境变量已经存在时,则不做更改

每个 service 会读取自身的 env 文件用于设置 host、port 等环境参数,最粗暴的做法是在 CLI 中通过 io 相关 api 直接修改 env 文件,然后再启动服务,但这会导致代码仓库出现 diff 结果,同时停止服务时,最好是要想办法还原。并不优雅,最好还是不操作文件,直接在内存中完成该操作。

最终方案:利用 dotenv 默认工作机制,在 monorepo 仓库的 root 目录中支持 env 文件的读取和设置,由于在启动各个服务之前,CLI 中已经完成了环境变量设置,此时 service 中 env 同名的环境变量将不会生效,同时自动选择空闲 port 完成各个服务之间不同的 port 设置。

具体实施

考虑到交互式操作相比纯命令行要更易用,选用如下三方库

  • commander 命令行开发工具
  • chalk 样式风格控制器
  • inquirer 交互式命令行工具
  • detect-port 检测端口号是否可用,如不可用,自动返回一个可用的端口号
  • cross-spawn 针对 node spawn/spawnSync api 的跨平台解决方案

其他工具

  • cli-spinners 命令行loading
  • semver 版本号合法性检测工具
  • minimist 参数解析器

上述三方库文档简单易懂,总结几个小细节。

关于路径的处理

// 通过 prcess.cwd() 得到当前 node 运行的工作目录
const appDirectory = fs.realpathSync(process.cwd());
// 解析相对目录
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);

关于 cross-spawn

// 每个命令都是一个新的 shell 中执行,因此无法先 cd 仅某个目录再执行,如果需要在特定目录执行,则可直接使用 cwd 参数
const child = spawn('npm', ['run', 'start'], { stdio: 'inherit', cwd: cwdMapper[service] });

一个小知识

使用 npm script 包装后运行和 node 直接运行有点差别

  • 直接使用 npm run hello -h 会提示你没有结果
  • 将 hello script 中的 node 命令直接运行,会有结果
  • 原因是当你通过 npm 调用时,如果需要传递 option,需要通过 -- 用于区分 npm 自身的参数和自定义的参数,npm 会将 -- 之后的参数透传给自定义脚本,因此你需要运行 npm run hello -- -h 即可


留言