lerna使用
1、原生脚手架开发痛点分析
1. 痛点一: 重复操作
- 多Package本地link
- 多Package本地安装
- 多Package单元测试
- 多Package代码提交
- 多Package代码发布
2. 痛点二:版本一致性
- 发布时版本一致性
- 发布后相互依赖版本升级
package越多, 管理复杂度越高
2、lerna简价
lerna是一个优化基于 git + npm的多package项目的管理工具
优势:
- 大幅减少重复操作
- 提升操作的标准化
lerna 是架构优化的产物,它揭示了一个架构真理: 项目复杂度提升后,就需要对项目进行架构优化。 架构优化的主要目标往往都是以效能为核心。
3、lerna框架的搭建
// 第一步 进行全局安装lerna
yarn global add lerna
// 第二步 进行项目初始化
npm init -y
lerna init // lerna初始化 --会自动生成lerna.json的配置文件,packages文件夹,并且自动实现git的初始化
// 第三步 创建.gitignore对一些文件进行git忽略
touch .gitignore // 添加 .vscode .idea node_modules packages/**/node_modules 等路径
// 第四步 在npm官网上创建组织,这点关乎后期发布成功问题,点击 add organization进行添加,注意组织的名称尽量与项目的name保持一致
// 第五步 在项目中利用 lerna create [package name] 进行创建分包,名称需要是 @[group name]/[package name]进行创建
4、lerna的简单使用
lerna init #项目初始化
lerna add <package name> #在所有的package中添加指定的依赖
lerna add <package name> packages/<local package> # 在本地的local项目中添加指定的依赖, packages表示目录文件夹
lerna link # 在根目录下执行lerna link,那么在本地之间会自动建立包的引用, 如在core中: @even-cli/utils: '^1.0.0',那么会自动进行软链,但是建议使用本地引用: @even-cli/utils: 'file:../utils'进行使用,注意更改后需要npm install一下,引入的时候是const utils = require('@even-cli/utils')
lerna clean # 把所有项目下的node_modules进行删除
lerna bootstrap # 对所有的依赖进行重新安装
lerna run <script name> #执行所以package下的script name的指令,如果指令存在的话
lerna run --scope <package path> <script> # 如: lerna run --scope @even-cli/utils test 表示执行utils这个包下的test脚本
lerna changed #用来查看哪些包发生了变更, 需要被发布
lerna diff #查看当前文件相对到上次提交发生的变化
lerna version #进行版本号的修改管理
lerna publish #进行包的发布, 注意不要使用npm publish, 而是使用lerna publish
常见报错注意:
-
发布时组织与包名需要一一对应
-
如果发布的过程中,报nolicense的错误,那么需要在项目的根目录下添加 LICENSE.md文件
-
需要在每个包的package.json中添加下面代码,在发布的过程中有报
E402 You must sign up for private packages
的情况下
"publishConfig": {
"access": "public"
}
注意: 在lerna中,如果不需要用package文件夹,那么就需要在lerna.json中改定义,如下:
{
//"packages": [ "packages/*"],
// 改成你需要定义的文件夹
"packages": ["command/*", "core/*", "models/*", "tools/*"],
"version": "0.0.1"
}
5、yargs入门
yargs的常规用法如下:
#! /usr/bin/env node
import yargs from 'yargs/yargs'
import dedent from 'dedent'
const pkg = require('../package.json') // 导入package.json的配置
// 获取到指令的参数
const argv: any = process.argv.slice(2)
const context: Record<string, string> = {
evenVersion: pkg.version, // 注意这里不要用version
}
const cli = yargs()
cli
.usage('Usage: even [command] <option>') // 表示提示命令的用法
//声明最少参数,如果少于指定参数,则进行提示
.demandCommand(1, 'A command is required. Pass --help to see all available commands and options.')
.strict() // 采用严格模式
.alias('h', 'help') // 取别名
.alias('v', 'version')
.wrap(cli.terminalWidth()) // wrap里面可以是一个数值,表示命令与解析的距离,同时可以使用 cli提供的方法来定义宽度
.epilogue(dedent`this is end of command`) // 这个指令表示加在末尾的文本,dedent表示即使换行也不进行文本缩进
//为全局command添加选项: options
.options({
// 可以在options里面进行批量配置指令
debug: {
type: 'boolean',
describe: 'start debug mode',
alias: 'd',
},
})
.option('log', {
// 这个命令是进行单个指令配置
type: 'boolean',
alias: 'l',
describe: 'this is test log',
})
.group(['debug', 'log'], 'spec options:') //对指令进行分组,把debug与log放能spec options下面
.recommendCommands() // 命令纠错提示, 会根据当前输入的command去找最相似的进行提示
.fail((msg: string, err: Error) => {
// 定制错误信息
console.log(msg)
})
.command(
// 命令的定义方式一
'serve [port]',
'start the serve',
(yargs) => {},
(argv) => {}
)
// .command({ // 命令的定义方式二
// command: 'ls',
// aliases: ['la'],
// describe: 'ls all argv',
// builder: (yargs) => {
// return yargs.option('all', {
// type: 'boolean',
// alias: 'a',
// describe: 'show all argv',
// })
// },
// handler: (argv) => {
// console.log(argv)
// },
// })
.command('ls', false, {
// 命令的定义方式三,注意第二个参数表示不展示命令在帮助信息里面
// 表示不在帮助信息中展示,即为内部使用
aliases: ['la'],
describe: 'ls all argv',
builder: (yargs) => {
return yargs.option('all', {
type: 'boolean',
alias: 'a',
describe: 'show all argv',
})
},
handler: (argv) => {
console.log(argv)
},
})
.parse(argv, context)
注意: import-local这个库是用于优先引入项目的本地库,示例如下
#!/usr/bin/env node
"use strict";
/* eslint-disable import/no-dynamic-require, global-require */
const importLocal = require("import-local");
if (importLocal(__filename)) { // 先引入本地的库,如果本地库对应的为null,那么就会引入全局的库
require("npmlog").info("cli", "using local version of lerna");
} else {
require(".")(process.argv.slice(2));
}
pkg-dir是查找package.json的上层文件夹目录
6、commander的使用
commander的基础用法
#! /usr/bin/env node
import { program, Command } from 'commander'
const pkg = require('../package.json')
// new Command().name('even-test').usage('<command> [options]').parse(process.argv)
program
.name('even-test')
.usage('<command> [options]')
.version(pkg.version)
.option('-d, --debug', '启动debug的模式', true)
.option('-m --mode <name>', '启动指定的mode模式')
.parse(process.argv)
console.log(program.opts()) // 获取到所有的参数
//这里的name相当于重新定义usage前的name,如: Usage: even-test <command> [options], 通常使用package.json里的bin的key
//以上两种写法都一样,但是更推荐使用第二种用法
//option 第一个参数表示option的名称和简称,第二个参数是表示describe, 第三个参数表示默认值
注意:在进行配置选项或者命令注册的时候
commander的命令注册
#! /usr/bin/env node
import { program, Command } from 'commander'
const pkg = require('../package.json')
program.name('even-test').usage('<command> [options]').version(pkg.version)
// 创建命令
program
.command('init <name>', { hidden: false, isDefault: false }) // 如果把isDefault设置为true那么even时默认就会执行该命令
.description('初始化项目')
.option('-f --force', '强制覆盖本地的项目')
.action((...rest) => {
console.log(rest)
})
//注意:在使用program.command的时候注意program.command返回的结果并不是program对象
//所以进行command后再进行配置全局option或者其他的命令时就需要用分号隔开
//注册子命令通过addCommand进行注册,相当于是命令中套用命令
const serve = new Command() // 创建子目录的根路径调用程序
.command('serve')
.description('服务')
.action(() => {
console.log('test')
})
serve // 声明和调用子命令的调用程序
.command('start [port]')
.description('启动服务')
.action((port) => {
console.log(port)
})
serve // 声明停止服务的子命令
.command('stop')
.description('停止服务')
.action(() => {
console.log('stop serve')
})
program.addCommand(serve)
program.parse(process.argv)
commander的高级用法
#! /usr/bin/env node
import { program, Command } from 'commander'
const pkg = require('../package.json')
program.name('even-test').usage('<command> [options]').version(pkg.version).option('-d, --debug', '打开debug模式')
program
.command('init <name>')
.description('初始化项目')
.option('-f --force', '强制覆盖本地的项目')
.action((...rest) => {
// console.log(rest)
})
// 高级定制1: 如何自定义help信息
// program.outputHelp() // 打印帮助信息
// console.log(program.helpInformation()) // 获取帮助信息,并且打印
// 修改方式一
// program.helpInformation = (...rest) => {
// return 'this is help info'
// }
//修改方式二
// program.on('--help', () => {
// console.log('yes help')
// })
// 高级定制2: 监听指定的选项
program.on('option:debug', () => {
console.log('this is debug')
})
// 注意:如果是监听help那么使用的是 program.on('--help',()=> {}) 进行监听
// 高级定制3:对未知命令的监听
program.on('command:*', (opt) => {
// 能进入到这里说明,没有匹配到上面所有的命令
const arr = program.commands.map((cmd) => cmd.name())
console.log(arr)
})
program.parse(process.argv)