介绍
Lerna 是 JavaScript/TypeScript 的原始 monorepo 工具。它已经存在很多年了,被数以万计的项目使用,包括 React 和 Jest。
它解决了 JavaScript/TypeScript monorepos 的两个最大问题:
- Lerna 对任意数量的项目运行命令,它以最有效的方式、以正确的顺序执行,并且可以将其分发到多台计算机上。
- Lerna 管理您的发布流程,从版本管理到发布到 NPM,它提供了多种选项以确保可以适应任何工作流程。
Nx 已经接管了 Lerna 的管理权。Nx 是由前 Google 员工开发的构建系统,利用了 Google 内部工具使用的许多技术。Lerna v5 是这个新管理下的第一个版本,更新了过时的软件包并开始对存储库本身进行一些清理。从 v6+ 开始,Lerna 将任务调度工作委托给 Nx 经过实战考验、行业领先的任务运行程序,这意味着 lerna run 免费获得缓存和命令分发的好处!
Why Lerna
- 超级快! - Lerna 速度很快,甚至比大多数同类解决方案还要快(请参阅此基准以了解更多信息)。如何?在底层,Lerna v6+ 使用 Nx 来运行任务。在此处了解有关运行任务的更多信息。
- 计算缓存 - Lerna 知道您将要运行的任务过去何时执行过。Lerna 将恢复文件并立即重播终端输出,而不是运行它。另外,此缓存可以与您的同事和 CI 共享。使用 Lerna 时,您的整个组织将永远不必构建或测试同一事物两次。
- 免配置分布式任务执行 - Lerna 可以在多台机器上分发任何命令,无需任何配置,同时保留在单台机器上运行它的开发人体工程学。换句话说,使用 Lerna 扩展 monorepo 就像启用布尔标志一样简单。换句话说,使用 Lerna 扩展 monorepo 就像启用布尔标志一样简单。查看启用 DTE 如何使 CI 速度提高 20 倍的示例。Read more »
- 漂亮的终端输出 - Monorepos 可以有数百或数千个项目。打印每个命令的所有内容使得很难看出失败的原因和失败的原因。值得庆幸的是,Lerna 做得更好。
- 强大的图形可视化工具 - Lerna 配备了强大的交互式可视化工具,可简化您对工作空间的理解。
- 发布到 NPM - Lerna 是将多个包发布到 npm 的终极工具。无论这些软件包是否有独立版本,Lerna 都能满足您的需求。
- 易于采用 - 即使具备所有这些功能,Lerna 也很容易采用。它需要接近于零的配置。
Getting Started
YouToBe视频介绍 (Getting started with Lerna v7) Lerna 附带了一个专用的 init 命令,可帮助您将 lerna 添加到现有存储库,或从头开始创建一个。
从头开始
在最简单的情况下,lerna init 可用于在空目录中创建新的存储库。为此,我们可以运行以下命令:
# 创建一个空目录
mkdir ./new-lerna-workspace
# 切换到新目录
cd ./new-lerna-workspace
# 初始化 lerna (使用 --dryRun 预览更改)
npx lerna init --dryRun
请注意,我们在这里传递了 --dryRun 标志,这使我们能够预览 lerna init 对我们的文件系统所做的更改。这使我们能够调整传递给 lerna init 的任何其他参数的值(例如 --exact 或 --independent),而不必担心消除任何错误。
一旦我们对它所做的更改感到满意,我们就可以简单地重复 npx lerna init 命令,但不要使用 --dryRun 标志。
现在,您将启动并运行一个工作 git 存储库,包括 npm 工作区,并且 lerna 可用于创建、版本控制和发布您想要开发的任何包。
将 lerna 添加到现有存储库
如果您已经有一个现有的存储库,您仍然可以使用 lerna init 将 lerna 添加到其中。
Lerna 不负责在存储库中安装和链接依赖项,您的包管理器更适合该任务。相反,我们强烈建议配置您选择的包管理器以使用其工作区功能:
npm(docs.npmjs.com/cli/using-n…)yarn(yarnpkg.com/features/wo…)pnpm(pnpm.io/workspaces)
当在现有存储库上初始化 lerna 时,它将需要一种方法来知道它应该在哪些包上运行。如果您正在使用包管理器的工作区功能(请参阅上面的注释),那么 lerna 将默认使用您已经配置的工作区模式。不需要额外的参数。
或者,您可以通过使用 lerna init 的 --packages 标志来手动指定一组要匹配的模式:
# Passing a single pattern
npx lerna init --packages="packages/*"
# Passing multiple patterns
npx lerna init --packages="foo/*" --packages="bar/*"
Lerna and Nx
Nrwl(开源构建系统 Nx 背后的公司)已接管 Lerna 的管理权。Nx 是由前 Google 员工开发的构建系统,利用了 Google 内部工具使用的许多技术。Lerna 使用 Nx 来检测工作区中的包以及它们之间的依赖关系。Lerna 遵循 Nx 强大的任务运行程序来运行脚本,允许您并行运行它们、缓存结果并将它们分发到多台计算机上,同时确保尊重包之间的依赖关系。有关哪些 Lerna 版本与哪些 Nx 版本兼容的完整列表,请参阅Lerna and Nx Version Matrix.
以下是每个工具提供的功能的高级概述。Lerna 可以继续单独使用,并且免费添加 Nx Cloud 可以极大地改进您已经在做的事情。
Lerna特征
- 版本 - 自动增加包的版本、生成变更日志信息、创建 Github 版本等。
- 发布 - 自动创建标签并将包发布到包注册表,例如 npm。
Cost
免费和开源
Set up
npm install lernanpx lerna init
Nx Cloud 特征
- 在整个组织内共享缓存的任务结果
- 分配任务执行 高效地跨代理机器
Cost
对于开源项目免费
对于闭源存储库,每月前 500 个计算小时是免费的。大多数存储库不会超过此限制。此后每计算小时 1 美元。
Set up
npx nx connect-to-nx-cloudnx generate @nrwl/workspace:ci-workflow(or set up your CI manually)- Continue using Lerna as usual
Features
Run Tasks
Monorepos 可以拥有数百甚至数千个项目,因此能够针对所有(或部分)项目运行 npm 脚本是 Lerna 这样的工具的一个关键功能。
定义
- Command - 开发人员在终端中输入的任何内容 (e.g.,
lerna run build --scope=header --concurrency=5). - Target - the name of an npm script (e.g.,
build) - Task - an invocation(调用) of an npm script (e.g.,
header:build).
示例存储库
示例基于此存储库,因此请随意克隆它并继续操作。
Run Everything
每个项目都定义了 test 和 build 脚本。
npx lerna run build
这将以正确的顺序构建项目: footer and header and then remixapp.
Terminal Output:
✔ header:build (501ms)
✔ footer:build (503ms)
✔ remixapp:build (670ms)
—————————————————————————————————————————————————————————————————————————————
> Lerna (powered by Nx) Successfully ran target build for 3 projects (1s)
请注意,Lerna 并不关心每个构建脚本的作用。build 也并不特殊:它只是 npm 脚本的名称。
同时运行多个任务
您可以传递希望触发同时运行的目标的逗号分隔列表。
npx lerna run test,build,lint
例如,如果您的任务之间存在依赖关系,例如需要在测试特定包之前运行构建,那么只要您配置了适当的任务管道配置,任务运行程序就会为您进行协调。
单个包运行任务
在开发过程中,您很少运行所有构建或所有测试。相反,您通常只针对您正在更改的项目运行操作。例如,您可以像这样运行header测试:
npx lerna run test --scope=header
运行受 PR 影响的任务
您还可以为 PR 中受影响的所有项目运行命令,如下所示:
npx lerna run test --since=origin/main
控制任务的运行方式
要更好地控制任务的执行顺序,请编辑(任务管道配置 Task Pipeline Configuration)
要加快任务执行速度,请了解如何缓存任务结果和分发任务执行
要加快任务执行速度, 请了解如何 缓存任务 Cache Task Results 和 分发任务执行 Distribute Task Execution
自动加载.env文件
默认情况下,由 Nx 提供支持的现代任务运行程序将自动为您加载 .env 文件。如果您出于任何原因想要禁用此行为,可以将 --load-env-files 设置为 false。
缓存任务结果
当涉及到运行任务、缓存等时,Lerna 和 Nx 可以互换使用。当我们说“Lerna 可以缓存构建”时,我们的意思是 Lerna 使用可以缓存构建的 Nx。
一遍又一遍地重建和重新测试相同的代码成本高昂。 Lerna 使用计算缓存来避免两次重建相同的代码。
Setup
Lerna via Nx 拥有最复杂且经过实战考验的计算缓存系统。它知道您要运行的任务之前已经执行过,因此它可以使用缓存来恢复运行该任务的结果。
如果您没有 nx.json,请运行 npx lerna add-caching
要为build和test启用缓存,请编辑 nx.json 中的 cacheableOperations 属性以包含build和test任务:
nx.json
{
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build", "test"]
}
}
}
}
请注意,cacheableOperations 需要没有副作用,这意味着给定相同的输入,它们应该始终产生相同的输出。例如,无法缓存命中后端 API 的 e2e 测试运行,因为后端可能会影响测试运行的结果。
现在,运行以下命令两次。第二次操作将是即时的:
lerna run build --scope=header
Terminal Output
> lerna run build --scope=header
> header:build [existing outputs match the cache, left as is]
> header@0.0.0 build
> rimraf dist && rollup --config
src/index.tsx → dist...
created dist in 858ms
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> Lerna (powered by Nx) Successfully ran target test for project header (4ms)
Nx read the output from the cache instead of running the command for 1 out of 1 tasks.
从缓存重播
当 Lerna 确定任务的输入未更改时,它会重新创建该任务的输出,就像它实际在您的计算机上运行一样 - 但速度要快得多。缓存任务的输出包括终端输出和在为该任务定义的输出目录中创建的文件。
您可以通过删除 header:build 任务输出到的 dist 文件夹,然后再次运行 lerna run build --scope=header 来测试这一点。缓存的任务将立即重播,并且正确的文件将出现在 dist 文件夹中。
header/
└── dist/ <-- this folder gets recreated
如果您的任务在不同位置创建输出工件,您可以更改缓存的输出文件夹。您还可以自定义哪些输入发生更改将使缓存失效。
高级缓存
要更深入地了解缓存实现并微调您的存储库的缓存,查阅 How Caching Works.
本地计算缓存
默认情况下,Lerna(通过 Nx)使用本地计算缓存。Nx 仅将缓存值存储一周,之后将被删除。要清除缓存,请运行 nx Reset,Nx 将在下次尝试访问它时创建一个新缓存。
分享缓存
Lerna 提供的计算缓存可以分布在多台机器上。您可以构建缓存的实现或使用 Nx Cloud。Nx Cloud 是一款提供快速且零配置的分布式缓存实施的应用程序。对于 OSS 项目和大多数闭源项目来说是完全免费的。
您可以通过运行以下命令将工作区连接到 Nx Cloud:
npx nx connect-to-nx-cloud
Terminal Output
✔ Enable distributed caching to make your CI faster · Yes
> NX Generating @nrwl/nx-cloud:init
UPDATE nx.json
> NX Distributed caching via Nx Cloud has been enabled
In addition to the caching, Nx Cloud provides config-free distributed execution,
UI for viewing complex runs and GitHub integration. Learn more at https://nx.app
Your workspace is currently unclaimed. Run details from unclaimed workspaces can be viewed on cloud.nx.app by anyone
with the link. Claim your workspace at the following link to restrict access.
https://cloud.nx.app/orgs/workspace-setup?accessToken=YOURACCESSTOKEN
要查看远程缓存的运行情况,请运行:
lerna run build --scope=header && nx reset && lerna run build --scope=header
Terminal Output
> lerna run build --scope=header
> header@0.0.0 build
> rimraf dist && rollup --config
src/index.tsx → dist...
created dist in 786ms
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> Lerna (powered by Nx) Successfully ran target build for project header (2s)
See logs and investigate cache misses at https://cloud.nx.app/runs/k0HDHACpL8
> NX Resetting the Nx workspace cache and stopping the Nx Daemon.
This might take a few minutes.
> NX Daemon Server - Stopped
> NX Successfully reset the Nx workspace.
> lerna run build --scope=header [remote cache]
> header@0.0.0 build
> rimraf dist && rollup --config
src/index.tsx → dist...
created dist in 786ms
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> Lerna (powered by Nx) Successfully ran target build for project header (664ms)
Nx read the output from the cache instead of running the command for 1 out of 1 tasks.
Nx Cloud made it possible to reuse header: https://nx.app/runs/P0X6ZGTkqZ
将工作区连接到 Nx 云帐户
在工作区启用 Nx Cloud 后,您将看到以下内容:
> NX NOTE Nx Cloud has been enabled
Your workspace is currently public. Anybody with code access
can view the workspace on nx.app.
You can connect the workspace to your Nx Cloud account at
https://nx.app/orgs/workspace-setup?accessToken=N2Y3NzcyO...
(You can do this later.)
单击此链接将工作区与您的 Nx Cloud 帐户关联。如果您没有 Nx Cloud 帐户,您可以当场创建一个。
认领工作空间后,您将能够管理权限、创建访问令牌、设置计费等。
您还将看到一个交互式教程,帮助您探索分布式缓存和 Nx Cloud 用户界面。
如果您丢失此链接,您仍然可以将您的工作区连接到 Nx Cloud。转到 nx.app,创建一个帐户,然后使用 nx.json 中的访问令牌连接您的工作区。
如果您丢失此链接,您仍然可以将您的工作区连接到 Nx Cloud。转到 nx.app,创建一个帐户,然后使用 nx.json 中的访问令牌连接您的工作区。
Skipping(跳过) Cloud
--skip-nx-cache 将指示 Nx 不要使用缓存类似,传递 --no-cloud 将告诉 Nx 不要使用 Nx Cloud。
探索项目图
为了让 Lerna(和 Nx)快速、正确地运行任务,它会创建存储库中所有项目之间的依赖关系图。直观地探索该图有助于理解 Lerna 为何以某种方式运行,并获得代码架构的高级视图。
要启动项目图形可视化,请运行:
nx graph
这将打开一个浏览器窗口,其中包含当前代码库的项目图的交互式表示。即使对于较小的存储库,查看整个图表也可能难以管理,因此有多种方法可以将可视化的焦点缩小到当前图表中最有用的部分。
- 专注于特定项目,然后使用邻近度和按文件夹分组控件来修改该项目周围的图表。
- 使用搜索栏查找名称中包含特定字符串的所有项目。
- 手动隐藏或显示侧边栏中的项目
显示图表后,您可以单击单个依赖项链接来查找创建该依赖项的特定文件。
JSON 项目图
如果您喜欢使用脚本或其他工具分析项目图的基础数据,您可以运行:
nx graph --file=output.json
这将为您提供用于创建项目图形可视化的所有信息。
Distribute Task Execution (分布式任务执行 DTE)
Lerna 通过缓存和 --since 标志加快了平均 CI 时间。但这些功能都无法应对最坏的情况。当您的存储库的核心内容被修改并且每个任务都需要在 CI 中运行时,提高性能的唯一方法是添加更多代理作业并有效地并行化任务。
并行化任务最明显的方法是按类型拆分任务:在一个作业上运行所有测试,在另一个作业上运行所有测试,在第三个作业上运行所有 lint 任务。这种策略称为分箱。如果某些测试任务以构建任务作为先决条件,这可能会变得困难,但假设您找到某种方法来处理该问题,典型的设置可能如下图所示。这里,测试任务被延迟,直到所有必要的构建工件准备就绪,但构建和 lint 任务可以立即开始。
分箱方法的问题是您最终会在一项或多项工作上有一些空闲时间。Nx 的分布式任务执行通过根据任务的平均运行时间将每个单独的任务分配给代理作业,将空闲时间降至最低。Nx 还保证任务以正确的顺序执行,并使用分布式缓存来确保以前任务的构建工件存在于需要它们的每个代理作业上。
当您设置 Nx 的分布式任务执行时,您的任务图将看起来更像这样:
CI 不仅可以更快地完成,而且调试体验与在单个作业上运行所有 CI 一样。这是因为 Nx 使用分布式缓存来重新创建所有日志并在主作业上构建工件。
Set up
要分发任务执行,您需要 (1) 连接到 Nx Cloud 并 (2) 在 CI 工作流程中启用 DTE。这些步骤中的每一个都可以使用单个命令来启用:
- Connect to Nx Cloud
nx connect-to-nx-cloud
- Enable DTE in CI
nx generate @nrwl/workspace:ci-workflow --ci=github
--ci 标志可以是 github、circleci 或 azure。
CI执行流程
分布式任务执行可以在任何 CI 提供商上运行。您负责在 CI 系统中启动作业。然后,Nx Cloud 协调这些作业的协同工作方式。您需要在 CI 系统中创建两种不同类型的作业。
- 控制要执行的内容的一项主要工作
- 实际执行任务的多个代理作业
主要作业执行流程如下所示:
# Coordinate the agents to run the tasks
- npx nx-cloud start-ci-run
# Run any commands you want here
- lerna run lint --since=main & lerna run test --since=main & lerna run build --since=main
# Stop any run away agents
- npx nx-cloud stop-all-agents
Agent作业执行流程非常简单:
# Wait for tasks to execute
- npx nx-cloud start-agent
主要工作看起来或多或少与您没有使用任何发行版相同。您唯一需要做的就是在开始时调用 npx nx-cloud start-ci-run ,并可选择在结束时调用 npx nx-cloud stop-all-agents 。
代理作业运行长时间运行的启动代理进程,该进程执行与给定 CI 运行关联的所有任务。设置它们所需要做的唯一一件事就是调用 npx nx-cloud start-agent。该进程将继续运行,直到 Nx Cloud 告诉它终止。
请注意,重要的是主作业和代理作业具有相同的环境和相同的源代码。他们大约在同一时间开始。而且,一旦主要工作完成,所有代理都将停止。
还需要注意的是,Nx Cloud 代理不是机器,而是在机器上运行的长时间运行的进程。也就是说,Nx Cloud 不管理您的代理——您需要在 CI 配置中进行管理(查看下面的 CI 示例)。
Nx Cloud 是一个协调器。主要作业告诉 Nx Cloud 您要运行什么,Nx Cloud 将在代理之间分配这些任务。 Nx Cloud 会自动将文件从一个代理移动到另一个代理,从代理移动到主作业。
最终结果是,当 lerna run build --since=main 在主作业上完成时,在代理上创建的所有文件工件都会复制到主作业,就好像主作业已在本地构建了所有内容一样。
并行运行事物
--concurrency 被传播到代理. 例如, npx lerna run build --since=main --concurrency=3 --dte 告诉 Nx Cloud 在每个代理上并行运行最多 3 个构建目标。因此,如果您有 10 个代理,您将在所有代理上并行运行最多 30 个构建。
您还希望并行运行尽可能多的命令。例如,
- lerna run lint --since=main
- lerna run test --since=main
- lerna run build --since=main
或者
- lerna run lint --since=main & lerna run test --since=main & lerna run build --since=main
后者将同时安排所有三个命令,因此如果代理找不到任何要构建的内容,它将开始运行测试和 lints。结果是更好的代理利用率和更短的 CI 时间。
CI/CD 示例
下面的示例展示了如何使用 Nx 和 Nx Cloud 使用分布式任务执行和分布式缓存来设置 CI。
每个组织以不同的方式管理其 CI/CD 管道,因此这些示例不涵盖 CI/CD 的特定于组织的方面(例如部署)。他们主要关注正确配置 Nx。
阅读指南以获取有关如何在 CI 中配置它们的更多信息。
请注意,只能分发可缓存的操作,因为它们必须在主作业上重播。
版本及发布
Lerna 可以增加包的版本以及将包发布到 NPM,并且它提供了多种选项来确保可以适应任何工作流程。
为了展示 Lerna 是如何做到这一点的,我们将查看this repository。
如果您通过实践学习得更好,请克隆存储库并继续操作。该存储库包含三个包或项目:
header(a library of React components)footer(a library of React components)remixapp(an app written using the Remix framework which depends on bothheaderandfooter)
我们将发布header和footer。
仅发布项目的子集是很常见的。有些项目可以是私有的(例如,仅用于测试),有些可以是演示应用程序。在此存储库中,remixapp 不是私有的,它只是不会发布到 NPM。
Versioning
Lerna 附带了一个版本命令,允许您增加包的版本号、提交更改并相应地标记它们。
lerna version --no-private
你会得到以下输出:
lerna notice cli v5.1.2
lerna info current version 1.0.0
lerna info Assuming all packages changed
? Select a new version (currently 1.0.0) (Use arrow keys)
❯ Patch (1.0.1)
Minor (1.1.0)
Major (2.0.0)
Prepatch (1.0.1-alpha.0)
Preminor (1.1.0-alpha.0)
Premajor (2.0.0-alpha.0)
Custom Prerelease
Custom Version
请注意,通过传递 --no-private 我们排除了在 package.json 文件中标记为私有的所有包。
Lerna 检测当前软件包,识别当前版本并建议选择下一个版本。注意,你也可以像 lerna 1.0.0 版本一样直接传递 semver 碰撞。More on the version docs details.选择给定版本后,Lerna 会使用版本号更新 package.json,提交更改,添加相应的版本标签(例如 v1.0.0),并将提交和标签推送到远程存储库。
packages/footer/package.json
{
"name": "footer",
"version": "1.0.1",
"main": "dist/index.js",
...
}
请注意,上述操作不会将包推送到任何 NPM 存储库。如果我们还希望 Lerna 负责发布过程,我们可以使用 lerna publish 来代替。
Lerna使用lerna.json中的version属性来确定当前使用的版本。
发布到 NPM
如果运行指令:
lerna publish --no-private
Lerna 执行版本递增工作流程(与 lerna 版本相同),此外还将包推送到 NPM。您应该得到以下输出:
Terminal Output
lerna notice cli v5.1.2
lerna info current version 1.0.0
lerna info Assuming all packages changed
? Select a new version (currently 1.0.0) Patch (1.0.1)
Changes:
- footer: 1.0.0 => 1.0.1
- header: 1.0.0 => 1.0.1
? Are you sure you want to publish these packages? Yes
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna info publish Publishing packages to npm...
...
lerna success published header 1.0.1
...
lerna success published footer 1.0.1
...
Successfully published:
- footer@1.0.1
- header@1.0.1
lerna success published 2 packages
from-package
Lerna 确定要发布哪些包的另一种方法是使用 from-package。Lerna 会将存储库中每个包的版本与发布到 npm 的版本进行比较。对于版本高于已发布版本的每个包,Lerna 都会将该包发布到 npm。
此模式并不明确要求包已使用 lerna version 进行版本控制,这对于拥有自己的版本控制脚本的工作区来说非常有用。
lerna publish from-package
Lerna 总是使用 npm 来发布包。如果您使用 npm 以外的包管理器,则仍然需要将适当的发布配置添加到 .npmrc,即使 npmClient 在 lerna.json 中设置为 npm 以外的其他内容。
版本控制策略
Lerna 允许您使用两种模式之一来管理您的项目:固定模式或独立模式。
Fixed/Locked mode (default)
固定模式 Lerna 项目在单一版本线上运行。该版本保存在项目根目录下的 lerna.json 文件中的版本密钥下。当您运行 lerna publish 时,如果自上次发布以来软件包已更新,它将更新为您要发布的新版本。这意味着您仅在需要时才发布包的新版本。
注意:如果您的主要版本为0,则所有更新都被视为破坏。因此,使用主版本 0 运行 lerna publish 并选择任何非预发布版本号将导致为所有包发布新版本,即使自上次发布以来并非所有包都已更改。
如果您想自动将所有包版本绑定在一起,请使用此选项。这种方法的一个问题是,任何包中的重大更改都将导致所有包都有新的主要版本。
Independent mode
npx lerna init --independent
独立模式 Lerna 项目允许维护者相互独立地增加包版本。每次发布时,您都会收到每个已更改的包的提示,以指定它是补丁、次要更改、主要更改还是自定义更改。
独立模式允许您更具体地更新每个包的版本,并且对于一组组件有意义。将这种模式与语义释放之类的东西结合起来会减轻痛苦。 (atlassian/lerna-semantic-release 已经有这方面的工作)。
将lerna.json中的版本键设置为independent以独立模式运行。
编辑器集成
Nx 控制台在 VS Code 侧栏中显示所有项目的 npm 脚本,并允许您通过单击运行它们或在编辑器中打开脚本定义。
Workspace Watching
从 Lerna 6.4.0 开始,工作区监视功能可用。
Lerna 可以监视包内的文件更改,并自动从存储库的根目录执行命令。如果您在开发工作流程中更新文件时需要重建包或重新运行测试,这非常有用。
这取代了手动设置单独监视每个包的需要。
Examples
监视所有包并回显包名称和更改的文件
$ lerna watch -- echo \$LERNA_PACKAGE_NAME \$LERNA_FILE_CHANGES
监视所有包并在包中的文件发生更改时在包上运行“构建”脚本:
$ lerna watch -- lerna run build --scope=\$LERNA_PACKAGE_NAME
监视所有包并对受更改影响的所有内容运行“构建”脚本:
$ lerna watch -- lerna run build --since
监视单个包并在其中的文件发生更改时在其上运行“构建”脚本:
$ lerna watch --scope="my-package-1" -- lerna run build --scope=\$LERNA_PACKAGE_NAME
观察单个包及其依赖项,对其中任何一个发生更改的包运行“构建”脚本:
$ lerna watch --scope="my-package-1" --include-dependencies -- lerna run build --scope=\$LERNA_PACKAGE_NAME
监视所有包并为已更改的包以及依赖它的所有包运行构建脚本:
$ lerna watch -- lerna run build --scope=\$LERNA_PACKAGE_NAME --include-dependents
观察环境变量
运行内部命令时,Lerna 将设置环境变量 $LERNA_PACKAGE_NAME 和 $LERNA_FILE_CHANGES。这些可用于自定义运行的命令。
$LERNA_PACKAGE_NAME将替换为已更改的包的名称。$LERNA_FILE_CHANGES将被更改的文件替换。 如果在一个周期内检测到多个文件更改,则$LERNA_FILE_CHANGES将列出所有文件,并用空格分隔。
注意:使用 $LERNA_PACKAGE_NAME 和 $LERNA_FILE_CHANGES 时,您需要使用反斜杠 () 对 $ 进行转义。
使用包管理器运行
上面的示例展示了直接在终端中使用 lerna。但是,您也可以通过包管理器使用 lerna,而无需将其添加到您的路径中:
pnpm:
pnpm lerna watch -- lerna run build --scope=\$LERNA_PACKAGE_NAME
yarn:
yarn lerna -- watch -- lerna run build --scope=\$LERNA_PACKAGE_NAME
npx:
npx -c 'lerna watch -- lerna run build --scope=\$LERNA_PACKAGE_NAME'
注意:在使用 npx 时,您需要使用 -c 并将整个 lerna watch 命令用单引号 (') 括起来。如果没有这个,npx 将在将命令传递给 lerna 之前尝试替换监视环境变量,从而导致 $LERNA_PACKAGE_NAME 和 $LERNA_FILE_CHANGES 始终为空值。
Concepts(概念)
任务管道配置
Lerna 将 npm 脚本(分叉进程等)的运行委托给 Nx。您可以在 nx.json 文件中配置 Nx 的工作方式。如果您没有 nx.json,请运行 npx lerna add-caching。
并行运行任务
如果要将运行脚本的进程数量增加到 5 个(默认情况下为 3),请传递以下命令:
npx lerna run build --concurrency=5
请注意,您还可以更改 nx.json 中的默认值,如下所示:
nx.json
{
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [],
"parallel": 5
}
}
}
}
定义任务依赖关系(也称为任务管道)
没有我们的帮助,Lerna 无法知道哪些目标(脚本)有先决条件,哪些没有。您可以在 nx.json 文件中定义任务依赖项:
nx.json
{
...
"targetDefaults": {
"build": {
"dependsOn": ["^build"]
}
}
}
由此,Lerna 知道在构建项目之前,需要首先构建其所有依赖项。然而,对测试没有任何限制。
定义 targetDefaults 属性后,排序标志将被忽略。这个机制非常灵活。让我们看一下这个例子:
nx.json
{
...
"targetDefaults": {
"build": {
"dependsOn": ["^build", "prebuild"]
},
"test": {
"dependsOn": ["build"]
}
}
}
请注意,旧版本的 Nx 使用 targetDependency 而不是 targetDefaults。两者仍然有效,但建议使用 targetDefaults。
^ 符号(又名插入符号)仅表示依赖关系。因此,“test”:{“dependsOn”:[“build”]}意味着特定项目的“test”目标依赖于其自己的“build”目标在运行前已经完成,"build": { "dependsOn": ["^build"] } 表示特定项目的“构建”目标依赖于在运行前已完成的所有项目依赖项的“构建”目标。
当运行 lerna run test --scope=myproj 时,上面的配置会告诉 Lerna,
- 运行 myproj 的测试命令
- 但由于测试 -> 构建定义了依赖项,Lerna 首先为 myproj 运行构建。
build本身定义了对prebuild(在同一项目上) 的依赖以及所有依赖项的build. 因此, 它将运行prebuild脚本 并将运行所有依赖项的build脚本。
请注意,Lerna 在开始运行测试之前不必运行所有构建。只要满足约束条件,任务编排器就会并行运行尽可能多的任务。
像这样的情况很常见:
因为我们在 nx.json 中描述了规则,所以它们将适用于存储库中的所有项目。您还可以通过将特定于项目的规则添加到项目的 package.json 中来定义它们。
{
...
"nx": {
"targets": {
"test": {
"dependsOn": [
"build"
]
}
}
}
}
缓存的工作原理
在运行任何任务之前,Lerna 都会计算其计算哈希值。只要计算哈希相同,运行任务的输出就相同。默认情况下,例如 lerna run test --scope=remixapp 的计算哈希包括:
remixapp的所有源文件及其依赖项- 相关全局配置
- 外部依赖项的版本
- 用户配置的运行时值,例如 Node 的版本
- CLI 命令标志
此行为是可定制的。例如,lint 检查可能仅依赖于项目的源代码和全局配置。构建可以依赖于已编译库的 dts 文件而不是其源。
Lerna 计算任务的哈希值后,它会检查之前是否运行过这个精确的计算。首先,它在本地进行检查,然后如果丢失,如果配置了远程缓存,则进行远程检查。
如果 Lerna 找到计算,Lerna 会检索它并重播它。 Lerna 将正确的文件放入正确的文件夹中并打印终端输出。从用户的角度来看,命令运行相同,只是速度快得多。
如果 Lerna 没有找到相应的计算哈希,Lerna 就会运行该任务,完成后,它会获取输出和终端日志并将它们存储在本地(如果也远程配置)。所有这一切都是透明发生的,因此您不必担心。
尽管从概念上讲这相当简单,但 Lerna 对其进行了优化,以使这种体验对您有利。以 Lerna 为例:
- 捕获 stdout 和 stderr 以确保重放的输出看起来相同,包括在 Windows 上。
- 通过记住哪些文件在哪里重播来最小化 IO。
- 仅在处理大型任务图时显示相关输出。
- 提供对缓存未命中进行故障排除的功能。以及许多其他优化。
随着工作区的增长,任务图看起来更像这样:
所有这些优化对于使 Lerna 可用于任何重要的工作空间至关重要。仅发生最少量的工作。其余部分要么保持原样,要么从缓存中恢复。
源代码哈希输入
building/testing 应用程序或库的结果取决于该项目的源代码以及它所依赖的所有库的所有源代码(直接或间接)。
默认情况下, Lerna 是保守的。运行时,例如 lerna run test --scope=remixapp Lerna 将考虑 remixapp 目录中的所有文件以及页眉和页脚目录中的所有文件(remixapp 依赖项)。这将导致不必要的缓存未命中。例如,我们知道更改页脚的规范文件不会更改上面测试命令的结果。我们可以定义更精确的配置,如下所示:
nx.json
{
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"prod": ["!{projectRoot}/**/*.spec.tsx"]
},
"targetDefaults": {
"build": {
"inputs": ["prod", "^prod"]
},
"test": {
"inputs": ["default", "^prod", "{workspaceRoot}/jest.config.ts"]
}
}
}
注意:“{projectRoot}”和“{workspaceRoot}”是任务运行程序支持的特殊语法,在命令运行时将在内部进行适当的插值。因此,您不应将“{projectRoot}”或“{workspaceRoot}”替换为固定路径,因为这会使您的配置不太灵活。
通过此配置,构建脚本将仅考虑 remixapp、页眉和页脚的非测试文件。测试脚本将考虑被测项目的所有源文件以及其依赖项的非测试文件。测试脚本还将考虑工作区根目录下的 jest 配置文件。
运行时哈希输入
您的目标还可以取决于运行时值。
nx.json
{
"targetDefaults": {
"build": {
"inputs": [{ "env": "MY_ENV_NAME" }, { "runtime": "node -v" }]
}
}
}
Args 哈希输入
最后,除了源代码哈希输入和运行时哈希输入之外,Lerna 还需要考虑参数:例如 lerna run build --scope=remixapp 和 lerna run build --scope=remixapp -- --flag=true 产生不同的结果结果。
请注意,只有传递给 npm 脚本本身的标志会影响计算结果。例如,从缓存的角度来看,以下命令是相同的。
npx lerna run build --scope=remixapp
npx lerna run build --ignore=header,footer
换句话说,Lerna 不会缓存开发人员在终端中输入的内容。
如果您build/test/lint…多个项目,每个单独的构建都有自己的哈希值,并且将从缓存中检索或运行。这意味着从缓存的角度来看,以下命令:
npx lerna run build --scope=header,footer
与以下两个命令相同:
npx lerna run build --scope=header
npx lerna run build --scope=footer
什么是缓存
Lerna 在流程层面上工作。无论您的项目使用什么工具来build/test/lint/等,结果都会被缓存。
Lerna 在运行命令之前设置钩子来收集 stdout/stderr。所有输出都会被缓存,然后在缓存命中期间重播。
Lerna 还缓存命令生成的文件。文件/文件夹列表列在项目的 package.json 的输出属性中:
E.g. packages/my-project/package.json
{
"nx": {
"targets": {
"build": {
"outputs": ["{projectRoot}/build", "{projectRoot}/public/build"]
}
}
}
}
注意:“{projectRoot}”和“{workspaceRoot}”是任务运行程序支持的特殊语法,在命令运行时将在内部进行适当的插值。因此,您不应将“{projectRoot}”或“{workspaceRoot}”替换为固定路径,因为这会使您的配置不太灵活。
如果给定目标的输出属性未在项目的 package.json 文件中定义,Lerna 将查看 nx.json 的 targetDefaults 部分:
nx.json
{
...
"targetDefaults": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
"{projectRoot}/dist",
"{projectRoot}/build",
"{projectRoot}/public/build"
]
}
}
}
如果两者都没有定义,Lerna 默认缓存 dist 并在存储库的根目录构建。
跳过缓存
有时您想跳过缓存。例如,如果您正在测量命令的性能,则可以使用 --skip-nx-cache 标志来跳过检查计算缓存。
npx lerna run build --skip-nx-cache
npx lerna run test --skip-nx-cache
自定义缓存位置
存默认存储在node_modules/.cache/nx中。要更改缓存位置,请更新 nx.json 中任务运行程序的 cacheDirectory 选项:
{
"tasksRunnerOptions": {
"default": {
"options": {
"cacheableOperations": ["build", "test"],
"cacheDirectory": "/tmp/mycache"
}
}
}
}
分布式任务执行指南
本指南中的插图由 Nrwlian Nicole Oliver 创建
什么是任务?
从 Lerna 的角度来看,任务是项目上运行的目标。即在项目shared-product-ui上运行的目标测试是一个任务。
Nx Cloud 自动安排您的 CI 任务
让我们想象一下,对于 CI 中的每个 PR,您都希望检查、测试和构建所有受影响的项目。当您编写 CI 工作流程时,您无法知道每个 PR 会影响多少项目或每个任务需要多长时间。无论您设置得多么仔细,如果您手动分配静态数量的代理机器来进行检查、测试和构建,都会浪费时间。这种方法称为分箱。
幸运的是,通过分布式任务执行,Nx Cloud 可以在代理可用时动态地将任务分配给代理。
Nx Cloud 高效协调代理
设置 DTE 时,您可以定义 (1) 要运行的任务以及 (2) 可供 Nx Cloud 使用的代理数量。然后,Nx 云编排器将任务有效地分配给代理,以便所有代理都得到充分利用,并且您的 CI 流程尽快完成。
任务执行顺序很重要
有些任务需要在其他任务之前执行,但 Nx Cloud 在向代理分配任务时会考虑到这一点。
为什么要分配任务?
跨多个代理有效地并行化 CI 流程可以显着加快 CI 速度,从而帮助开发人员更快地发现问题并完成更多工作。
What Does It Cost?
Nx Cloud 对于开源项目是免费的。请联系 cloud-support@nrwl.io 进行设置。
对于闭源项目,每月前 500 个计算小时是免费的。大多数工作空间不会超过这个数量。不需要信用卡。 500 小时后,每计算小时的成本为 1 美元。
Security
您的实际代码并不存储在云中,但任务的哈希输入和缓存结果却存储在云中。可以对该数据启用端到端加密,这样没有您的密钥,任何人都无法查看该信息。此外,如果您想在自己的服务器上托管 Nx Cloud,您可以注册 Nx Private Cloud。
Example
这是一个示例存储库,展示了设置分布式任务执行是多么容易,显示了性能增益,并与分片/分箱进行了比较。
Illustration插图
这是完整的图解说明页面
配置已发布的文件
将包发布到注册表时,默认情况下会发布包源目录中的所有内容。这并不总是最佳的,因为通常存在仅与开发相关的文件,例如测试和配置文件,并且您可能首先编译源文件并将它们输出到 monorepo 设置中的集中位置。
Lerna 提供了许多配置选项,以确保仅打包适当的文件并将其发布到注册表。
"files" 和 .gitignore
Lerna 总是使用 npm 的工具进行发布,并且它有一些内置的方法来包含或排除文件。配置发布的包中包含哪些文件的最简单方法是通过 package.json 和 .gitignore 中的“files”属性。
--contents [legacy -> prefer --directory]
许多命令(包括 lerna publish)都支持通用的 --contents 选项,该选项设置要发布所有包的目录。
仅当您的 monorepo 中的包具有简单、统一的输出结构时,这才对发布有用。传递给 --contents 的参数必须是每个正在发布的包中存在的子目录。
在 v7 中,我们为 lerna 发布引入了更强大、更集中的 --directory 选项。请优先使用 --contents 选项,后者将来可能会被弃用。
--directory
在 v7 中,我们为 lerna 发布引入了更强大、更集中的 --directory 选项。
它可以在 lerna.json 中配置,并支持使用以下动态占位符:{workspaceRoot}、{projectRoot}、{projectName}。这些值将在发布时动态替换。
这意味着您现在可以简洁地表达风格统一的设置,但在所有包中并非完全相同。
例如,假设我们有一个 monorepo,我们在其中构建所有包,并将它们的输出设置为转到集中位置(例如,这在 Nx 工作区中很常见):
我们有packages/package-a,它将其构建输出写入dist/packages/package-a,以及packages/package-b,它将其构建输出写入dist/packages/package-b。
尽管路径完全不同,但我们现在可以使用占位符来表达一致的方法:
{workspaceRoot}/dist/{projectRoot}
{workspaceRoot} 将被替换为我们的 lerna 存储库的绝对路径,而 {projectRoot} 将被替换为packages/package-a(如果是package-a)和packages/package-b(如果是package-b)。
我们在 lerna.json 中应用它的方式如下:
// lerna.json
{
"version": "1.0.0",
"command": {
"publish": {
"directory": "{workspaceRoot}/dist/{projectRoot}"
}
}
}
如果您需要针对特定包进行完全自定义的操作,您还可以在包的 package.json 中设置或覆盖发布目录选项。
从存储库根目录中的 dist/packages/foo 文件夹发布的包的示例配置:
// packages/foo/package.json
{
"name": "foo",
"version": "1.0.0",
"lerna": {
"command": {
"publish": {
"directory": "../../dist/packages/foo"
}
}
}
}
您需要确保您的自定义目录位置包含将用于注册表发布的有效 package.json。如果您需要涉及更复杂的自定义逻辑,您可以通过生命周期脚本(例如prepare、prepublishOnly或prepack)创建它,或者如果您需要涉及更复杂的自定义逻辑,则进行预打包,或者只是通过将其配置为资产来自动从包的源中复制它。
如果你想让你的包之一像标准 lerna 包一样运行并从源代码发布,你可以像这样覆盖它的发布配置:
// packages/foo/package.json
{
"name": "foo",
"version": "1.0.0",
"lerna": {
"command": {
"publish": {
"directory": "."
}
}
}
}
在已发布的包中包含其他资源
Lerna 可以将文件从源目录复制到指定用于发布的目录。就像目录选项一样,可以在 lerna.json 中(包括在资产定义中使用动态占位符)或特定包的 package.json 中进行配置。
无论在哪个文件中配置,“assets”属性都应该是具有“from”和“to”属性的 glob 模式或对象的数组。“from”属性应该是与源目录中的文件匹配的特定文件或 glob 模式,“to”属性是将文件复制到发布目录中的路径。
此示例包将其输出构建到根 dist/packages/bar 目录。 Lerna 配置为将其他文件复制到此目录,然后将 dist/packages/bar 的内容发布到 npm。
// packages/bar/package.json
{
"name": "bar",
"version": "1.0.0",
"lerna": {
"command": {
"publish": {
"directory": "../../dist/packages/bar",
"assets": [
"README.md",
"package.json",
"docs/*.md",
{
"from": "static/images/*",
"to": "assets"
},
{
"from": "../../CONTRIBUTING.md",
"to": "./"
}
]
}
}
}
}
Lerna 将使用上述配置并将适当的文件复制到 dist 目录,生成如下结构:
dist/packages/bar
├── assets
│ ├── my-image-1.png
│ └── my-image-2.png
├── CONTRIBUTING.md
├── docs
│ ├── my-doc-1.md
│ └── my-doc-2.md
├── package.json
└── README.md
将 pnpm 与 Lerna 一起使用
Lerna 可以在 pnpm 工作区中使用,以获得 pnpm 和 Lerna 的全部优势。当在 pnpm 工作区中使用时,Lerna 将:
-
解析包位置用
pnpm-workspace.yaml(pnpm.io/workspaces) -
在 lerna.json 中强制执行 useWorkspaces: true (并在 package.json 中忽略 packages:)。
-
阻止使用
bootstrap,link, 和add命令. 相反,您应该直接使用 pnpm 命令来管理依赖项(pnpm.io/cli/install). -
尊重 workspace protocol 的包依赖协议。
- 在
lerna version期间, 依赖项将正常更新,但会保留工作空间:前缀(如果存在)。 - 如果使用了 workspace alias , 那么
lerna version将不会更改依赖项的版本,因为别名没有指定要更改的版本号。
- 在
Getting Started
使用 Lerna 设置 pnpm
- 如果尚未安装,请安装
pnpm: pnpm.io/installatio…. - 删除根目录中的 node_modules/ 文件夹(如果存在)。如果尚未使用工作区, 运行
lerna clean来 移除 所有包中的node_modules/文件。 - 设置
"npmClient": "pnpm"和"useWorkspaces": true在lerna.json文件中. - 创建一个
pnpm-workspace.yaml文件 在项目的根目录中. 如果您已经在使用 npm 或 Yarn 工作区, 将“workspaces”属性从package.json移至pnpm-workspace.yaml。如果您尚未使用工作区, 将“packages”属性从lerna.json移至pnpm-workspace.yaml。示例:
// package.json
{
"workspaces": ["packages/*"]
}
// lerna.json
{
"packages": ["packages/*"]
}
修改为
// pnpm-workspace.yaml
packages:
- "packages/*"
- (可选)运行
pnpm import以从现有锁定文件生成 pnpm-lock.yaml 文件。 - 运行
pnpm install.
API Reference
Commands
lerna add-caching:运行设置基本缓存选项的向导。lerna changed:列出自上次标记版本以来已更改的本地包lerna clean:从所有包中删除node_modules目录lerna create:创建一个新的 lerna 管理的包lerna diff:自上次发布以来比较所有包或单个包lerna exec:在每个包中执行任意命令lerna import:将包导入到具有提交历史记录的 monorepo 中lerna info:打印本地环境信息lerna init:创建新的 Lerna 存储库或将现有存储库升级到当前版本的 Lernalerna list:列出本地包lerna publish:在当前项目中发布包lerna repair:更新配置文件以匹配当前安装的 lerna 版本lerna run:在包含该脚本的每个包中运行 npm 脚本lerna version:显示历史版本lerna watch:监视包内的更改并从存储库的根目录执行命令
Filter Options
Lerna 命令可以应用过滤器选项来控制它们操作的包。需要过滤的lerna子命令的选项
Options
--scope <glob>
仅包含名称与给定 glob 匹配的包。
$ lerna exec --scope my-component -- ls -la
$ lerna run --scope "toolbar-*" test
$ lerna run --scope package-1 --scope "*-2" lint
注意:对于某些 glob,可能需要引用选项参数以避免过早的 shell 扩展。
Running with npx
当使用 npx 运行 lerna 时,在传递 全局 参数时需要使用显式的“=”。这是为了防止 npx 过早地扩展参数。
For example:
$ npx lerna run --scope="toolbar-*" test
$ npx lerna run --scope="package-{1,2,5}" test
--ignore <glob>
排除名称与给定 全局 匹配的包。
$ lerna exec --ignore "package-{1,2,5}" -- ls -la
$ lerna run --ignore package-1 test
$ lerna run --ignore "package-@(1|2)" --ignore package-3 lint
--no-private
排除私人包裹。默认情况下包含它们。
--since [ref]
仅包含自指定引用以来已更改的包。如果没有传递 ref,则默认为最新的标签。
# List the contents of packages that have changed since the latest tag
$ lerna exec --since -- ls -la
# Run the tests for all packages that have changed since `main`
$ lerna run test --since main
# List all packages that have changed since `some-branch`
$ lerna ls --since some-branch
这在 CI 中使用时特别有用,如果您可以获得 PR 将进入的目标分支,因为您可以将其用作 --since 选项的引用。这对于进入默认分支和功能分支的 PR 非常有效。
--exclude-dependents
使用 --since 运行命令时排除所有传递依赖项,覆盖默认的“已更改”算法。
如果没有 --since,此标志不起作用,并且在这种情况下会抛出错误。
--include-dependents
运行命令时包括所有传递依赖项,无论 --scope、--ignore 或 --since。
--include-dependencies
运行命令时包括所有传递依赖项,无论 --scope、--ignore 或 --since。与任何接受 --scope 的命令(bootstrap、clean、ls、run、exec)结合使用。确保也对任何作用域包(通过 --scope 或 --ignore)的所有依赖项(和开发依赖项)进行操作。
注意:这将覆盖 --scope 和 --ignore 标志。即,如果与 --ignore 标志匹配的包被另一个正在引导的包所依赖,则该包仍将被引导。
这对于您想要“设置”依赖于正在设置的其他包的单个包的情况非常有用。
$ lerna bootstrap --scope my-component --include-dependencies
# my-component and all of its dependencies will be bootstrapped
$ lerna bootstrap --scope "package-*" --ignore "package-util-*" --include-dependencies
# all packages matching "package-util-*" will be ignored unless they are
# depended upon by a package whose name matches "package-*"
--include-merged-tags
$ lerna exec --since --include-merged-tags -- ls -la
使用 --since 运行命令时包含合并分支中的标签。仅当您从功能分支进行大量发布时这才有用,通常不建议这样做。
Configuration配置
Lerna 的配置分为两个文件:lerna.json 和 nx.json。
Lerna.json
npmClient
如果您不使用npm作为包管理器(例如,如果您使用yarn或pnpm),那么设置此值很重要,以便lerna在解析配置和包时可以调整其一些内部逻辑。对于 pnpm 来说尤其如此,因为它使用单独的 pnpm-workspaces.yaml 文件来定义其工作区配置。
packages
默认情况下,lerna 将尝试重用您选择的包管理器中可能拥有的任何工作区配置。如果您希望指定 lerna 操作的可用包的子集,您可以使用 packages 属性,该属性将告诉 Lerna 在哪里查找 package.json 文件。
lerna.json
{
"packages": ["packages/*"]
}
version
Lerna 有两种发布包的模式:固定和独立。当使用固定模式时,所有受影响的包将使用相同的版本发布。最后发布的版本记录在lerna.json中,如下:
lerna.json
{
"version": "independent"
}
commands
lerna.json 文件还可以对每个命令的选项进行编码,如下所示:
{
"command": {
"version": {
"allowBranch": "main",
"conventionalCommits": true
}
}
}
Nx.json
注意:“{projectRoot}”和“{workspaceRoot}”是任务运行程序支持的特殊语法,在命令运行时将在内部进行适当的插值。因此,您不应将“{projectRoot}”或“{workspaceRoot}”替换为固定路径,因为这会使您的配置不太灵活。
nx.json
{
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build", "test"]
}
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"prod": ["!{projectRoot}/**/*.spec.tsx"]
},
"targetDefaults": {
"build": {
"dependsOn": ["prebuild", "^build"],
"inputs": ["prod", "^prod"],
"outputs": ["{projectRoot}/dist"]
},
"test": {
"inputs": ["default", "^prod", "{workspaceRoot}/jest.config.ts"]
}
}
}
taskRunnerOptions
runner
Nx 中的所有内容都是可定制的,包括运行 npm 脚本。大多数时候,您将使用默认运行程序或@nrwl/nx-cloud 运行程序。
cacheableOperations
cacheableOperations 数组定义了 Nx 缓存的 npm 脚本/操作的列表。在大多数存储库中,所有非长时间运行的任务(即不服务)都应该是可缓存的。
Target Defaults
目标是 npm 脚本名称。您可以在 targetDefaults 部分的存储库中添加与每个项目的构建脚本关联的元数据。
dependsOn
目标可以依赖于其他目标。一个常见的场景是在构建项目之前必须先构建项目的依赖关系。dependentOn 属性可用于定义单个目标的依赖关系。
"dependsOn": [ "prebuild", "^build"] 告诉 Nx 每个构建脚本都需要同一个项目的预构建脚本和所有依赖项的构建脚本首先运行。
inputs & namedInputs
输入数组告诉 Nx 要考虑什么来确定脚本的特定调用是否应该缓存命中。输入分为三种类型:
Filesets(文件集)
Examples:
{projectRoot}/**.*.ts- same as
{fileset: "{projectRoot}/**/*.ts"} {workspaceRoot}/jest.config.ts- same as
{fileset: "{workspaceRoot}/jest.config.ts}
Runtime Inputs
Examples:
{runtime: "node -v"}
节点结果值经过哈希处理,因此永远不会显示。
Env Variables
Examples:
{env: "MY_ENV_VAR"}
节点结果值经过哈希处理,因此永远不会显示。
Named Inputs
Examples:
inputs: ["prod"]- same as
inputs: [{input: "prod", projects: "self"}]
通常相同的 glob 会出现在许多地方(例如,prod 文件集将排除所有项目的规范文件)。因为保持它们同步很容易出错,所以我们建议定义命名输入,然后您可以在所有这些地方引用它们。
Examples:
inputs: ["^prod"]- same as
inputs: [{input: "prod", projects: "dependencies"}]
与dependsOn类似,“^”符号表示“依赖项”。这是一个非常重要的想法,所以让我们用一个例子来说明它。
"test": {
"inputs": [ "default", "^prod" ]
}
上面的配置意味着测试目标依赖于给定项目的所有源文件,并且仅依赖于其依赖项的 prod 源(非测试源)。换句话说,它将测试源视为私有的。如果您的 remixapp 项目依赖于标头库,则更改标头测试不会对 remixapp 测试目标产生任何影响。
outputs
"outputs": ["{projectRoot}/dist"] 告诉 Nx 构建脚本将在哪里创建文件工件。提供的值实际上是默认值,因此在这种情况下我们可以省略它。"outputs": [] 告诉 Nx 测试目标不会在磁盘上创建任何工件。您可以列出任意数量的输出。您还可以使用 glob 或单个文件作为输出。
通常不需要此配置。 Nx 具有合理的默认值,可以实现上述配置。
Project-Specific Configuration
对于许多项目相似的工作区,nx.json 将包含整个 Nx 配置。有时,将特定于项目的配置放置在项目的 package.json 文件中很有用。
package.json
{
"name": "parent",
"scripts": {
"build": "...",
"test": "..."
},
"dependencies": {...},
"nx": {
"namedInputs": {
"prod": [
"!{projectRoot}/**/*.test.tsx",
"{workspaceRoot}/configs/webpack.conf.js"
]
},
"targets": {
"build": {
"dependsOn": [
"^build"
],
"inputs": [
"prod",
"^prod"
],
"outputs": [
"{workspaceRoot}/dist/parent"
]
}
}
"implicitDependencies": ["projecta", "!projectb"]
}
}
请注意,nx.json 中定义的namedInputs 和targetDefaults 只是默认值。如果您采用该配置并将其复制到每个项目的 package.json 文件中,结果将是相同的。
inputs & namedInputs
为给定目标定义输入将替换 nx.json 中定义的该目标名称的输入集。使用伪代码输入= packageJson.targets.build.inputs || nxJson.targetDefaults.build.inputs。
您还可以定义和重新定义命名输入。这实现了一个关键用例,您的 nx.json 可以定义如下内容(适用于每个项目):
"test": {
"inputs": [
"default",
"^prod"
]
}
项目可以定义其产品文件集,而无需重新定义测试目标的输入。
package.json
{
"name": "parent",
"scripts": {
"build": "...",
"test": "..."
},
"dependencies": {...},
"nx": {
"namedInputs": {
"prod": [
"!{projectRoot}/**/*.test.js",
"{workspacRoot}/jest.config.js"
]
}
}
}
在这种情况下,Nx 将为每个项目使用正确的产品输入。
dependsOn
为给定目标定义 dependentOn 将替换 nx.json 中定义的目标名称的dependsOn。使用伪代码dependsOn = packageJson.targets.build.dependsOn || nxJson.targetDefaults.build.dependsOn。
outputs
为给定目标定义输出将替换 nx.json 中定义的目标名称的输出。使用伪代码输出 = packageJson.targets.build.outputs || nxJson.targetDefaults.build.outputs。
implicitDependencies
"implicitDependency": ["projecta", "!projectb"] 行告诉 Nx 父项目依赖于projecta,即使其 package.json 中没有依赖项。 Nx 将以与处理显式依赖关系相同的方式处理此类依赖关系。它还告诉 Nx,即使对projectb 存在显式依赖,也应该忽略它。
Utilities
Lerna 提供了一些实用函数,可用于在 Lerna monorepo 中创建您自己的工具。
const utils = require("lerna/utils");
detectProjects()
detectorProjects() 函数创建 Lerna 在幕后使用的相同项目图形文件映射来执行其命令。这对于编写您自己的脚本非常有用,这些脚本需要与 Lerna 操作同一组包。
const { detectProjects } = require("lerna/utils");
const { projectGraph, projectFileMap } = await detectProjects();
返回的projectGraph将是一个ProjectGraphWithPackages,它是来自@nx/devkit的ProjectGraph类型的扩展。它包含有关具有 package.json 文件的项目的其他元数据。它还具有 localPackageDependency 属性,用于跟踪项目之间的内部 npm 依赖关系(而不是从注册表下载的外部 npm 依赖关系)。
projectFileMap 是项目名称到其中文件的映射。这用于确定当文件更改时需要对哪个项目进行版本控制。