完整的 monorepo 设置演练
那么,您说的是 monorepo?
那么,这值得吗?
如何做 monorepo?
勒纳 (Lerna) 太棒了!
Monorepos...😵
这篇文章取自我的博客,所以一定要查看它以获取更多最新内容😉
我就是那种总是喜欢用最新最好东西的人。🌟 至于好坏,那就另当别论了。但正是这种内心的渴望让我拓展了知识和经验。而这一次,它让我开始使用Monorepos ……
Monorepo本身的概念其实并不新鲜。事实上,它相当古老。但是,随着我们的代码量越来越大,以及我们对越来越完善的架构的渴望📈,它再次开始获得显著的关注。因此,在这篇文章中,我们将探讨什么是 Monorepo,它的主要优势以及其他细节。然后,我们将凭借丰富的 Web 开发经验,基于Lerna、 TypeScript ** 和Rollup等强大的工具,配置我们自己的 Monorepo 设置!让我们开始吧!
那么,您说的是 monorepo?
我想你已经知道,或者至少猜到 monorepo 的含义和含义了。Monorepo(顺便说一句,这词儿玩得真好)指的是在单个仓库中组织代码库的方式(并非任何技术定义😅)。如果你还没读过几十篇关于monorepo 优点的文章,你可能会认为,这样一来,你不断增长的代码库很快就会变得一团糟。而且,你猜对了!——你完全错了。
为了更好地理解这一点,让我们将兴趣范围降低到更具体的领域 - JS 开发。目前,JavaScript 工作流程一直由NPM 包主导。这种形式使我们能够轻松地创建、共享和重用代码。不计算恶意代码和可能占用数 GB 空间的大型依赖树的可能性,它们很棒!🙃 从开发角度来看,通常单个包 = 单个代码存储库 - 合乎逻辑。但是,如果您开发一个很可能相互依赖的包生态系统呢?您甚至可以使用NPM 范围使您的包类似于那样。您会将您的代码放在单独的存储库中吗?您知道生态系统解耦并不好,不是吗?此外,随着包数量的不断增长,单独的问题、拉取请求和整个管理流程将是一场噩梦。如您所料,这个问题的解决方案是以 monorepo 的形式出现的。
Monorepos 结合了两个领域的优点 - 单一、小型且易于管理的存储库,具有多功能性和多种容量。👌 它只不过是一个具有良好结构的存储库 - 每个单独的包都有自己的位置,代码的组织方式与平常一样。
那么,这值得吗?
在某些情况下绝对是这样的。当然,你不应该用用途完全不同的包来创建 monorepo。但是,正如前面提到的,它非常适合创建可以协同工作或具有相同目标的包生态系统。这只是一个经验法则 -只将应该分组的东西分组。所以,下次你想要同时创建多个具有独立代码库的独立包时,考虑一下使用 monorepo 是否更好。为了不让你陷入完全的黑暗,作为一个很好的案例研究,你可以查看流行 JS 库和工具的源代码及其结构,例如Babel、Jest **、粉丝最喜欢的React、Vue和Angular等等。
因此,总结一下所有信息…… Monorepo 将相似的包分组,并具有坚实的结构和包之间的依赖关系。现在,诸如单一问题板、更容易的跨包更改以及配置、测试和示例的单一位置之类的事情显而易见。但是,如果没有适当的工具,管理具有自身依赖关系且位于不同路径的多个包并不是一件容易的事。在 Web 开发领域,Lerna提供了这样的功能。此工具作为标准包管理器(如NPM或Yarn )的高级包装器,在设计时特别考虑了 monorepo。它使您可以访问大量不同的配置选项和专用命令- 例如,在每个包中执行给定的脚本、在单独的包中安装依赖项、管理版本控制以及发布到 NPM。从字面上看,所有您需要轻松管理 monorepo 的东西。✨
但是,这么多不同的选项和命令,很容易让人迷失在这个单一仓库的丛林里。所以我觉得是时候自己创建一个好的单一仓库结构了……
如何做 monorepo?
在这里,我将指导您完成我个人的 monorepo 设置。正如之前所说,我们将使用Yarn、Lerna、TypeScript和Rollup。但是,由于我们主要想关注 monorepo 设置本身,因此我们不会从头开始配置 Rollup 之类的工具。相反,我将使用我在项目中使用的最喜欢的、基于 Rollup 的打包工具- Bili,它需要的配置要少得多。当然,它与 Rollup 本身一样,可以用于生产环境。当然,如果您以前使用过 Rollup ,您很可能可以轻松地用它来替换 Bili 。
基础知识
让我们首先创建核心包并安装必要的依赖项。目前,我希望你已经安装了 Node.js 和 Yarn(或者 NPM)。
yarn init
使用上面的命令,您将完成package.json文件的基本设置。您可以提供所需的数据,但一个重要的要求是将选项设置private
为true
。这将确保给定的包永远不会被发布,在我们的例子中,这意味着它只是我们 monorepo 树的主干。最终,您的 package.json 应该类似于以下内容:
{
"name": "package",
"version": "0.0.0",
"description": "Simple package",
"main": "src/index.ts",
"license": "MIT",
"private": true
}
接下来,我们将安装所有需要的依赖项,以便以后不必再为它们烦恼。
yarn add lerna typescript bili rollup-plugin-typescript2 --dev
现在,让我再谈谈 Lerna。本质上,Lerna 是一个强大的 Monorepo 引擎。对于 Monorepo 中你想用到的大多数功能,它都应有尽有。所有这些功能都有完善且编写良好的文档。因此,在本教程中涵盖所有这些功能毫无意义。相反,我们将只关注那些帮助我们设置和使用 Monorepo 的命令。只是一点小提示。😉
我们首先在根目录中创建lerna.json文件。这只是一个供 Lerna 读取的配置文件。最佳且最简单的方法是使用命令。lerna init
yarn run lerna init
此命令将执行 3 项操作:创建配置文件、创建packages 文件夹以及将 Lerna 添加到您的配置文件中devDependencies
(如果尚未添加,例如使用 Lerna 全局安装时)。查看默认配置文件:
{
"packages": [
"packages/*"
],
"version": "0.0.0"
}
packages
字段是一个包含目录或通配符的数组,用于指定我们的包所在的位置。我个人认为packages/中的默认位置最好,因为它一目了然,而且不需要我们在根目录中填充单独的包文件夹。表示 monorepo 的当前版本 - 不一定与 package.json 中的版本同步,但最好这样做。还有一些其他可用的属性,您可以在此处查看完整列表,但我只想重点介绍一个 - 。在我们的例子中,我们需要将其设置为。🧶version
npmClient
"yarn"
{
"npmClient": "yarn",
...
}
这自然会表明使用 Yarn 而不是默认的 NPM 来管理我们的包。现在,当使用 Yarn 时,您还有一个重要的选项 - useWorkspaces
。这个布尔值会让 Lerna 知道您希望它使用Yarn 工作区功能来管理包。Yarn 工作区基本上是管理 monorepos 的一个稍低级别的解决方案。它们的工作方式与 Lerna 略有不同,并且不提供相同的功能集。但是,当与 Lerna 一起使用时,它们可以在链接依赖项等时提供更好的性能。那么,为什么我们不使用它们呢?简单的答案是 -它们对范围包没有很好的支持,对我个人而言,这是一个交易破坏者。Monorepos 和范围包一起工作得很好,所以我认为它们的支持是强制性的。
套餐
完成以上所有步骤后,Lerna 基本就可以使用了。很简单,不是吗?接下来该设置一些软件包了!这里,你有两个选择——使用lerna create
Lerna 引导你完成创建新软件包所需的步骤(就像 一样yarn init
),或者进入软件包文件夹,创建子文件夹并单独设置每个软件包。
lerna create <name>
当然,使用 Lerna 命令,你根本不需要创建目录或进入 packages 文件夹。但我还是更喜欢使用标准方法,因为它lerna create
会额外为你设置一些样板文件,而这对我来说并不符合我的期望。😕
现在,您的软件包已经准备就绪。在每个软件包中,您只需像通常在单个软件包中一样创建结构即可。但是,如果这些软件包需要共享某些内容怎么办?例如,您希望每个软件包都以相同的方式与相同的工具捆绑在一起。为此,我们将在 monorepo 的根目录下的bili.config.js文件中设置 Bili 配置。
但在此之前,先简单介绍一下 Bili。Bili 是一款优秀的、基于 Rollup 的、默认零配置的打包器,内置了对ES-Next和CSS的支持。我发现,当不想从头开始配置 Rollup 时,它是一个非常不错的选择。即便如此,Bili 在配置自身和底层 Rollup(例如添加插件)时仍然提供了相当多的选项。话虽如此,在我们的例子中,所有适用于 Bili 的配置都可以应用于 Rollup 专用配置。
现在,我们应该更深入地了解配置文件中的路径。请考虑下面相当完整的设置:
// bili.config.js
// ...
module.exports = {
input: "./src/index.ts",
output: {
moduleName: "Package",
minify: true,
format: ["umd", "esm"],
dir: "./build"
},
// ...
};
从以前的一些配置文件中,您可能知道使用了 Node.js 内置path
模块和提供的__dirname
变量。在这里,区分__dirname
和相对路径(始终以 开头./
)非常重要。我们的配置文件位于 monorepo 的根目录,而 Bili 将分别在不同的子文件夹中运行。这是 Lerna 为我们提供的一个很好的功能,我们稍后会使用它。但是现在,重要的是让我们的配置工作。因此,变量引用给定文件所在的__dirname
目录,而以 开头的路径引用相对于当前工作的路径的目录。这是需要注意、记住和在我们的配置中使用它的地方,稍后将被不同目录中的多个包使用。./
TypeScript
// bili.config.js
const path = require("path");
module.exports = {
// ...
plugins: {
typescript2: {
cacheRoot: path.join(__dirname, ".rpt2_cache"),
useTsconfigDeclarationDir: true
}
}
};
您可以在其官方文档中找到所有其他 Bili 选项的文档。在这里,我只想讨论 plugins 属性,我们将使用它来支持TypeScript 编译(正如承诺的那样)。您可能还记得,我们之前安装了带有 typescript2 后缀的 Rollup 插件以及所有其他开发依赖项。并且,使用这个后缀,您可以让 Bili 使用我们选择的插件并自由配置它。请注意,安装后的 typescript2 插件默认受支持,无需进一步配置。但在这里,我想更改 2 个选项- cacheRoot
- 只是为了让我们的缓存不单独位于每个包内,而是位于根目录中(美观原因💅) - 另一个长选项用于将我们的 TS 声明文件输出到 tsconfig.json 中指定的目录中。
说到 tsconfig.json,我们也应该为它做一个特殊的设置!不过这次会稍微复杂一些。在根目录中,我们将设置基础配置,以便其他包范围内的配置可以继承。
{
"compilerOptions": {
"module": "esnext",
"lib": ["esnext", "dom"],
"strict": true,
"declaration": true,
"esModuleInterop": true,
"moduleResolution": "node"
}
}
接下来,在每个包的目录中,我们需要创建一个单独的 tsconfig.json 文件,将所有与路径相关的选项放入其中。例如:
{
"extends": "../../tsconfig.json",
"exclude": ["node_modules", "tests"],
"include": ["src/**/*"],
"compilerOptions": {
"declarationDir": "./typings"
}
}
有了这些,我们应该有一个完美的Bili + TypeScript设置,bundles 会输出到每个包的build目录,而 Typing 文件则会输出到Typings目录。太棒了!😎
用法
现在,我们的 monorepo 设置已经完成,是时候测试它了!要将代码捆绑到每个包中,我们可以使用lerna exec
:
lerna exec -- bili --config ../../.bilirc.js
主命令后的两个短划线 ( --
) 允许将传入的参数传递给正在执行的命令,而不是传递给 Lerna。现在,我们所有的软件包都应该正确打包了。
但是,没必要一遍又一遍地输入相同的方法。当然,你可以直接将lerna exec
上面的命令添加到scripts
根 package.json 的属性中,但我有一个更好的解决方案。假设你为每个包设置了不同的构建脚本(虽然我们的配置中没有这种情况,但无论如何),并且你仍然希望能够使用单个命令运行所有脚本。为此,你可以build
在每个包的 package.json 中提供单独的脚本,如下所示(watch
这只是一个不错的补充👍):
{
...
"scripts": {
"build": "bili --config ../../.bilirc.js",
"watch": "bili --watch --config ../../.bilirc.js"
}
}
脚本设置完成后,您可以使用lerna run
命令在所有包中运行它们:
lerna run build
如果您希望lerna run
或lerna exec
其他 Lerna 命令(例如lerna add
)仅适用于某些包,则应使用过滤标志 ia--scope
或--ignore
。这些标志在传递包名称(包名称在相应的 package.json 文件中,而不是目录名称)时,将正确选择要应用给定操作的包。
使用 Lerna 时,你必须知道的最后一件事是如何让你的 monorepo 包相互依赖。这其实也很简单!只需将你的包名称添加到给定的 package.jsondependencies
列表中,然后运行lerna bootstrap
即可正确完成所有包的符号链接和设置。
lerna bootstrap
勒纳 (Lerna) 太棒了!
可以说,我们今天只是触及了皮毛,但也学到了很多东西。当然,Lerna 还有一些我们没有谈到的命令,主要与管理NPM 发布和版本有关。不过,目前为止,我们之前讨论的 monorepo 设置已经完成了。现在您可以自由地开发您的 monorepo,并在之后考虑版本管理。有了如此强大的工具,您应该很快就能掌握。😉 最后,记得查看官方(实际上非常优秀)的Lerna 文档了解更多信息。
Monorepos...😵
那么,你对这篇文章和 Monorepos 本身有什么看法?你喜欢这个想法吗?更重要的是,你喜欢这篇文章吗?😅 在下面的评论区写下你的想法吧!哦,如果你愿意的话,也欢迎留言!
一如既往,请关注我的 Twitter和Facebook 页面,及时了解最新内容。此外,如果您感兴趣,也可以访问我的个人博客。再次感谢您阅读这篇文章,祝您拥有美好的一天!🖐
鏂囩珷鏉ユ簮锛�https://dev.to/areknawo/full-blown-monorepo-setup-walkthrough-15jf