Lerna --多包存储管理工具(一)

lerna

最近在看[vue-cli](https://github.com/vuejs/vue-cli的源码部分,注意到这一个仓库下维护了多个package,很好奇他是如何在一个repo中管理这些package的。

我们组现在也在使用组件库的方式维护项目间共用的业务代码。有两个组件库,存在依赖的关系,目前联调是通过npm link的方式,性能并不好,时常出现卡顿的问题。加上前一段时间组内分享vue3也提到了lerna,于是便决定仔细的调研一下这个工具,为接下里的组件库优化助力。

lerna的文档还是很详细的,因为全是英文的,考虑到阅读问题,这里我先是自己跑了几个demo,然后做了中文翻译。后续我会出一篇专门的lerna实战篇

[demo](https://github.com/qinzhiwei1993/lerna-repo-test

lerna 是干什么的?

Lerna 是一个工具,它优化了使用 git 和 npm 管理多包存储库的工作流。

背景

1.将一个大的 package 分割成一些小的 packcage 便于分享,调试

2.在多个 git 仓库中更改容易变得混乱且难以跟踪

3.在多个 git 仓库中维护测试繁琐

两种工作模式

Fixed/Locked mode (default)

vue,babel 都是用这种,在 publish 的时候,所有的包版本都会更新,并且包的版本都是一致的,版本号维护在 lerna.jon 的 version 中

Independent mode

lerna init --independent

独立模式,每个 package 都可以有自己的版本号。版本号维护在各自 package.json 的 version 中。每次发布前都会提示已经更改的包,以及建议的版本号或者自定义版本号。这种方式相对第一种来说,更灵活

初始化项目


npm install -g lerna // 这里是全局安装,也可以安装为项目开发依赖,使用全局方便后期使用命令行

mkdir lerna-repo

cd lerna-repo

lerna init // 初始化一个lerna项目结构,如果希望各个包使用单独版本号可以加 -i | --independent

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-znl8Z0YA-1599646450309)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]

标准的 lerna 目录结构

  • 每个单独的包下都有一个 package.json 文件

  • 如果包名是带 scope 的,例如@test/lerna,package.json 中,必须配置"publishConfig": {“access”: “public”}


my-lerna-repo/

    package.json

    lerna.json

    LICENSE

    packages/

        package-1/

            package.json

        package-2/

            package.json

启用 yarn Workspaces (强烈建议)

Workspaces can only be enabled in private projects.

默认是 npm, 每个子 package 下都有自己的 node_modules,通过这样设置后,会把所有的依赖提升到顶层的 node_modules 中,并且在 node_modules 中链接本地的 package,便于调试

注意:必须是 private 项目才可以开启 workspaces


// package.json

"private": true,

"workspaces": [

    "packages/*"

],
 

// lerna.json

 
"useWorkspaces": true,

"npmClient": "yarn",

hoist: 提取公共的依赖到根目录的node_moduels,可以自定义指定。其余依赖安装的package/node_modeles中,可执行文件必须安装在package/node_modeles

workspaces: 所有依赖全部在跟目录的node_moduels,除了可执行文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wk3liN9K-1599646450317)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]

常用命令

lerna init

初始化 lerna 项目

  • -i, --independent 独立版本模式

lerna create [loc]

创建一个 packcage

  • --access 当使用scope package时(@qinzhiwei/lerna),需要设置此选项 [可选值: “public”, “restricted”][默认值: public]

  • --bin 创建可执行文件 --bin

  • --description 描述 [字符串]

  • --dependencies 依赖,用逗号分隔 [数组]

  • --es-module 初始化一个转化的Es Module [布尔]

  • --homepage 源码地址 [字符串]

  • --keywords 关键字数 [数组]

  • --license 协议 [字符串][默认值: isc]

  • --private 是否私有仓库 [布尔]

  • --registry 源 [字符串]

  • --tag 发布的标签 [字符串]

  • -y, --yes 跳过所有的提示,使用默认配置 [布尔]

lerna add

为匹配的 package 添加本地或者远程依赖,一次只能添加一个依赖


$ lerna add [@version] [--dev] [--exact] [--peer]

运行该命令时做的事情:

  1. 为匹配到的 package 添加依赖

  2. 更改每个 package 下的 package.json 中的依赖项属性

Command Options

以下几个选项的含义和npm install时一致

  • --dev

  • --exact

  • --peer 同级依赖,使用该package需要在项目中同时安装的依赖

  • --registry

  • --no-bootstrap 跳过 lerna bootstrap,只在更改对应的 package 的 package.json 中的属性

所有的过滤选项都支持

Examples


# Adds the module-1 package to the packages in the 'prefix-' prefixed folders

lerna add module-1 packages/prefix-*

  

# Install module-1 to module-2

lerna add module-1 --scope=module-2

  

# Install module-1 to module-2 in devDependencies

lerna add module-1 --scope=module-2 --dev

  

# Install module-1 to module-2 in peerDependencies

lerna add module-1 --scope=module-2 --peer

  

# Install module-1 in all modules except module-1

lerna add module-1

  

# Install babel-core in all modules

lerna add babel-core

lerna bootstrap

将本地 package 链接在一起并安装依赖

执行该命令式做了一下四件事:

1.为每个 package 安装依赖

2.链接相互依赖的库到具体的目录,例如:如果 lerna1 依赖 lerna2,且版本刚好为本地版本,那么会在 node_modules 中链接本地项目,如果版本不满足,需按正常依赖安装

3.在 bootstraped packages 中 执行 npm run prepublish

4.在 bootstraped packages 中 执行 npm run prepare

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENY4EVrV-1599646450349)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DVSIBjOD-1599646450351)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]

Command Options

  • --hoist 匹配 [glob] 依赖 提升到根目录 [默认值: ‘**’], 包含可执行二进制文件的依赖项还是必须安装在当前 package 的 node_modules 下,以确保 npm 脚本的运行

  • --nohoist 和上面刚好相反 [字符串]

  • --ignore-prepublish 在 bootstraped packages 中不再运行 prepublish 生命周期中的脚本 [布尔]

  • --ignore-scripts 在 bootstraped packages 中不再运行任何生命周期中的脚本 [布尔]

  • --npm-client 使用的 npm 客户端(npm, yarn, pnpm, …) [字符串]

  • --registry 源 [字符串]

  • --strict 在 bootstrap 的过程中不允许发出警告,避免花销更长的时间或者导致其他问题 [布尔]

  • --use-workspaces 启用 yarn 的 workspaces 模式 [布尔]

  • --force-local 无论版本范围是否匹配,强制本地同级链接 [布尔]

  • --contents 子目录用作任何链接的源。必须适用于所有包 [字符串][默认值: .]

lerna link

将本地相互依赖的 package 相互连接。例如 lerna1 依赖 lerna2,且版本号刚好为本地的 lerna2,那么会在 lerna1 下 node_modules 中建立软连指向 lerna2

Command Options

  • –force-local 无论本地 package 是否满足版本需求,都链接本地的

// 指定软链到package的特定目录

"publishConfig": {

    "directory": "dist" // bootstrap的时候软链package下的dist目录 package-1/dist => node_modules/package-1

}

lerna list

list 子命令

  • lerna ls: 等同于 lerna list本身,输出项目下所有的 package

  • lerna ll: 输出项目下所有 package 名称、当前版本、所在位置

  • lerna la: 输出项目下所有 package 名称、当前版本、所在位置,包括 private package

Command Options

  • --json

  • --ndjson

  • -a, --all

  • -l, --long

  • -p, --parseable

  • --toposort

  • --graph

所有的过滤选项都支持

--json

以 json 形式展示


$ lerna ls --json

[

    {

        "name": "package-1",

        "version": "1.0.0",

        "private": false,

        "location": "/path/to/packages/pkg-1"

    },

    {

        "name": "package-2",

        "version": "1.0.0",

        "private": false,

        "location": "/path/to/packages/pkg-2"

    }

]

--ndjson

以newline-delimited JSON展示信息


$ lerna ls --ndjson

{"name":"package-1","version":"1.0.0","private":false,"location":"/path/to/packages/pkg-1"}

{"name":"package-2","version":"1.0.0","private":false,"location":"/path/to/packages/pkg-2"}

--all

Alias: -a

显示默认隐藏的 private package


$ lerna ls --all

package-1

package-2

package-3 (private)

--long

Alias: -l

显示包的版本、位置、名称


$ lerna ls --long

package-1 v1.0.1 packages/pkg-1

package-2 v1.0.2 packages/pkg-2

  

$ lerna ls -la

package-1 v1.0.1 packages/pkg-1

package-2 v1.0.2 packages/pkg-2

package-3 v1.0.3 packages/pkg-3 (private)

--parseable

Alias: -p

显示包的绝对路径

In --long output, each line is a :-separated list: ::[:flags..]


$ lerna ls --parseable

/path/to/packages/pkg-1

/path/to/packages/pkg-2

  

$ lerna ls -pl

/path/to/packages/pkg-1:package-1:1.0.1

/path/to/packages/pkg-2:package-2:1.0.2

  

$ lerna ls -pla

/path/to/packages/pkg-1:package-1:1.0.1

/path/to/packages/pkg-2:package-2:1.0.2

/path/to/packages/pkg-3:package-3:1.0.3:PRIVATE

--toposort

按照拓扑顺序(dependencies before dependents)对包进行排序,而不是按目录对包进行词法排序。


$ json dependencies 

--graph

将依赖关系图显示为 JSON 格式的邻接表 adjacency list.


$ lerna ls --graph

{

    "pkg-1": [

        "pkg-2"

    ],

    "pkg-2": []

}

  

$ lerna ls --graph --all

{

    "pkg-1": [

       "pkg-2"

    ],

    "pkg-2": [

        "pkg-3"

    ],

    "pkg-3": [

        "pkg-2"

    ]

}

lerna changed

列出自上次发布(打 tag)以来本地发生变化的 package

注意: lerna publishlerna versionlerna.json配置同样影响lerna changed。 例如 command.publish.ignoreChanges.

Command Options

lerna changed 支持 lerna ls的所有标记:

  • --json

  • --ndjson

  • -a, --all

  • -l, --long

  • -p, --parseable

  • --toposort

  • --graph

lerna 不支持过滤选项, 因为lerna version or lerna publish不支持过滤选项.

lerna changed 支持 lerna version (the others are irrelevant)的过滤选项:

  • --conventional-graduate.

  • --force-publish.

  • --ignore-changes.

  • --include-merged-tags.

lerna import

lerna import

将现有的 package 导入到 lerna 项目中。可以保留之前的原始提交作者,日期和消息将保留。

注意:如果要在一个新的 lerna 中引入,必须至少有个 commit

Command Options

  • --flatten 处理合并冲突

  • --dest 指定引入包的目录

  • --preserve-commit 保持引入项目原有的提交者信息

lerna clean

lerna clean

移除所有 packages 下的 node_modules,并不会移除根目录下的

所有的过滤选项都支持

lerna diff

查看自上次发布(打 tag)以来某个 package 或者所有 package 的变化


$ lerna diff [package]

  

$ lerna diff

# diff a specific package

$ lerna diff package-name

Similar to lerna changed. This command runs git diff.

lerna exec

在每个 package 中执行任意命令,用波折号(--)分割命令语句

使用方式


$ lerna exec --  [..args] # runs the command in all packages

$ lerna exec -- rm -rf ./node_modules

$ lerna exec -- protractor conf.js

可以通过LERNA_PACKAGE_NAME变量获取当前 package 名称:


$ lerna exec -- npm view $LERNA_PACKAGE_NAME

也可以通过LERNA_ROOT_PATH获取根目录绝对路径:


$ lerna exec -- node $LERNA_ROOT_PATH/scripts/some-script.js

Command Options

所有的过滤选项都支持


$ lerna exec --scope my-component -- ls -la

  • –concurrenty

使用给定的数量进行并发执行(除非指定了 --parallel)。

输出是经过管道过滤,存在不确定性。

如果你希望命令一个接着一个执行,可以使用如下方式:


$ lerna exec --concurrency 1 -- ls -la

  • --stream

从子进程立即输出,前缀是包的名称。该方式允许交叉输出:


$ lerna exec --stream -- babel src -d lib

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jh92cyxU-1599646450354)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]

  • --parallel

--stream很像。但是完全忽略了并发性和排序,立即在所有匹配的包中运行给定的命令或脚本。适合长时间运行的进程。例如处于监听状态的babel src -d lib -w


$ lerna exec --parallel -- babel src -d lib -w

注意: 建议使用命令式控制包的范围。

因为过多的进程可能会损害shell的稳定。例如最大文件描述符限制

  • --no-bail

# Run a command, ignoring non-zero (error) exit codes

$ lerna exec --no-bail 

默认情况下,如果一但出现命令报错就会退费进程。使用该命令会禁止此行为,跳过改报错行为,继续执行其他命令

  • --no-prefix

在输出中不显示 package 的名称

  • --profile

生成一个 json 文件,可以在 chrome 浏览器(devtools://devtools/bundled/devtools_app.html)查看性能分析。通过配置--concurrenty可以开启固定数量的子进程数量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-230mu6uR-1599646450356)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]


$ lerna exec --profile -- 

注意: 仅在启用拓扑排序时分析。不能和 --parallel and --no-sort一同使用。

  • --profile-location

设置分析文件存放位置


$ lerna exec --profile --profile-location=logs/profile/ -- 

lerna run

在每个 package 中运行 npm 脚本

使用方法


$ lerna run 
                    
                    

你可能感兴趣的:(前端进阶,javascript,npm,cli,github)