解决团队当前本地开发存在的痛点。
背景
当前端需要启多个服务时不太便捷,存在如下问题
- 每个 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 即可