lerna是怎么执行一条命令的(结合源码详解)

前言

大家好,我是作曲家种太阳
lerna是一个多包管理工具,可以帮助我们多个package统一管理维护,极大提升了开发效率,常用于脚手架开发,组件库开发. babel也是基于lerna开发的. lerna本身是集成了yargs,其实自身也是可以注册命令,

看完本文,你的收获

    1. 学会调试源码技巧
    1. 搞懂lerna如何执行一条命令的思路,更有利于你搞懂脚手架的开发流程

lerna的github地址

1.使用流程

建议先去官网了解下使用流程与基本命令,使用环节本文不会占太多篇幅
画张图你就明白lerna的基本命令使用了~:

image.png

2. 安装lerna

npm install lerna --save
复制代码

3.webstorm 调试源码前准备

(1).打开webstorm的prefences面板,勾选node包,内置库会高亮可调式

image.png

(2).取消下面两个勾选,让debug模式可以在node_mudules下面运行

image.png

(3).在node_modules下面找到lerna和@lerna 这两个文件包

在node_modules/lerna/cli.js中开启debug模式,并且加入参数

image.png

image.png
加入参数init

(4).开始测试

给cli.js的12行打上断点,开始测试

image.png

4.开始调试lerna init命令

我们刚才配置好了调试参数,也配置好了命令,我们看看在lerna init 这一句命令是怎么执行的 \

(1).先来看看lerna/package.json中的bin

  "bin": {
    "lerna": "cli.js"
  },
复制代码

ps: bin 中 key是lerna ,value是cli.js,这个细节很重要,也是脚手架比较重要的细节,当有了bin以后,安装过后,lerna可以直接执行

(2).首先 在cli.js中

#!/usr/bin/env node
const importLocal = require("import-local")
if (importLocal(__filename)) {
  require("npmlog").info("cli", "using local version of lerna");
} else {
  console.log("test start")
  require(".")(process.argv.slice(2));
}

复制代码

ps:

    1. process.argv.slice(2) 是截取了lerna后的参数,这里截取的具体指是 init
    1. require(“.”) 看起来很奇怪,但其实加载的./index.js文件,并把参数传递过去了
    1. 开头 #!/usr/bin/env node 是执行命令时候告诉系统用node环境运行,这也是脚手架的重要的一环

(3).进入到lerna/index.js中

进入到这里你会发现导入了很多来自于@lerna的包,并有一个main函数

function main(argv) {
  const context = {
    lernaVersion: pkg.version,
  };
  return cli()
    .command(addCmd)
    .command(bootstrapCmd)
    .command(changedCmd)
    .command(cleanCmd)
    .command(createCmd)
    .command(diffCmd)
    .command(execCmd)
    .command(importCmd)
    .command(infoCmd)
    .command(initCmd)
    .command(linkCmd)
    .command(listCmd)
    .command(publishCmd)
    .command(runCmd)
    .command(versionCmd)
    .parse(argv, context);
}
复制代码

ps:

    1. main函数的位置参数argv 就是 cli.js传递过来的命令参数(init)
    1. 返回了一个cli()函数,并注册了很命
    1. 不比深究,我们继续往下看cli() 这个函数是干什么

5.调试@lerna/cli

进入到@lerna/cli/index.js文件
进入后你会恍然大悟,原来cli()函数是一个yargs的实例对象,并配置了一些参数
这里有个有意思的是:

  • 1.摘要
    const cli = yargs(argv, cwd);
    .wrap(cli.terminalWidth())
    复制代码

    ps:这里是生成一个yargs实例,并且终端设置成满屏

结合4.3步骤,你应该就清楚了,原来cli().command() 是在注册命令
根据不同的命令参数,执行不同的模块,所以我们下一步去@lerna/init模块当中

6.调试 @lerna/init

(1)进入到 @lerna/init/command 当中(是参考@lerna/cli.js的initCmd路径)

  1. 这里是在cli()函数(yargs实例)中注册命令与handle执行函数
  2. handle执行函数并传递了参数argv 里调用了@lerna/init/index

(2)进入@lerna/init/index

首先进入眼帘的是:
factory
InitCommand
这两个函数
ps发现:

  • 1.factory返回了InitCommand的实例
  • 2.InitCommand继承了commder类
  • 3.根据以上两点argv参数其实是传递到了commder类中
  • 4.commder类做了对argv参数的校验,后续逻辑先不关注
  • 5.有一段代码引起了我的好奇
  execute() {
    let chain = Promise.resolve();
    chain = chain.then(() => this.ensurePackageJSON());
    chain = chain.then(() => this.ensureLernaConfig());
    chain = chain.then(() => this.ensurePackagesDir());
    return chain.then(() => {
      this.logger.success("", "Initialized Lerna files");
    });
  }
复制代码

这种链式调用不正是控制异步执行顺序的妙招么,有兴趣的同学可以研究下event loop和promise规范!!

  • 6.ensurePackageJSON,ensureLernaConfig,ensurePackagesDir逻辑 分别创建了json文件,config文件,还有一个packages文件夹,我们不比深究逻辑,有意思的是ensurePackageJSON使用了write-json-file这个npm包

课外加餐:介绍 write-json-file 包

这里不是调试lerna源码的逻辑主干,只是觉得这个包很方便,创建一个json文件
安装: npm install write-json-file
使用:

const writeJsonFile = require('write-json-file');
const log = require("npmlog");
//创建json文件,indent为缩进
writeJsonFile('foo.json', {test: 123},{indent:1} );
复制代码

总结

从头到尾我带大家调试lerna的源码,并且一步步的带你走完命令怎么执行的流程,
明白了lerna执行命令的逻辑
相信你对类似于脚手架的业务又有了新的了解,有兴趣的同学可以继续往下深入研究其他包的源码

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享