1、lerna入口是lerna/core/lerna/cli.js这个文件

  1. #!/usr/bin/env node //执行node命令
  2. "use strict";
  3. /* eslint-disable import/no-dynamic-require, global-require */
  4. //当我们本地node_modules存在一个脚手架命令,同时全局在node_modules中也存在这个脚手架命令的时候,优先选用本地node_modules中的版本
  5. const importLocal = require("import-local");
  6. if (importLocal(__filename)) {
  7. require("npmlog").info("cli", "using local version of lerna");
  8. } else {
  9. //.相当于./index.js
  10. require(".")(process.argv.slice(2));
  11. }

2、进入lerna/core/lerna/index.js

  1. "use strict";
  2. //依次加载相应命令
  3. const cli = require("@lerna/cli");
  4. const addCmd = require("@lerna/add/command");
  5. const bootstrapCmd = require("@lerna/bootstrap/command");
  6. const changedCmd = require("@lerna/changed/command");
  7. const cleanCmd = require("@lerna/clean/command");
  8. const createCmd = require("@lerna/create/command");
  9. const diffCmd = require("@lerna/diff/command");
  10. const execCmd = require("@lerna/exec/command");
  11. const importCmd = require("@lerna/import/command");
  12. const infoCmd = require("@lerna/info/command");
  13. const initCmd = require("@lerna/init/command");
  14. const linkCmd = require("@lerna/link/command");
  15. const listCmd = require("@lerna/list/command");
  16. const publishCmd = require("@lerna/publish/command");
  17. const runCmd = require("@lerna/run/command");
  18. const versionCmd = require("@lerna/version/command");
  19. const pkg = require("./package.json");
  20. module.exports = main;
  21. function main(argv) {
  22. const context = {
  23. lernaVersion: pkg.version,
  24. };
  25. return cli()
  26. //添加命令
  27. .command(addCmd)
  28. .command(bootstrapCmd)
  29. .command(changedCmd)
  30. .command(cleanCmd)
  31. .command(createCmd)
  32. .command(diffCmd)
  33. .command(execCmd)
  34. .command(importCmd)
  35. .command(infoCmd)
  36. .command(initCmd)
  37. .command(linkCmd)
  38. .command(listCmd)
  39. .command(publishCmd)
  40. .command(runCmd)
  41. .command(versionCmd)
  42. // 合并参数 , 将 argv 和自定义的 context 中的属性合并到 argv 中
  43. .parse(argv, context);
  44. }

3、进入@lerna/cli/index.js

  1. "use strict";
  2. const dedent = require("dedent");
  3. const log = require("npmlog");
  4. const yargs = require("yargs/yargs");
  5. const { globalOptions } = require("@lerna/global-options");
  6. module.exports = lernaCLI;
  7. /**
  8. * A factory that returns a yargs() instance configured with everything except commands.
  9. * Chain .parse() from this method to invoke.
  10. *
  11. * @param {Array = []} argv
  12. * @param {String = process.cwd()} cwd
  13. */
  14. function lernaCLI(argv, cwd) {
  15. // 对 yargs 进行初始化
  16. const cli = yargs(argv, cwd);
  17. //globalOptions 也是一个方法 把 yargs 作为参数传入 返回的还是这个 yargs 对象
  18. //运用构造者模式,对一个对象调用方法,然后返回这个对象本身
  19. return globalOptions(cli)
  20. /// 配置 cli 的开始内容 $0 表示再 argv 中寻找 $0 这个值进行替换
  21. .usage("Usage: $0 <command> [options]")
  22. // 配置输入的最小命令,为1,否则弹出提示
  23. .demandCommand(1, "A command is required. Pass --help to see all available commands and options.")
  24. // 配置 command 的与错误最相近的 command 提示
  25. .recommendCommands()
  26. // 开启严格模式
  27. .strict()
  28. //命令不存在时,弹出警告
  29. .fail((msg, err) => {
  30. // certain yargs validations throw strings :P
  31. const actual = err || new Error(msg);
  32. // ValidationErrors are already logged, as are package errors
  33. if (actual.name !== "ValidationError" && !actual.pkg) {
  34. // the recommendCommands() message is too terse
  35. if (/Did you mean/.test(actual.message)) {
  36. log.error("lerna", `Unknown command "${cli.parsed.argv._[0]}"`);
  37. }
  38. //日记打印
  39. log.error("lerna", actual.message);
  40. }
  41. // exit non-zero so the CLI can be usefully chained
  42. cli.exit(actual.exitCode > 0 ? actual.exitCode : 1, actual);
  43. })
  44. //命令别名
  45. .alias("h", "help")
  46. .alias("v", "version")
  47. // 配置cli的宽度和命令行一样
  48. .wrap(cli.terminalWidth())
  49. // 配置 cli 结尾的内容
  50. .epilogue(dedent`
  51. When a command fails, all logs are written to lerna-debug.log in the current working directory.
  52. For more information, find our manual at https://github.com/lerna/lerna
  53. `);
  54. }

4、进入@lerna/global-options/index.js

  1. "use strict";
  2. const os = require("os");
  3. module.exports.globalOptions = globalOptions;
  4. function globalOptions(yargs) {
  5. // 适用于 每一条命令的全局选项
  6. const opts = {
  7. loglevel: {
  8. defaultDescription: "info",
  9. describe: "What level of logs to report.",
  10. type: "string",
  11. },
  12. concurrency: {
  13. defaultDescription: os.cpus().length,
  14. describe: "How many processes to use when lerna parallelizes tasks.",
  15. type: "number",
  16. requiresArg: true,
  17. },
  18. "reject-cycles": {
  19. describe: "Fail if a cycle is detected among dependencies.",
  20. type: "boolean",
  21. },
  22. "no-progress": {
  23. describe: "Disable progress bars. (Always off in CI)",
  24. type: "boolean",
  25. },
  26. progress: {
  27. // proxy for --no-progress
  28. hidden: true,
  29. type: "boolean",
  30. },
  31. "no-sort": {
  32. describe: "Do not sort packages topologically (dependencies before dependents).",
  33. type: "boolean",
  34. },
  35. sort: {
  36. // proxy for --no-sort
  37. hidden: true,
  38. type: "boolean",
  39. },
  40. "max-buffer": {
  41. describe: "Set max-buffer (in bytes) for subcommand execution",
  42. type: "number",
  43. requiresArg: true,
  44. },
  45. };
  46. // 得到这个option的名称
  47. const globalKeys = Object.keys(opts).concat(["help", "version"]);
  48. // 给 yargs 添加 全局options
  49. return yargs.options(opts)
  50. // 对 options 进行分组
  51. .group(globalKeys, "Global Options:")
  52. // 添加了一个隐藏的 option
  53. .option("ci", {
  54. hidden: true,
  55. type: "boolean",
  56. });
  57. }

5、从2,进入@lerna/list/command中

  1. "use strict";
  2. //需要过滤的lerna子命令的选项
  3. const { filterOptions } = require("@lerna/filter-options");
  4. const listable = require("@lerna/listable");
  5. /**
  6. * @see https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module
  7. */
  8. //command 的格式
  9. exports.command = "list";
  10. exports.aliases = ["ls", "la", "ll"];
  11. //对 command 的描述
  12. exports.describe = "List local packages";
  13. //builder 函数,在执行命令之前做的一些事情
  14. exports.builder = (yargs) => {
  15. listable.options(yargs);
  16. return filterOptions(yargs);
  17. };
  18. //handler 函数,执行 command 的行为
  19. exports.handler = function handler(argv) {
  20. //调index.js函数,并把argv传入
  21. return require(".")(argv);
  22. };
  23. //返回一个对象,包含command、aliases、describe等方法

7、从handler进入index.js,执行factory方法

  1. "use strict";
  2. const { Command } = require("@lerna/command");
  3. const listable = require("@lerna/listable");
  4. const { output } = require("@lerna/output");
  5. const { getFilteredPackages } = require("@lerna/filter-options");
  6. module.exports = factory;
  7. function factory(argv) {
  8. //实例化一个 ListCommand
  9. return new ListCommand(argv);
  10. }
  11. //继承Command
  12. class ListCommand extends Command {
  13. get requiresGit() {
  14. return false;
  15. }
  16. initialize() {
  17. // 也是通过 chain 微任务队列的方式
  18. let chain = Promise.resolve();
  19. chain = chain.then(() => getFilteredPackages(this.packageGraph, this.execOpts, this.options));
  20. chain = chain.then((filteredPackages) => {
  21. this.result = listable.format(filteredPackages, this.options);
  22. });
  23. return chain;
  24. }
  25. execute() {
  26. // piping to `wc -l` should not yield 1 when no packages matched
  27. if (this.result.text.length) {
  28. output(this.result.text);
  29. }
  30. this.logger.success(
  31. "found",
  32. "%d %s",
  33. this.result.count,
  34. this.result.count === 1 ? "package" : "packages"
  35. );
  36. }
  37. }
  38. module.exports.ListCommand = ListCommand;

8、继承Command

  1. "use strict";
  2. class Command {
  3. constructor(_argv) {
  4. //深拷贝argv
  5. const argv = cloneDeep(_argv);
  6. // 对constructor的name进行处理,ListCommand变成list "FooCommand" => "foo"
  7. this.name = this.constructor.name.replace(/Command$/, "").toLowerCase();
  8. // 添加 composed 属性, 是否使用复合指令
  9. this.composed = typeof argv.composed === "string" && argv.composed !== this.name;
  10. //如果不是复合指令
  11. if (!this.composed) {
  12. // composed commands have already logged the lerna version
  13. log.notice("cli", `v${argv.lernaVersion}`);
  14. }
  15. // 执行命令
  16. let runner = new Promise((resolve, reject) => {
  17. // 定义一个微任务 chain.then 会被加入到微任务队列,不会立即执行
  18. let chain = Promise.resolve();
  19. s // 会行程队列,一个接一个执行
  20. chain = chain.then(() => {
  21. this.project = new Project(argv.cwd);
  22. });
  23. chain = chain.then(() => this.configureEnvironment());
  24. chain = chain.then(() => this.configureOptions());
  25. chain = chain.then(() => this.configureProperties());
  26. chain = chain.then(() => this.configureLogging());
  27. chain = chain.then(() => this.runValidations());
  28. chain = chain.then(() => this.runPreparations());
  29. //执行命令
  30. chain = chain.then(() => this.runCommand());
  31. ...
  32. // argv中顶一个cwd和$0的参数
  33. for (const key of ["cwd", "$0"]) {
  34. Object.defineProperty(argv, key, { enumerable: false });
  35. }
  36. // 对 argv 属性做一些处理
  37. Object.defineProperty(this, "argv", {
  38. value: Object.freeze(argv),
  39. });
  40. // 对 runner 属性做一些处理
  41. Object.defineProperty(this, "runner", {
  42. value: runner,
  43. });
  44. }
  45. ...
  46. //核心内容
  47. runCommand() {
  48. return Promise.resolve()
  49. // 调用 initialize 方法
  50. .then(() => this.initialize())
  51. .then((proceed) => {
  52. if (proceed !== false) {
  53. // 调用 execute方法
  54. return this.execute();
  55. }
  56. // early exits set their own exitCode (if non-zero)
  57. });
  58. }
  59. // initialize 和 execute 强制用户实现,否则会报错
  60. initialize() {
  61. throw new ValidationError(this.name, "initialize() needs to be implemented.");
  62. }
  63. execute() {
  64. throw new ValidationError(this.name, "execute() needs to be implemented.");
  65. }
  66. }
  67. module.exports.Command = Command;

6、注意:npm项目本地依赖引用方法

  1. "dependencies": {
  2. //依赖本地的文件相对于npm link,不用将文件导入到全局,占内存,更推荐
  3. "@lerna/global-options": "file:../global-options",
  4. "dedent": "^0.7.0",
  5. "npmlog": "^4.1.2",
  6. "yargs": "^16.2.0"
  7. }