Skip to content
Go back

lerna用法及其构建命令详解

Published:  at  08:00 AM

文章目录

一、Lerna 入门指南

1.1 什么是 Lerna

Lerna 是各大开源项目常用的 npm 项目包管理工具,用于管理包含多个包的 JavaScript 项目。它可以帮助你执行安装、更新和发布依赖项等操作,同时保持各个包之间的独立性。

1.2 Lerna 核心命令

安装与初始化

# 全局安装 lerna
cnpm i -g lerna

# 项目初始化(记得添加.gitignore)
lerna init

# 创建 package 包
lerna create utils

依赖管理

# 为所有包添加依赖
lerna add lodash

# 为 package/core/ 添加 vue 依赖
lerna add vue package/core/

# 清空所有包的依赖
lerna clear

# 重新安装依赖
lerna bootstrap

# 在每个包的 package.json 中写好依赖关系及版本号,
# lerna link 会为每个 package 创建彼此之间的依赖关系
lerna link

命令执行

# lerna exec 为每个包执行命令
lerna exec -- rm -rf node_modules/

# 在指定的 @attack-i/core 包中执行命令
lerna exec --scope @attack-i/core -- rm -rf node_modules/

# lerna run 在每个包中执行 npm scripts
lerna run test

# 在指定的 @attack-i/core 包中 run script
lerna run --scope @attack-i/core test

版本与发布

# 版本管理
lerna version

# 查看依赖的所有变更
lerna changed 

# 查看 diff
lerna diff 

# 发布
lerna publish

更多命令及详情可以去 Lerna 官方文档 中查看


二、脚手架原理深入解析

2.1 脚手架的本质

脚手架本质上是一个操作系统的客户端,通过命令行执行特定任务。

2.2 核心价值

脚手架能够提升前端研发效能,主要体现在三个方面:

  1. 自动化:项目重复代码的拷贝、git 操作、发布上线操作
  2. 标准化:项目创建、git flow、发布流程、回滚流程
  3. 数据化:研发过程系统化、数据化,使得研发过程可量化

2.3 与自动化构建工具的区别

Jenkins、Travis 等自动化工具已经比较成熟,是否还需要自研脚手架?

对比项Jenkins/Travis自研脚手架
执行位置服务端(git hooks 触发)本地开发环境
覆盖场景构建、测试、部署项目创建、本地 git 操作、开发辅助
定制难度复杂(需后端语言)灵活(Node.js 生态)

核心区别

2.4 从使用角度理解脚手架

vue create vue-test-app
# - 主命令:vue
# - command:create
# - command 的参数:vue-test-app

vue create vue-test-app --force -r https://regisetry.npm.taobao.org

命令结构解析

部分说明示例
主命令脚手架名称vue
command具体命令create
参数command 的参数vue-test-app
options (全称)配置选项--force, --registry
options (简写)配置选项简写-f, -r

--force 叫做 option,用来辅助脚手架确认在特定场景下用户的选择(可以理解为配置)。

-r 也叫做 option,与 --force 的区别在于使用 -,使用简写,-r 也可以替换为 --registry


三、脚手架执行原理

3.1 常见问题

在深入讲解原理之前,先思考几个核心问题:

3.2 全局安装原理

全局安装 @vue/cli 时发生了什么:

  1. npm 在进行全局安装 vue-cli
  2. 在 vue-cli 的 package.json 中查看 bin 字段
  3. bin 中如果有指令,会在 node 安装目录的 bin 文件夹中添加 vue 的软链接

package.json 配置示例

{
    "name": "@vue/cli",
    ...
    "bin": {
        "vue": "bin/vue.js"  // 终端中 vue 主命令从这里来的
    }
}

npm 安装 vue 作为全局依赖的时候,为 vue 在 bin 目录中创建了软链接,并指向当前使用的 node 版本安装路径的 lib/node_modules 目录中 vue-cli 中的 vue.js

3.3 命令执行原理

执行 vue 命令,会查到 vue-cli 的 js 文件,但是为什么会被 node 执行呢?

关键代码

#!/usr/bin/env node
// 上面这行会让操作系统在环境变量中寻找 node,
// nvm 可以切换 node,也就是修改了环境变量中的 PATH

console.log("hello world~")

3.4 如何在命令行创建自定义命令

# 先进入 node 的 bin 目录
cd ~/.nvm/versions/node/v16.13.1/bin

# 创建软链接
ln -s ~/pixel/source/_posts/backend/nodejs/cli/test.js attr

# 为命令添加别名,软连接是可以嵌套的
ln -s ./attr attr2

# 删除软链接
rm -rf attr attr2

脚手架之所以是客户端,因为 node 是客户端

3.5 完整执行流程

vue create vue-test-app 为例:

  1. 终端解析 vue 命令
  2. 在环境变量 $PATH 中查询 vue 的软链接
  3. 执行 vue-cli 的 bin 目录中的 vue.js
  4. 终端利用 node 执行 vue.js
  5. vue.js 第一行声明 #!/usr/bin/env node,会让终端通过环境变量查找 node 客户端
  6. vue.js 用 node 执行
  7. vue.js 解析 command/options
  8. vue.js 执行 command
  9. 执行完毕,退出

四、脚手架开发实战

4.1 开发流程

  1. 创建 npm 项目:npm init -y
  2. 创建入口文件,最上方添加 #!/usr/bin/env node
  3. 配置 package.json,添加 bin 属性
  4. 编写脚手架代码
  5. 将脚手架发布到 npm
  6. 安装脚手架:npm install -g your-own-cli
  7. 使用脚手架:your-own-cli

4.2 开发难点解析

4.2.1 分包管理

将复杂的系统拆分成若干个模块。

4.2.2 命令注册

vue create
vue add
vue invoke

4.2.3 参数解析

命令格式

vue command [options] <params>

Options 类型

4.2.4 帮助文档

Global Help

Command Help: 例如:vue 的帮助信息

4.2.5 其他功能点

4.3 Node 根据路径执行文件

node -e "require('../cli/index.js')"

五、命令行参数解析库详解

Lerna 能够在命令行运行的原理:Lerna 其实是一个封装了 npm 命令的工具,在执行 lerna 命令时,其实是调用了 npm 命令。具体实现主要依赖两个模块:commander 和 yargs,这两个库都是用来解析命令行参数的。

5.1 Commander 使用详解

Commander 的作用是读取命令行参数。

#!/usr/bin/env node

const commander = require('commander')
const program = new commander.Command()
const pkg = require('../package.json')

program
    .name(Object.keys(pkg.bin)[0])
    .usage('<cmd> [option]')
    .version(pkg.version)
    .option('-d, --debug', 'start debug mode', false)
    .option('-e --envName <envName>', 'fetch the environment', 'development')

// 定义 clone 命令
const clone = program.command('clone <source> [destination]');
clone
    .description('clone an existing project')
    .option('-f, --force', 'clone force')
    .action((source, dest, cmdObj) => {
        console.log('clone', source, dest, cmdObj.force);
    })

// 定义 server 子命令
const server = new commander.Command('server')
server.command('start [port]')
    .description('start server at specified port')
    .action((port) => {
        console.log(`start server at ${port} ...`);
    })

server.command('stop')
    .description("stop server")
    .action(() => {
        console.log("stop server");
    })

// 定义 install 命令,委托给 npm 执行
program.command('install [name] [path]', 'install package at specified path', {
    executableFile: 'npm',  // 当前 install 命令执行,相当于 yarn add 执行
    // isDefault: true,        // 这里设置为 true,当执行脚手架时,默认就会执行 npm
    hidden: true            // 作为一个隐藏的命令
}).alias('i')

// 监听 --help 事件
program.on('--help', function () {
    console.log('help info');
})

// debug 模式实现
program.on('--debug', function () {
    process.env.LOG_LEVEL = program.debug ? 'verbose' : 'info';
    console.log(process.env.LOG_LEVEL);
})

// 对未知命令监听
program.on('command:*', function (obj) {
    console.error('unknown command', obj[0]);
    const availableCommand = program.commands.map(cmd => cmd.name);
    console.log('可用命令:' + availableCommand.join(""));
})

// 适用于所有命令的监听
program.arguments('<cmd> [options]')
    .description('test command', {
        cmd: 'command',
        options: 'options for command',
    })
    .action((cmd, options) => {
        console.log(cmd, options);
    })

program.addCommand(server)

program.parse(process.argv)

5.2 Yargs 使用详解

Yargs 是一个命令行参数解析器,它可以帮助你快速构建一个命令行工具。

const yargs = require('yargs/yargs')
// 页脚格式化插件
const dedent = require('dedent')
const pkg = require('../package.json')

const cli = yargs()
const argv = process.argv.slice(2)

const context = {
    cliVersion: pkg.version
}

cli
    .usage('Usage: lio-imooc-test [command] <options>')
    .demandCommand(1, 'A command is required. Pass --help to see all available commands and options. 最少要输入一个参数')
    .strict()
    .recommendCommands()
    .fail((err, msg) => {
        console.log('err =======>', err)
        console.log('msg =======>', msg)
    })
    .alias('h', 'help')
    .alias('v', 'version')
    .wrap(cli.terminalWidth())  // 显示的宽度(撑满全屏)
    .epilogue(dedent`  hello 
        world`)  // 去除缩进
    .options({  // 对所有的 command 添加的 options 都有效
        debug: {  // 添加 debug 命令
            type: 'boolean',
            describe: 'Bootstrap debug mode',
            alias: 'd'
        }
    })
    .option('ci', {
        type: 'string',
        describe: 'Define global registry',
        alias: 'r'
    })
    .group(['debug'], 'Dev Options:')  // 把 debug 添加到 Dev Options 中
    .group(['registry'], 'Extra Options:')
    .command('init [name]', 'Do init a project', (yargs) => {
        yargs
            .option('name', {
                type: 'string',
                describe: 'Name of a project',
                alias: 'n'
            })
    }, (argv) => {
        console.log(argv)
    })
    .command({
        command: 'list',
        aliases: ['ls', 'la', 'll'],
        describe: 'List local packages',
        builder: (yargs) => {

        },
        handler: (argv) => { }
    })
    .parse(argv, context)  // 将默认参数与配置参数合并

六、Node.js 相关技术参考

6.1 Node 原生方法和属性

在开发脚手架和命令行工具时,常用的 Node.js 原生方法和属性:

方法/属性说明
fs.statSync / fs.lstatSync获取文件状态信息
fs.accessSync(path)检查文件是否可访问
fs.realpathSync()根据相对路径,返回绝对路径(如果是软链接,会一直寻到最终路径)
fs.toRealPath()调用生成真实路径,判断是否存在
Module._nodeModulePaths(path)返回 path 各层级的 node_modules 路径数组
Module._resolveFilename(filename)返回文件的真实路径
NativeModule.canBeRequiredByUsers(request)是否是内置模块
Module._resolveLookupPaths(request, parent)返回一个数组,当前模块可能存在的所有路径,将当前模块文件路径各层级的 node_modules 目录数组和 node 环境变量中的 node_modules 数组合并,这是一个有顺序的数组,离 path 最近的一层目录为首位
process.cwd()当前进程运行目录

6.2 常用第三方库

在开发脚手架时,常用的第三方库:

库名说明
import-local查询是否是本地的包
pkg-dir查找当前文件或者目录层级最近的 package.json
find-up从当前目录向上查找文件
path-exists检查路径是否存在
locate-path定制路径查找逻辑
resolve-cwd解析当前工作目录的路径

七、总结

本文从 Lerna 的实际使用出发,深入讲解了脚手架和命令行工具的开发原理。通过学习本文,你应该能够:

  1. 熟练使用 Lerna 管理多包项目
  2. 理解脚手架的执行原理和工作机制
  3. 掌握 Commander 和 Yargs 的使用方法
  4. 了解 Node.js 在命令行工具开发中的关键 API
  5. 具备开发自定义脚手架的能力

脚手架是提升前端研发效能的重要工具,希望本文能够帮助你更好地理解和应用相关技术。



Previous Post
yargs常用方法
Next Post
Npm常用命令