1.lerna #

1.1 简介 #

1.2.lerna 入门 #

首先使用 npm 将 Lerna 安装到全局环境中

npm install -g lerna

接下来,我们将创建一个新的 git 代码仓库

mkdir lerna4 && cd lerna4

现在,我们将上述仓库转变为一个 Lerna 仓库:

lerna init
lerna notice cli v4.0.0
lerna info Initializing Git repository
lerna info Creating package.json
lerna info Creating lerna.json
lerna info Creating packages directory
lerna success Initialized Lerna files
lerna4 / packages / 放置多个软件包(package);
package.json;
lerna.json;

.gitignore

node_modules.idea.vscode;

2.lerna 源码 #

2.1 配置安装源 #

npm install -g yrm
npm install -g nrm

2.2 克隆源码 #

git clone https://gitee.com/zhufengpeixun/lerna.git --depth=1

2.3 调试源码 #

.vscode\launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "skipFiles": ["<node_internals>/**"],
      "program": "${workspaceFolder}\\core\\lerna\\cli.js",
      "args": ["ls"]
    }
  ]
}

2.4 核心包 #

lerna 入口核心包
@lerna/cli
@lerna/create 创建包命令
@lerna/init 初始化lerna项目

3.创建 npm 私服 #

cnpm install verdaccio -g
verdaccio
http://localhost:4873
npm adduser --registry http://localhost:4873/
npm publish --registry http://localhost:4873/

4.创建包 #

lerna create lerna4 --registry http://localhost:4873
lerna success create New package lerna4 created at ./packages\lerna4
lerna create @lerna4/cli --registry http://localhost:4873
lerna success create New package @lerna4/cli created at ./packages\cli
lerna create @lerna4/create --registry http://localhost:4873
lerna success create New package @lerna4/create created at ./packages\create
lerna create @lerna4/init --registry http://localhost:4873
lerna success create New package @lerna4/init created at ./packages\init

4.单元测试 #

npm install --save-dev jest
//在所有的包下执行test命令
lerna run test
//在lerna4下执行test命令
lerna run test --scope lerna4

//在所有的包下执行shell脚本
lerna exec -- jest
//在lerna4下执行shell脚本
lerna exec --scope lerna4 -- jest

5.1 package.json #

{
  "name": "root",
  "private": true,
  "devDependencies": {
    "lerna": "^4.0.0"
  },
+ "scripts": {
+    "test":"jest"
+  }
}

5.2 jest.config.js #

module.exports = {
  testMatch: ["**/__tests__/**/*.test.js"],
};

5.3 lerna4\package.json #

packages\lerna4\package.json

{
+  "scripts": {
+    "test": "jest"
+  }
}

5.4 lerna4.js #

packages\lerna4\lib\lerna4.js

module.exports = lerna4;
function lerna4() {
  return "lerna4";
}

5.5 create.test.js #

packages\create__tests__\create.test.js

"use strict";

const create = require("..");
describe("@lerna4/create", () => {
  it("create", () => {
    expect(create()).toEqual("create");
  });
});

6.eslint #

cnpm i eslint  --save-dev

6.1 .eslintrc.js #

module.exports = {
  parserOptions: { ecmaVersion: 2017, sourceType: "module" },
  extends: ["eslint:recommended"],
  rules: {
    "no-unused-vars": ["off"],
  },
  env: { node: true, jest: false },
};

6.2 .eslintignore #

__tests__;

6.3 package.json #

  "scripts": {
    "test": "jest",
+   "lint":"eslint --ext .js packages/**/*.js --no-error-on-unmatched-pattern --fix"
  }

7.Prettier #

cnpm i   prettier eslint-plugin-prettier  --save-dev

7.1 .eslintrc.js #

module.exports = {
  extends: ['eslint:recommended'],
  //让所有可能会与 prettier 规则存在冲突的 eslint rule失效,并使用 prettier 的规则进行代码检查
  //相当于用 prettier 的规则,覆盖掉 eslint:recommended 的部分规则
+ plugins: ['prettier'],
  rules: {
    'no-unused-vars': ['off'],
    //不符合prettier规则的代码要进行错误提示
+   'prettier/prettier': ['error', { endOfLine: 'auto' }],
  },
  env: { node: true, jest: false },
};

7.2 .prettierrc.js #

module.exports = {
  singleQuote: true,
};

8. editorconfig #

8.1 .editorconfig #

root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

8.2 总结 #

9. git hook #

9.1 pre-commit #

9.1.1 安装 Git hooks #

cnpm i husky --save-dev
npm set-script prepare "husky install"

9.1.2 安装 pre-commit #

npx husky add .husky/pre-commit "npx lint-staged"

9.2 commit-msg #

9.2.1 安装配置 #

cnpm install commitizen cz-customizable @commitlint/cli @commitlint/config-conventional --save-dev

9.2.2 安装 commit-msg #

npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

9.2.3 添加命令 #

npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

9.2.4 .cz-config.js #

module.exports = {
  types: [
    { value: "feat", name: "feat:一个新特性" },
    { value: "fix", name: "fix:修复BUG" },
  ],
  scopes: [{ name: "sale" }, { name: "user" }, { name: "admin" }],
};

9.2.5 commitlint.config.js #

module.exports = {
  extends: ["@commitlint/config-conventional"],
};

9.2.6 package.json #

  "scripts": {
    "test": "jest",
    "lint": "eslint --ext .js packages/**/*.js --no-error-on-unmatched-pattern --fix",
    "prepare": "husky install",
+   "commit": "cz"
  },

10.发布上线 #

npx husky add .husky/pre-push "npm run test"
lerna version
lerna publish

11.安装命令 #

11.1 cli.js #

packages\lerna4\cli.js

#!/usr/bin/env node
require(".")(process.argv.slice(2));

11.2 lerna4\index.js #

packages\lerna4\index.js

module.exports = main;
function main(argv) {
  console.log(argv);
}

11.3 lerna4\package.json #

packages\lerna4\package.json

{
+  "main": "index.js",
+  "bin":{
+    "lerna4":"cli.js"
+  }
}

11.4 链接 #

cd packages\lerna4
npm link
lerna4

12. yargs #

const yargs = require("yargs/yargs");
const argv = process.argv.slice(2);
const cli = yargs(argv);
//应用到每一个命令的全局参数
const opts = {
  loglevel: {
    defaultDescription: "info",
    describe: "报告日志的级别",
    type: "string",
    alias: "L",
  },
};
//全局的key
const globalKeys = Object.keys(opts).concat(["help", "version"]);
cli
  .options(opts) //配置全局参数
  .group(globalKeys, "Global Options:") // 把全局参数分到全局组里
  .usage("Usage: $0 <command> [options]") //提示使用说明
  .demandCommand(1, "至少需要一个命令,传递--help查看所有的命令和选项") //指定最小命令数量
  .recommendCommands() //推荐命令
  .strict() //严格命令,不正确 会报错
  .fail((msg, err) => {
    //自定义错误打印
    console.error("lerna", msg, err);
  })
  .alias("h", "help") //别名
  .alias("v", "version") //别名
  .wrap(cli.terminalWidth()) //命令行宽度
  .epilogue(
    //结语
    `当1个命令失败了,所有的日志将会写入当前工作目录中的lerna-debug.log`
  )
  .command({
    command: "create <name>",
    describe: "创建一个新的lerna管理的包",
    builder: (yargs) => {
      yargs
        .positional("name", {
          describe: "包名(包含scope)",
          type: "string",
        })
        .options({
          registry: {
            group: "Command Options:",
            describe: "配置包的发布仓库",
            type: "string",
          },
        });
    },
    handler: (argv) => {
      console.log("执行init命令", argv);
    },
  })
  .parse(argv);

/**
node lerna4.js create project --registry  http://localhost:4873
执行init命令 {
  '$0': 'lerna4.js',
  _: [ 'create' ],
  name: 'project'
  registry: 'http://localhost:4873',
}
*/

13.跑通 init 命令 #

lerna link
lerna bootstrap

13.1 cli\package.json #

packages\cli\package.json

"dependencies": {
    "@lerna4/cli":"^0.0.4",
    "@lerna4/init":"^0.0.4"
},
+  "main": "index.js",

13.2 cli\index.js #

packages\cli\index.js

const yargs = require("yargs/yargs");
function lernaCLI() {
  const cli = yargs();
  //应用到每一个命令的全局参数
  const opts = {
    loglevel: {
      defaultDescription: "info",
      describe: "报告日志的级别",
      type: "string",
      alias: "L",
    },
  };
  //全局的key
  const globalKeys = Object.keys(opts).concat(["help", "version"]);
  return cli
    .options(opts) //配置全局参数
    .group(globalKeys, "Global Options:") // 把全局参数分到全局组里
    .usage("Usage: $0 <command> [options]") //提示使用说明
    .demandCommand(1, "至少需要一个命令,传递--help查看所有的命令和选项") //指定最小命令数量
    .recommendCommands() //推荐命令
    .strict() //严格命令,不正确 会报错
    .fail((msg, err) => {
      //自定义错误打印
      console.error("lerna", msg, err);
    })
    .alias("h", "help") //别名
    .alias("v", "version") //别名
    .wrap(cli.terminalWidth()) //命令行宽度
    .epilogue(
      //结语
      `当1个命令失败了,所有的日志将会写入当前工作目录中的lerna-debug.log`
    );
}
module.exports = lernaCLI;

13.3 init\command.js #

packages\init\command.js

exports.command = "init";
exports.describe = "创建一个新的Lerna仓库";
exports.builder = (yargs) => {
  console.log("执行init builder");
};
exports.handler = (argv) => {
  console.log("执行init命令", argv);
};

13.4 lerna4\package.json #

packages\lerna4\package.json

+  "main": "index.js"

13.5 lerna4\index.js #

packages\lerna4\index.js

const cli = require('@lerna4/cli');
const initCmd = require('@lerna4/init/command');
function main(argv) {
  return cli().command(initCmd).parse(argv);
}

module.exports = main;

14.实现 init 命令 #

14.1 安装依赖 #

lerna add fs-extra  packages/init
lerna add  execa  packages/init

14.2 init\command.js #

packages\init\command.js

exports.command = "init";
exports.describe = "创建一个新的Lerna仓库";
exports.builder = () => {
  console.log("执行init builder");
};
exports.handler = (argv) => {
  console.log("执行init命令", argv);
  return require(".")(argv);
};

14.3 packages\init\index.js #

packages\init\index.js

const path = require("path");
const fs = require("fs-extra");
const execa = require("execa");

class InitCommand {
  constructor(argv) {
    this.argv = argv;
    this.rootPath = path.resolve();
  }
  async execute() {
    await execa("git", ["init"], { stdio: "pipe" });
    await this.ensurePackageJSON();
    await this.ensureLernaConfig();
    await this.ensurePackagesDir();
    console.log("Initialized Lerna files");
  }
  async ensurePackageJSON() {
    console.log("创建 package.json");
    await fs.writeJson(
      path.join(this.rootPath, "package.json"),
      {
        name: "root",
        private: true,
        devDependencies: {
          lerna: "^4.0.0",
        },
      },
      { spaces: 2 }
    );
  }
  async ensureLernaConfig() {
    console.log("创建 lerna.json");
    await fs.writeJson(
      path.join(this.rootPath, "lerna.json"),
      {
        packages: ["packages/*"],
        version: "0.0.0",
      },
      { spaces: 2 }
    );
  }
  async ensurePackagesDir() {
    console.log("创建 packages 目录");
    await fs.mkdirp(path.join(this.rootPath, "packages"));
  }
}
function factory(argv) {
  new InitCommand(argv).execute();
}
module.exports = factory;

15.实现 create 命令 #

15.1 安装依赖 #

lerna add pify  packages/crate
lerna add  init-package-json  packages/crate
lerna add  dedent  packages/crate

15.2 lerna4\index.js #

packages\lerna4\index.js

const cli = require('@lerna4/cli');
const initCmd = require('@lerna4/init/command');
const createCmd = require('@lerna4/create/command');
function main(argv) {
  return cli()
   .command(initCmd)
+  .command(createCmd)
   .parse(argv);
}

module.exports = main;

15.3 lerna4\package.json #

packages\lerna4\package.json

{
  "dependencies": {
    "@lerna4/cli":"^0.0.4",
    "@lerna4/init":"^0.0.4",
+   "@lerna4/create":"^0.0.4"
  },
}

15.4 create\command.js #

packages\create\command.js

exports.command = "create <name>";
exports.describe = "创建一个新的lerna管理的包";
exports.builder = (yargs) => {
  console.log("执行init builder");
  yargs
    .positional("name", {
      describe: "包名(包含scope)",
      type: "string",
    })
    .options({
      registry: {
        group: "Command Options:",
        describe: "配置包的发布仓库",
        type: "string",
      },
    });
};
exports.handler = (argv) => {
  console.log("执行create命令", argv);
  return require(".")(argv);
};

15.5 create\index.js #

packages\create\index.js

const path = require("path");
const fs = require("fs-extra");
const dedent = require("dedent");
const initPackageJson = require("pify")(require("init-package-json"));
class CreateCommand {
  constructor(options) {
    this.options = options;
    this.rootPath = path.resolve();
    console.log("options", options);
  }
  async execute() {
    const { name, registry } = this.options;
    this.targetDir = path.join(this.rootPath, "packages/cli");
    this.libDir = path.join(this.targetDir, "lib");
    this.testDir = path.join(this.targetDir, "__tests__");
    this.libFileName = `${name}.js`;
    this.testFileName = `${name}.test.js`;
    await fs.mkdirp(this.libDir);
    await fs.mkdirp(this.testDir);
    await this.writeLibFile();
    await this.writeTestFile();
    await this.writeReadme();
    var initFile = path.resolve(process.env.HOME, ".npm-init");
    await initPackageJson(this.targetDir, initFile);
  }
  async writeLibFile() {
    const libContent = dedent`
        module.exports = ${this.camelName};
        function ${this.camelName}() {
            // TODO
        }
    `;
    await catFile(this.libDir, this.libFileName, libContent);
  }
  async writeTestFile() {
    const testContent = dedent`
    const ${this.camelName} = require('..');
    describe('${this.pkgName}', () => {
        it('needs tests');
    });
  `;
    await catFile(this.testDir, this.testFileName, testContent);
  }
  async writeReadme() {
    const readmeContent = dedent`## Usage`;
    await catFile(this.targetDir, "README.md", readmeContent);
  }
}
function catFile(baseDir, fileName, content) {
  return fs.writeFile(path.join(baseDir, fileName), `${content}\n`);
}
function factory(argv) {
  new CreateCommand(argv).execute();
}

module.exports = factory;

16.参考 #

16.1 lerna 命令 #

项目初始化 #

命令 说明
lerna init 初始化项目

16.2 创建包 #

命令 说明
lerna create 创建 package
lerna add 安装依赖
lerna link 链接依赖

16.3 开发和测试 #

命令 说明
lerna exec 执行 shell 脚本
lerna run 执行 npm 命令
lerna clean 清空依赖
lerna bootstrap 重新安装依赖

16.4 发布上线 #

命令 说明
lerna version 修改版本号
lerna changed 查看上个版本以来的所有变更
lerna diff 查看 diff
lerna publish 发布项目

17.格式化提交 #

17.1 Conventional Commits #

<类型>[可选 范围]: <描述>

[可选 正文]

[可选 脚注]

17.2 类型(type) #

17.3 范围(scope) #