tl dr_tl; dr

tl dr

tl; dr (tl;dr)

If you want to see the final repository, take a look at it here.

如果您想查看最终的存储库,请在此处查看

介绍 (Introduction)

Organizing code has become a difficult task. Monolithic applications are becoming difficult to scale, and releases are beginning to crumble under their architecture.

组织代码已成为一项艰巨的任务。 单片应用程序变得难以扩展 ,并且发布在其体系结构下开始崩溃。

This is how not only the deployment process evolved, but also the organization of the code.

这不仅是部署过程的演变方式,还是代码的组织方式。

​**IMHO**: Monolithic applications are not bad, and the monorepo approach is not new. The way we create and manage projects started to change somewhere as we started to use more and more micro-services. From that point of view, you can also organize your code into separate packages and have a nice way to put everything back together.

**恕我直言**:整体应用程序还不错,monorepo方法也不是新鲜事。 随着我们开始使用越来越多的微服务,我们创建和管理项目的方式开始发生变化。 从这个角度来看,您还可以将代码组织到单独的程序包中,并有一种很好的方法将所有内容重新组合在一起。

​In this blog post, we’ll create a starter repository for Node.js with some tools, and you’ll understand how things work in between.

在这篇博客文章中,我们将使用一些工具为Node.js创建一个启动器存储库,您将了解它们之间的工作方式。

要求 (Requirements)

I use Microsoft Windows for development. You should be able to run everything on a Mac or Linux.

我使用Microsoft Windows进行开发。 您应该能够在Mac或Linux上运行所有内容。

In this Post we will use following tools:

在这篇文章中,我们将使用以下工具:

and these npm packages:

这些npm软件包:

在你开始前 (Before you start)

As a software engineer, you should first consider whether this approach is appropriate for the project. If you don’t like this type of architecture, you can split the packages you have in the monorepo and develop them in a separate repository.

作为软件工程师,您首先应该考虑这种方法是否适合该项目。 如果您不喜欢这种类型的体系结构,则可以拆分monorepo中的软件包,并在单独的存储库中进行开发。

I tried to make all steps easy & understandable. But, I recommend reading the whole article before starting coding.

我试图使所有步骤变得简单易懂。 但是,我建议在开始编码之前阅读整篇文章。

莱娜 (Lerna)

Monorepos are not easy to handle. There are issues as dependency management, execution of tasks throughout the project, the build and pipeline process can change over time, and so on. In these cases, you can be sure that something might go wrong or even break on the way.

Monorepos不容易处理。 存在诸如依赖性管理,整个项目中的任务执行,构建和管道过程可能随时间变化等问题。 在这些情况下,您可以确定某些地方可能出错或中断。

To fix a lot of problems for us, we can use Lerna for:

要为我们解决很多问题,我们可以将Lerna用于:

  • Install multiple packages

    安装多个软件包
  • Publish management for multiple packages (independent or synced versioning)

    发布多个软件包的管理(独立或同步版本控制)
  • Show which package changed/diff since last release

    显示自上次发行以来哪个软件包已更改/差异
  • Run multiple packages with one command

    使用一个命令运行多个软件包
  • List all public packages in the current repository

    列出当前存储库中的所有公共软件包

Take a look at their repository to understand what it does in detail and how it can help you.

查看他们的存储库,以了解其详细功能以及如何为您提供帮助。

A note to package managers: you cannot use multiple package managers for different packages within the repository. This means that you will not be able to run npm and yarn for the entire repository.

程序包管理器的注释:不能对资源库中的不同程序包使用多个程序包管理器。 这意味着您将无法为整个存储库运行npm yarn。

快速入门:使用Lerna创建测试存储库 (Quick start: Create a test repository with Lerna)

Before we start the example project let’s experiment a little with the Lerna CLI. Execute the following commands:

在开始示例项目之前,让我们对Lerna CLI进行一些实验。 执行以下命令:

npm install --global lerna
git init test-lerna && cd test-lerna
lerna init
echo "node_modules" > .gitignore

I will explain what each line does:

我将解释每一行的作用:

  1. Install Lerna CLI globally

    全局安装Lerna CLI
  2. Create a folder for the repository and move into it

    为存储库创建一个文件夹并将其移入
  3. Initialize Lerna, it will create the required standard files for us

    初始化Lerna,它将为我们创建所需的标准文件
  4. Add a .gitignore file which ignores the node_modules folders in the repository

    添加一个.gitignore文件,该文件将忽略存储库中的node_modules文件夹

We will have a simple root folder with the following files and folders:

我们将有一个简单的根文件夹,其中包含以下文件和文件夹:

packages/
.gitignore
lerna.json
package.json

You have created your monorepo. Congratulations!

您已经创建了monorepo。 恭喜你!

初始文件 (Initial files)

Let’s now look in detail about the structure:

现在让我们详细了解一下结构:

  • packages/: Folder where we will put in all packages

    包/ :我们将放入所有包的文件夹

  • .gitignore: Ignore files and folders for Git such as node_modules folders, .env files, build files, etc. Get the example here and save it to the file.

    .gitignore :忽略Git的文件和文件夹,例如node_modules文件夹,.env文件,构建文件等。在此处获取示例并将其保存到文件中。

  • lerna.json: Lerna configuration file

    lerna.json :Lerna配置文件

  • package.json: Package file containing only tools for handling packages from the root folder

    package.json :软件包文件,仅包含用于处理根文件夹中的软件包的工具

In the next parts I will explain a bit about how Lerna works and what we will use for the example so that you understand what we do.

在接下来的部分中,我将对Lerna的工作原理以及该示例将使用的内容进行一些解释,以便您了解我们的工作。

Lerna入门 (Getting started with Lerna)

There are two ways to start with Lerna:

从Lerna开始有两种方法:

  • Initialize over Lerna CLI

    通过Lerna CLI初始化
  • Have a configuration ready to use

    准备好要使用的配置

@ lerna / init (@lerna/init)

To use the lerna init command, you must first install the CLI globally or use npx. We will use the global installed CLI.

要使用lerna init命令,必须首先全局安装CLI或使用npx。 我们将使用全局安装的CLI。

In this case, Lerna runs with its default behaviour: the same version in all packages. This is useful if you only use one project with several packages.

在这种情况下,Lerna会以其默认行为运行:所有软件包中的版本均相同。 如果您仅将一个项目与多个软件包一起使用,这将很有用。

There are two other options you can check out in the @lerna/init readme file.

您可以在@ lerna / init自述文件中签出其他两个选项。

You can see the lerna.json file with the following content:

您可以看到具有以下内容的lerna.json文件:

{
"packages": ["packages/*"],
"version": "0.0.0"
}

You can add folders other than packages/* to the configuration if you wish: src/*, resources/*, etc.

如果愿意,可以将packages/*以外的文件夹添加到配置中: src/*resources/*等。

引导程序 (Bootstrap)

The command lerna bootstrap installs all dependencies within all packages below the package folder. We will use the default behaviour and npm will be used as the package manager.

命令lerna bootstrap将所有依赖项安装在package文件夹下的所有包中。 我们将使用默认行为,而npm将用作包管理器。

Let’s add a package to our test repository:

让我们向测试存储库添加一个包:

cd packages/ && mkdir package1 && cd package1/
npm init
lerna add lodash
npx rimraf node_modules package-lock.json
cd ../..

For a clean installation we have removed the folder and files. Now run from the root folder:

为了进行全新安装,我们删除了文件夹和文件。 现在从根文件夹运行:

lerna bootstrap

Once the command has finished, you will see a package-lock.json file and a node_modules folder in our package.

命令完成后,您将在package-lock.json看到一个package-lock.json文件和一个node_modules文件夹。

吊装 (Hoisting)

There is a reason why many developers do not like the monorepo approach because of the disk space and download time. Suppose you have four packages with react as a dependency, then you will download and install it four times. Don't get fat and move smart!

由于磁盘空间和下载时间的原因,许多开发人员不喜欢monorepo方法是有原因的。 假设您有四个具有react作为依赖项的软件包,那么您将下载并安装四次。 不要发胖,不要聪明!

The --hoist option can help you with this:

--hoist选项可以帮助您:

lerna bootstrap --hoist

When you run this command, Lerna installs the dependencies from the packages into the root folder. At the same time, the package-lock.json file contains the path to the root folder for the packages, so everything is linked to a node_modules folder, as far as the .bin folder is concerned.

当您运行此命令时,Lerna将软件包中的依赖项安装到根文件夹中。 同时, package-lock.json文件包含软件包根文件夹的路径,因此就.bin文件夹而言,所有内容都链接到node_modules文件夹。

You can read more about the details in the official documentation.

您可以在官方文档中阅读有关详细信息的更多信息。

向软件包添加依赖项 (Adding dependencies to packages)

In some cases we will install additional dependencies to packages. We have the command for this:

在某些情况下,我们将对软件包安装其他依赖项。 我们有以下命令:

lerna add <package/dependecy>

For more information, please see the official GitHub page for @lerna/add.

有关更多信息,请参见@ lerna / add的官方GitHub页面。

This command installs the defined <package/dependecy> in all packages and updates the package.json and package-lock.json files.

此命令在所有软件包中安装定义的<package/dependecy>并更新package.jsonpackage-lock.json文件。

To restrict this to a number of packages, you can use the --scope option. For more information about this filter option, see @lerna/filter-options --scope

要将其限制为多个软件包,可以使用--scope选项。 有关此过滤器选项的更多信息,请参见@ lerna / filter-options --scope

You can also use lerna add inside the package folders, just like we did before and it will install everything for you.

您也可以像以前一样在软件包文件夹中使用lerna add ,它将为您安装所有内容。

运行命令 (Running commands)

Just like npm run, Lerna offers us something similar: Lerna run. This command executes a command within all packages. If the command is not available in a package, it will not run and remain silent.

就像npm run ,Lerna为我们提供了类似的东西: Lerna run 。 该命令在所有软件包中执行命令。 如果该命令在软件包中不可用,它将不会运行并且保持静默状态。

Let’s test the following command:

让我们测试以下命令:

lerna run start --stream

This will run npm run start in all packages where it is available. In some cases it will not work because there is none.

这将在所有可用的软件包中运行npm run start 。 在某些情况下,它将无法正常工作,因为没有。

The --stream option is useful because you will see the result of the process. Without it you will not see anything.

--stream选项很有用,因为您将看到该过程的结果。 没有它,您将看不到任何东西。

将软件包导入或添加到monorepo (Import or add packages to the monorepo)

To import existing repositories, you should try the Lerna import command for more details.

要导入现有存储库,应尝试使用Lerna import命令以获取更多详细信息。

To add a new package to the monorepo execute:

要将新软件包添加到monorepo,请执行:

lerna create <name>

Now a new package should be available in the packages/ folder.

现在,在packages/文件夹中应该有一个新的packages/

根文件夹上的其他依赖项 (Additional dependencies on the root folder)

There might be some cases that you want to add other useful dependencies for all packages, but then you need to check where exactly you need to install them. Using these tools might be only possible to have it in the root folder. depending on if they are running tools inside or outside the repository.

在某些情况下,您可能希望为所有软件包添加其他有用的依赖项,但随后需要检查将它们安装在什么位置。 使用这些工具可能只有将其放在根文件夹中。 取决于它们是在存储库内部还是外部运行工具。

Here are some tools that should go into the root folder:

以下是应放在根文件夹中的一些工具:

Both work at a higher level because they are executed in combination with “Git” commands. The .git folder is also located in the root directory where they should go, and should only be installed once. We will include them in our example project.

两者都可以与“ Git”命令结合执行,因此可以在更高级别上工作。 .git文件夹也位于它们应存放的根目录中,并且只能安装一次。 我们将它们包含在示例项目中。

示例项目 (The example project)

Let’s create a new Lerna repository to have a clean folder. Run these commands from a folder where you want to create the new project (settings for created packages are set to default).

让我们创建一个新的Lerna存储库以拥有一个干净的文件夹。 从要创建新项目的文件夹中运行这些命令(创建的包的设置被设置为默认值)。

Hint: If you want to use the example repository from before, skip the first two commands and start creating packages.

提示 :如果您想使用以前的示例存储库,请跳过前两个命令并开始创建包。

git init lerna-monorepo-starter && cd lerna-monorepo-starter
lerna init
lerna create package1 --yes && lerna create package2 --yes
echo "node_modules" > .gitignore
npm install

The result is:

结果是:

node_modules/
packages/
- package1/
- __tests__/
- lib/
- package.json
- README.md
- package2/
- __tests__/
- lib/
- package.json
- README.md
.gitignore
lerna.json
package.json

Time to do some preparations on the root and packages package.json files. To make things short, we want to merge these command steps into one:

是时候对根目录进行一些准备并打包package.json文件了。 为了简短起见,我们希望将这些命令步骤合并为一个:

  1. Install dependencies in root

    在根目录下安装依赖项
  2. Install dependencies in all packages with lerna bootstrap

    使用lerna bootstrap在所有软件包中安装依赖lerna bootstrap

  3. Start the packages in parallel

    并行启动软件包

Consideration on point three if parallel or not: You should check if you are depending on another package and if that needs to be build before, else you can run in parallel.

关于第三个点的考虑(如果不并行):您应该检查是否依赖于另一个软件包,并且是否需要在以前构建,否则可以并行运行。

根准备 (Root preparation)

Here is the code that we will use with the scripts in the package.json file on the root folder.

这是我们将与根文件夹的package.json文件中的脚本一起使用的代码。

{
"name": "lerna-monorepo-starter",
"private": true,
"devDependencies": {
"lerna": "^3.22.1"
},
"scripts": {
"bootstrap": "lerna bootstrap",
"lint": "lerna run lint --parallel",
"setup": "npm install && npm run bootstrap",
"start": "lerna run start --parallel"
}
}

Let’s go over the script commands step by step:

让我们逐步看一下脚本命令:

  • bootstrap: Lerna installs package dependencies from the root folder (mentioned in step 2)

    bootstrap :Lerna从根文件夹安装软件包依赖项(在步骤2中提到)

  • start: Lerna will start the packages in parallel (step 3)

    start :Lerna将并行启动软件包(步骤3)

  • lint: lint the packages in parallel

    lint :并行并行处理软件包

  • setup: Initialization to install the dependencies at the root folder and the packages (step 1 and 2)

    setup :初始化以在根文件夹和软件包中安装依赖项(步骤1和2)

将Husky并commitlint添加到根文件夹 (Add Husky and commitlint to the root folder)

We install more packages mentioned before to root and configure them:

我们将安装前面提到的更多软件包以进行root和配置:

npm install --save-dev husky @commitlint/cli @commitlint/config-conventional @commitlint/config-lerna-scopes

提交配置 (Commitlint configuration)

Next create two config files at the root folder.

接下来,在根文件夹中创建两个配置文件。

First file is commitlint.config.js:

第一个文件是commitlint.config.js

module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'scope-enum': [
2,
'always',
[
// workspace packages
'package1',
'package2',
'eslint',
'*',
],
],
},
}

Here we use some predefined configuration and add one rule to it. For more information visit the official commitlint page and rules documentation page.

在这里,我们使用一些预定义的配置,并向其中添加一条规则。 有关更多信息,请访问官方的commitlint页面规则文档页面

赫斯基配置 (Husky configuration)

As recommended on the page for commitlint you should now also configure Husky in the root folder.

按照页面上有关commitlint的建议,您现在还应该在根文件夹中配置Husky。

Create the Husky configuration file .huskyrc.json and put in the following content:

创建Husky配置文件.huskyrc.json并输入以下内容:

{
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "lerna run precommit --concurrency 2 --stream",
"pre-push": "lerna run lint"
}
}

These require some explanation now at this stage.

这些现阶段需要一些解释。

You must know that Husky is a Git hook helper. If you do anything with a Git command that matches them in the file, it can mount itself in front of them and execute commands.

您必须知道Husky是一个Git钩子助手。 如果您使用与文件中的它们匹配的Git命令执行任何操作,它可以将自身安装在它们前面并执行命令。

let’s check the commands line by line:

让我们逐行检查命令行:

  • commit-msg: the commit message will be checked. If it fails according to the rules defined in the configuration, you will receive an error and nothing will be committed.

    commit-msg :将检查提交消息。 如果根据配置中定义的规则失败,则将收到错误,并且不会提交任何内容。

  • pre-commit: Lerna will run the command precommit in each package if available (for us it will run a command to lint staged files in the package folder, later more to this). Additionally, it executes the packages parallel by order (--concurrency, max two packages in parallel) and gives us a stream output.

    预提交 :勒拿湖将运行命令precommit每包如果有(对我们来说会运行命令皮棉包中的文件夹中的文件上演,后来更到这一点)。 此外,它按顺序并行执行软件包(-并发,最多并行两个软件包),并为我们提供流输出。

  • pre-push: Lerna executes multiple commands before a push (useful to check the whole application, not only the changed files)

    pre-push :Lerna在推送之前执行多个命令(用于检查整个应用程序,不仅检查更改的文件)

.editorconfig文件 (The .editorconfig file)

Now the root folder is prepared for your packages that we can add. Before we do this let’s add some useful things for IDE and editor configuration.

现在,根文件夹已经为我们可以添加的软件包做好了准备。 在执行此操作之前,让我们为IDE和编辑器配置添加一些有用的东西。

Here is the content of the .editorconfig file:

这是.editorconfig文件的内容:

root = true

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

You can learn more about this file at editorconfig.org.

您可以在editorconfig.org上了解有关此文件的更多信息。

最终根结构 (Final root structure)

This is how your root folder should look like:

这是您的根文件夹的外观:

node_modules/
packages/
- package1/
- index.js
- package.json
- package2/
- index.js
- package.json
.editorconfig
.gitignore
.huskyrc.json
commitlint.config.js
lerna.json
package.json
package-lock.json

准备节点包 (Prepare the node packages)

For our example, we have two very simple Node.js hello-world packages. Here you see the content for the files:

对于我们的示例,我们有两个非常简单的Node.js hello-world程序包。 在这里,您可以看到文件的内容:

packages/package1/lib/package1.js:

packages/package1/lib/package1.js

const http = require('http')
const hostname = 'localhost'
const port = 3001

const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('Hello World - Package 1')
})

server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})

In this file, we will run a simple Node.js server that is showing a simple content with: Hello World. The same goes for the second package, with a slightly different content output and port to differentiate.

在此文件中,我们将运行一个简单的Node.js服务器,该服务器使用以下内容显示简单的内容: Hello World 。 第二个程序包也是如此,内容输出和端口有所不同,以加以区别。

The packages/package1/package.json file:

packages/package1/package.json文件:

{
"name": "package1",
"version": "0.0.0",
"description": "",
"keywords": [],
"author": "",
"license": "ISC",
"private": true,
"main": "lib/package1.js",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": ["lib"],
"scripts": {
"start": "node lib/package1.js",
"test": "echo \"Error: run tests from root\" && exit 1"
}
}

packages/package2/lib/package2.js

packages/package2/lib/package2.js

const http = require('http')

const hostname = 'localhost'
const port = 3002

const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('Hello World - Part 2')
})

server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})

packages/package2/package.json

packages/package2/package.json

{
"name": "package2",
"version": "0.0.0",
"description": "",
"keywords": [],
"author": "",
"license": "ISC",
"private": true,
"main": "lib/package2.js",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": ["lib"],
"scripts": {
"start": "node lib/package2.js",
"test": "echo \"Error: run tests from root\" && exit 1"
}
}

Important: The name key includes the unique name of the package, that means no other package can have the same name, even if it is included in another folder name. By that Lerna can check on the name and filter the scope on its commands.

重要说明: name键包含程序包的唯一名称,这意味着即使其他程序包名称中包含其他程序包,也不能使用相同的名称。 这样,Lerna可以检查名称并在其命令上过滤作用域。

Here is a small simulation to show you the error with hints:

这是一个小模拟,向您显示错误提示:

lerna ERR! ENAME Package name "package1" used in multiple packages:
lerna ERR! ENAME C:\dev\lerna-monorepo-starter\packages\package1
lerna ERR! ENAME C:\dev\lerna-monorepo-starter\packages\package2

Let’s wrap it up a little:

让我们包装一下:

  • We have a monorepo created with two simple Node.js packages

    我们有一个用两个简单的Node.js包创建的monorepo
  • Packages can install dependencies and run separately without other packages to be involved

    程序包可以安装依赖项并单独运行,而无需涉及其他程序包
  • The root folder scripts are managing the Lerna commands to run our package commands

    根文件夹脚本正在管理Lerna命令以运行我们的软件包命令
  • Configuration for the connection to the root folder, and in this case to Husky and commitlint.

    与根文件夹(在本例中为Husky和commitlint)的连接配置。

皮棉文件更改 (Lint file changes)

What we need now are two packages to get the linting to go in two ways:

现在我们需要两个包装,以两种方式使棉绒行进:

  1. Lint over the files inside each package folder

    整理每个软件包文件夹中的文件
  2. Lint over Git staged files to have a check on files before they are committed.

    Lint over Git暂存文件可以在提交文件之前对其进行检查。

Add ESLint and lint-staged to all packages. In this case, we will go to the root folder in the terminal and run the following command:

将ESLint和lint暂存到所有软件包中。 在这种情况下,我们将转到终端中的根文件夹并运行以下命令:

lerna add eslint --scope=package* --dev && lerna add lint-staged --scope=package* --dev

Before you get going you might question why to install these dependecies in the package folder and not at the root folder. The explanation is quite simple: You want to make the packages independent from the outside of its folder as much as possible. In that way just put all the packages into the package folder as long as it is not depending on tools outside of the monorepo (like Git).

在开始之前,您可能会问为什么将这些依赖项安装在package文件夹中而不是在根文件夹中。 解释很简单:您想使软件包尽可能独立于其文件夹的外部。 这样,只要不依赖于monorepo之外的工具(如Git),就将所有软件包放入package文件夹。

Let’s check the package.json files for the first and second package. You will find the updates now included and installed:

让我们检查第一个和第二个软件包的package.json文件。 您会发现现在包含并安装了更新:

{
...
"devDependencies": {
"eslint": "^7.5.0",
"lint-staged": "^10.2.11"
}
...
}

Preparations are done. Don’t forget to save your game.

准备工作已经完成。 不要忘记保存您的游戏。

配置ESLint (Configure ESLint)

Now we configure ESLint and add one npm script to the package package.json files. Here we start with packages/package1/.eslintrc.js:

现在,我们配置ESLint并将一个npm脚本添加到package.json文件包中。 在这里,我们从packages/package1/.eslintrc.js

module.exports = {
env: {
browser: true,
es6: true,
node: true,
},
parserOptions: {
ecmaVersion: 2019,
sourceType: 'module',
},
rules: {
indent: ['error', 2],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
},
}

Nothing dramatic for a starter. Check out the rules from ESLint in their documentation.

对于初学者来说没什么大不了的。 从ESLint的文档中检查规则。

Copy this file to packages/package2/ folder and add the lint script to both package.json files. Your scripts block looks like this now:

将此文件复制到packages/package2/文件夹,然后将lint脚本添加到两个package.json文件中。 您的scripts块现在看起来像这样:

packages/package1/package.json:

packages/package1/package.json

{
...
"scripts": {
"lint": "eslint -f codeframe \"**/*.js\"",
"start": "node lib/package1.js",
"test": "echo \"Error: run tests from root\" && exit 1"
}
...
}

packages/package2/package.json:

packages/package2/package.json

{
...
"scripts": {
"lint": "eslint -f codeframe \"**/*.js\"",
"start": "node lib/package2.js",
"test": "echo \"Error: run tests from root\" && exit 1"
}
...
}

Run the lint command from the root folder now:

现在从根文件夹运行lint命令:

npm run lint

You get errors because of the indent rule. Fix the files and try it again until there is no error. If you don't get errors, then most likely your IDE/editor has fixed it for you automatically.

由于indent规则,您会收到错误。 修复文件,然后重试,直到没有错误。 如果没有错误,则很可能是您的IDE /编辑器已自动为您修复了它。

配置lint-staged (Configure lint-staged)

ESLint can lint all files if you run npm run lint from within the package folder at this moment, so why do we use lint-staged?

如果您此时从package文件夹内运行npm run lint ,则ESLint可以对所有文件进行处理,那么为什么要使用lint-staged呢?

It is nice to check changed files before committing them rather than linting everything that can become time-consuming. That is why we want to hook into git commit and check the files with error before we include them into our branch. The positive effect is: the linter run is shorter, as it does not check every file.

最好在提交更改前检查已更改的文件,而不是放弃所有可能很费时的文件。 这就是为什么我们要挂接到git commit并检查错误的文件,然后再将它们包含到分支中。 积极的效果是:lint的运行时间较短,因为它不会检查每个文件。

This way we will add this functionality with a little go around for the monorepo. Let’s look at what we prepared in .huskyrc.json:

这样,我们将为monorepo添加一些功能。 让我们看看我们在.huskyrc.json准备的.huskyrc.json

"pre-commit": "lerna run precommit --concurrency 2 --stream",

This hook command is executed before a git commit. Do you see that precommit command? We need to add this npm script to the packages now:

这个钩子命令在git commit之前执行。 您看到该precommit命令了吗? 我们现在需要将此npm脚本添加到软件包中:

packages/package1/package.json

packages/package1/package.json

{
...
"scripts": {
"precommit": "lint-staged",
"lint": "eslint -f codeframe \"**/*.js\"",
"start": "node lib/package1.js",
"test": "echo \"Error: run tests from root\" && exit 1"
}
...
}

packages/package2/package.json

packages/package2/package.json

{
...
"scripts": {
"precommit": "lint-staged",
"lint": "eslint -f codeframe \"**/*.js\"",
"start": "node lib/package2.js",
"test": "echo \"Error: run tests from root\" && exit 1"
}
...
}

The commands are set, but the configuration for lint-staged is missing, so we add a .lintstagedrc.js file in all the packages with the following content:

设置了命令,但是缺少lint-staged的配置,因此我们在所有包含以下内容的软件包中添加了.lintstagedrc.js文件:

module.exports = {
'*.js': 'eslint',
}

This tells lint-staged to run on following glob rule of files: all files with extension .js with the eslint command. You can play with it later to modify it for your use case.

这告诉lint-staged在以下文件的全局规则上运行:所有带有eslint命令的扩展名为.js文件。 您可以稍后使用它来针对您的用例进行修改。

Done. Everything is prepared.

做完了 一切准备就绪。

检查一切运行情况 (Check how everything is running)

To see if everything is working you can try the following steps:

要查看一切是否正常,可以尝试以下步骤:

  • Start the application with npm run setup && npm run start (installation and start all packages)

    使用npm run setup && npm run start启动应用程序(安装并启动所有软件包)

  • Make some changes to packages/package1/lib/package1.js and commit with an invalid message (example: ‘test some changes'), then retry with an updated message (example: ‘test: some changes')

    packages/package1/lib/package1.js进行一些更改,并提交一条无效消息(例如:“测试某些更改”),然后使用更新后的消息重试(例如:“ test:一些更改”)

  • Add an error to packages/package2/lib/package2.js to trigger linting behaviour with lint-staged (check if your editor is fixing it automatically before testing, fix file and re-try)

    packages/package2/lib/package2.js添加错误,以使用lint暂存来触发packages/package2/lib/package2.js行为(在测试,检查文件packages/package2/lib/package2.js试之前,请检查编辑器是否已自动对其进行修复)

  • Push changes to trigger the linting for all files, not only staged before uploading to ‘origin’.

    推送更改以触发所有文件的棉绒,不仅在上传到“原始文件”之前进行。

There is a reason why there is a check on the pre-push on all the files: In a complex codebase the linting for single files might be fine, but that does not mean the changes are fine for other files. Before that, we need to check everything automatically and in case fix the issue before we push the changes.

有一个原因需要检查所有文件的预推:在复杂的代码库中,单个文件的皮棉可能很好,但这并不意味着其他文件的更改也可以。 在此之前,我们需要自动检查所有内容,以防在推送更改之前解决问题。

ESLint规则独立软件包 (ESLint rules stand-alone package)

Now let’s say you want to share the same configuration for ESLint in all the packages. Right now, we have the same configuration twice, but we can do better than that. Let’s extend the rules by another ruleset package from within our monorepo.

现在,假设您要在所有软件包中共享相同的ESLint配置。 现在,我们有两次相同的配置,但是我们可以做得更好。 让我们通过monorepo中的另一个规则集来扩展规则。

To create a new package in the monorepo, run this from the root folder:

要在monorepo中创建一个新软件包,请从根文件夹运行该软件包:

lerna create eslint-config --yes
cd packages/eslint-config
npx rimraf __tests__ lib README.md
touch index.js

Your new package should look like this now:

您的新软件包现在应如下所示:

packages/
- eslint-config/
- index.js
- package.json

Copy the content from packages/package1/.eslintrc.js and paste it into the new packages/eslint-config/index.js file.

复制packages/package1/.eslintrc.js的内容,并将其粘贴到新的packages/eslint-config/index.js文件中。

Update the packages/eslint-config/package.json file:

更新packages/eslint-config/package.json文件:

{
"name": "@shared/eslint-config",
"version": "0.0.0",
"description": "Shareable ESLint configuration",
"keywords": [],
"author": "",
"license": "ISC",
"private": true,
"main": "index.js",
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
}
}

Add the package to the other packages by running lerna add from the root folder:

通过从根文件夹运行lerna add软件包添加到其他软件包:

lerna add @shared/eslint-config --scope=package* --dev

You can see, that we are using the package name @shared/eslint-config and also scope with package*. Here we are using one package from inside the monorepo and install it under devDependecies of the packages in the scope, in our case in package1 and package2.

可以看到,我们使用的是包名称@shared/eslint-config ,还使用了package* 。 在这里,我们从monorepo内部使用一个软件包,并将其安装在范围内的软件包的devDependecies下,在我们的示例中为package1package2

To test this out you need to replace now the ESLint configuration files in the other packages with following content:

要对此进行测试,您现在需要用以下内容替换其他软件包中的ESLint配置文件:

packages/package1/.eslintrc.js

packages/package1/.eslintrc.js

module.exports = {
extends: ['@shared/eslint-config'],
}

packages/package2/.eslintrc.js

packages/package2/.eslintrc.js

module.exports = {
extends: ['@shared/eslint-config'],
}

You are extending your configuration with the content from the shared configuration package named by its package name.

您正在使用共享配置包中以其包名称命名的内容扩展配置。

Here you see the final structure with all files and folders from the example:

在这里,您可以看到示例中所有文件和文件夹的最终结构:

node_modules/
packages/
- eslint-config/
- index.js
- package.json
- packages1/
- __tests__
- lib
- node_modules/
- .eslintrc.js
- .lintstagedrc.js
- package.json
- package-lock.json
- README.md
- packages2/
- __tests__
- lib
- node_modules/
- .eslintrc.js
- .lintstagedrc.js
- package.json
- package-lock.json
- README.md
.editorconfig
.gitignore
.huskyrc.json
commitlint.config.js
lerna.json
package.json
package-lock.json

奖励:具有多根工作区的monorepo的Visual Studio Code配置 (Bonus: Visual Studio Code configuration for monorepo with Multi-root Workspaces)

Visual Studio Code has sometimes issues with monorepos in a way that it gets tools/extensions misconfigured. In some cases, the linting or testing will fail internally in your code editor because of the monorepo structure and that becomes a problem. This is why Multi-root Workspaces are the perfect way to handle monorepos. Here I will show you the example file for the repository.

Visual Studio Code有时由于配置错误的工具/扩展而导致monorepos出现问题。 在某些情况下,由于monorepo结构的原因,插入或测试将在代码编辑器内部内部失败,这成为一个问题。 这就是为什么多根工作区是处理monorepos的理想方法的原因。 在这里,我将向您展示存储库的示例文件。

Let’s create the workspaces: We need a .code-workspace file in the root folder:

让我们创建工作区:在根文件夹中需要一个.code-workspace文件:

touch lerna-monorepo-starter.code-workspace

.lerna-monorepo-starter.code-workspace:

.lerna-monorepo-starter.code-workspace

{
"folders": [
{
"name": "ROOT",
"path": "."
},
{
"path": "packages/package1"
},
{
"path": "packages/package2"
},
{
"path": "packages/eslint-config"
}
],
"settings": {
"files.exclude": {
"**/node_modules/**": true,
"packages/*/*": true
}
}
}

Some explanation:

一些解释:

  • filename (lerna-monorepo-starter): Is used in the title of Visual Studio Code, so you will know where exactly you are:

    文件名 (lerna-monorepo-starter):在Visual Studio代码的标题中使用,因此您将确切知道您的位置:

Image for post
Visual Studio Code workspace title
Visual Studio Code工作区标题
  • folders: Folders that you will see in the sidebar explorer:

    文件夹 :您将在边栏资源管理器中看到的文件夹:

Image for post
Visual Studio Code explorer view
Visual Studio Code资源管理器视图
  • Optionally: I set the name for the root folder to ROOT. You don't have to include all the packages in your workspace view, so you could also omit the unnecessary packages you will not work with.

    可选 :我将根文件夹的名称设置为ROOT 。 您不必在工作空间视图中包括所有软件包,因此您也可以省略不需要的软件包。

  • files.exclude: Set the settings to ignore some folders from our view and also search results. This way the folder view is not covered by unnecessary files. In the settings it will remove node_modules folders everywhere and also remove all packages content below one level under the package folder, but only in the ROOT workspace:

    files.exclude :设置设置以忽略我们视图中的某些文件夹以及搜索结果。 这样,文件夹视图就不会被不必要的文件覆盖。 在设置中,它将删除所有位置的node_modules文件夹,也将删除node_modules文件夹下一层以下的所有包内容,但仅在ROOT工作区中:

Image for post
Visual Studio Code folder view
Visual Studio Code文件夹视图

总结一下 (Sum it up)

This example shows you how to create a monorepo mindfully. There are more possibilities that you can use Lerna with like publishing, versioning, cleaning, etc., that we did not cover. You can put this as a challenge for the next steps.

这个例子向您展示了如何仔细地创建一个monorepo。 您可能会使用Lerna进行发布,版本控制,清理等更多工作,而这是我们未涵盖的。 您可以将其作为下一步的挑战。

In the example repository, we split up ESLint configuration from all packages and made it reusable. This will also make changes easier in the future, and you are still flexible peer package to modify the rules.

在示例存储库中,我们从所有软件包中拆分了ESLint配置,并使其可重用。 这也将使将来的更改变得更容易,并且您仍然可以灵活地对等软件包来修改规则。

From here you can start trying out more things like adding Angular, React, Svelte or Vue packages and another package that combines all of them in one together. Don’t forget to use Storybook on UI component visualization. For that, you could also create another package and use components from the other packages. The possibilities are endless.

从这里开始,您可以尝试更多的事情,例如添加Angular,React,Svelte或Vue软件包,以及将所有软件包组合在一起的另一个软件包。 不要忘记在UI组件可视化上使用Storybook 。 为此,您还可以创建另一个程序包并使用其他程序包中的组件。 可能性是无止境。

Have a nice time coding.

编码愉快。

翻译自: https://medium.com/rewrite-tech/how-to-create-a-monorepo-with-lerna-3ed6dfec5021

tl dr

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值