手摸手教大家用lerna搭建自己的monorepo并开发npm包

2,076 阅读25分钟

这篇文章的目标不仅仅是想让各位收获一个可用的开发框架,还会帮助各位读者解读复杂配置项的含义,讲明各项配置究竟是为了解决什么问题,以及指明遇到问题该去翻阅哪篇文档,最终让大家达到知其所以然的程度。

大家可以clone下项目结合文章一起看,项目clone下来后如果不会使用请参考README.md文件,或是参考这一节的内容。

总体思路

首先我选择的代码管理模式是单体仓库(monorepo)。使用这种模式的目的在于,它能够减轻我们在多包开发时的联调工作。 要注意,monorepo只是个概念,我们要想从0实现它是非常非常困难的,因此就需要借助一些优秀的工具。这些工具已经实现了一些基础功能,我们在其上继续做拓展就比较轻松了。本文选择的是lerna这个工具。

下面我会一步步仔仔细细的描述如何把架子搭起来,如果各位能跟着我一起做一遍一定会有所收获。

初始化npm目录

首先要创建我们的项目目录。只需要选择合适的位置创建好空文件夹,然后执行npm init指令即可。这或许是我们已经做过无数次的事情了。

提示一下,你的package-name和version等等一系列信息都可以随意填写,因为我们现在创建只是monorepo的壳子,壳子本身是不需要发布到npm上去的。 这一步操作完成之后的项目目录如下图。可以看到有个package.json文件,仅此而已。 image.png

初始化存储库

甭管是monorepo还是multirepo,那总得有个repo对不对。为了方便多人合作,我们得将这个项目发布到远程仓库去。

1. 在github上创建一个新的仓库。

image.png

2. 初始化git目录

然后回到我们的项目,在控制台依次执行图中的两个指令即可。 image.png git init执行成功之后控制台会告诉你在当前目录下创建了一个名为.git的目录。有可能你看不到这个目录,但没关系,它不需要我们操作。

只是git remote add origin这个指令执行完之后没有任何输出,怎么确定是否执行成功了呢?你可以输入git remote -v。如果出现了下图的内容就说明远程仓库地址设置成功了。

image.png

安装lerna

控制台输入yarn add lerna -D即可。我这里选择的包管理器是yarn,你可以选择自己喜欢的。

指令执行结束后,项目根目录下多了个yarn.lock文件和node_modules目录。并且大家会发现git提示工作区有成千上万个改动,此时不用着急写.gitignore。我们继续。

用lerna初始化项目

这一步很简单,只需在项目根目录下执行npx lerna init --independent即可初始化lerna项目。 但我明白各位可能会对这个命令产生一些疑惑,下面我将对个命令作出一些解释。

lerna init这个命令究竟做了什么事?

一共做了四件事

  1. 如果我们项目中还没有安装过lerna,就将lerna添加到项目根目录的package.json的devDependency中。不过,我们之前已经使用yarn add lerna -D指令安装过lerna了。
  2. 在项目根目录下创建一个名为lerna.json的配置文件,用来存储版本号等信息。
  3. 如果项目根目录下不存在.gitignore的话,就创建它。
  4. 在根目录下创建名为packages的目录。将来我们全部的package都会放到这个目录下做管理。

为什么要加--independent选项?

lerna有两种版本控制的模式,一种叫Fixed/Locked mode另一种叫Independent mode。添加--independent选项就是在告诉lerna要使用Independent mode来管理package。如果不加这个选项则表示使用默认的Fixed/Locked mode。

简要说一下两种模式的区别。 如果你使用Fixed/Locked mode,则你所有package的版本号都是相同的。举个例子,在默认情况下你所有package都是从0.0.0版本开始的,假设你修改了某个package中的一行代码,那么下一次发布新版本的时候,lerna会将你每一个包的packge.json中的版本号都同步变为0.0.1(只是假设)。但请注意,你自始至终只修改了一个包的内容,可结果是所有包都发布了新的版本。这个统一的版本号会被记录在根目录的lerna.json文件中的version字段,且默认的初始版本号是0.0.0,如下图。 image.png

而Independent mode则与之不同。 如果你修改了某个package的内容,那么下次发布新版的时候,lerna会先检测你到底修改了哪几个package的代码,最终只有被修改过的package会被发布到npm上并累增版本号,而其他包是不会受到任何影响的。另外,由于这个模式下的版本号不需要设置成相同值,而是交由各package自己负责记录,因此根目录的lerna.json文件中的version字段值会被设置成了independent,如下图。

image.png

为什么要加npx

由于我们的lerna是安装在项目根目录下的,因此所有指令必须使用npx来运行。但如果你的lerna是提前安装到全局的,则无需添加npx。

设置默认的包管理器

虽然我们是可以使用lerna add来安装依赖的,但是要注意,lerna并不是包管理器,之所以能下载和安装依赖是因为在默认情况下它会利用npm install来执行我们的安装命令。可如果我更喜欢用yarn来管理依赖怎么办呢?官方是允许我们将管理工具切换为yarn的,因此有相同习惯的读者可以像我一样将包管理器更换为yarn。

切换方法很简单,只需打开根目录下的lerna.json文件,增加一项配置即可。如下图。 image.png

有读者可能会发出疑问,明明可以直接使用yarn add来安装依赖,为啥要用lerna add呢? 这个原因我会在下一段说明。

Lerna Hoisting

Lerna Hoisting是monorepo的核心功能,为了说清什么是Lerna Hoisting我们不妨一起设想这样一个场景。假设你的项目内有三个package,这三个package都需要安装lodash这个依赖,那么你现在会怎么做?假设你的目录如下:

├─lerna.json
├─package.json
├─yarn.lock
└─packages
    ├─package3
    |    ├─index.js
    |    └─package.json
    ├─package2
    |    ├─index.js
    |    └─package.json
    └─package1
        ├─index.js
        └─package.json

有可能你会在每个package目录下执行yarn add lodash, 我承认这样做确实可行,但它有个问题——明明是同样的依赖文件,却被安装了3次。这不仅意味着你会因为重复打了多条指令而感到心烦,也意味着三个package/node_modules目录下存在大量相互重复的文件。那么有没有更好的做法呢?

当然是有的。有一个方案是,我们可以选择将所有依赖都安装到项目根目录的node_modules中,这样每个package引用的lodash都来自于项目根目录的node_modules,而非package/node_modules。这样我们既可以少输入几次安装指令,依赖也可以只保存一份了——这就是Lerna Hoisting。这种方法之所以能行得通,是因为node查找依赖的顺序是先从当前目录的node_modules开始找起,如果找不到的话会逐级向上检测每一级目录下的node_moduels目录。这个方案虽然可行,但手动做起来还是太累了,因为每次安装依赖你都需要做下面两步:

  1. 在根目录下安装此依赖
  2. 在需要用到此依赖的pakage的package.json中手动添加这个依赖的名称和版本号。

那有没有办法可以一个指令完成所有事情呢?当然是有的,我们只需要使用lerna add来安装依赖即可,因为lerna会自动帮我们完成上面两个步骤。

顺便一提,如果我们安装了某个依赖的多个版本的话,那么只有引用次数最多的版本会被提升到根目录的node_modules下。其余版本则仍然安装在package/node_modules中。

如何开启Lerna Hoisting?

1. 修改package.json

在根目录的package.json内添加workspaces与private配置项,如下图。 image.png

2. 修改lerna.json

image.png

创建第一个package

至此,一个最最简单的monorepo架子就搭好了,接下来的问题就转变为“这个架子该如何使用?”。我打算以一个智能机器人作为例子,讲解如何进行package开发。

首先,我们将来要开发的所有package都是要放在packages目录下的,每一个package目录都拥有一份完整的项目结构。因此,我们第一步先在packages目录下创建一个名为my-chatter-bot的目录。并继续在此目录下执行npm init指令。注意,这个包是准备发布到npm上面去的,所以package.json中的各项配置要认真写。我的package.json内容如下图

image.png 紧接着,我们在packages/my-chatter-bot目录内创建一个名为lib的文件夹,用来存放我们的源代码。然后继续创建一个index.ts,作为我们的打包入口。如下图。

image.png

代码到这里就已经写完了,这就是一个最最简单的package。我们完全可以把它发到npm上去,但是,大家也看到了,我们在源码中使用了ts语法,我们不能要求所有使用我们这个包的人都拥有ts编译环境。因此我们需要在发布前就将我们的代码进行编译。我这里选择的编译工具是rollup。

安装依赖

要想编译我们的代码,需要依次安装下列依赖。注意不要企图在一条lerna add指令中安装多个依赖,因为lerna不支持这种用法。我们必须依次安装所有依赖。

npx lerna add typescript -D
npx lerna add rollup -D
npx lerna add rollup-plugin-dts -D   # 用于汇总ts的类型定义
npx lerna add rollup-plugin-typescript2 -D # 将ts语法编译为js

配置tsconfig.json

packages/my-chatter-bot目录下创建tsconfig.json文件,输入如下内容

{
  "compilerOptions": {
    "baseUrl": ".",
    "outDir": "./dist/types",
    "sourceMap": true,
    "target": "ES2018",
    "module": "ESNext",
    "moduleResolution": "node",
    "allowJs": true,
    "strict": true,
    "noEmit": true,
    "noImplicitThis": true,
    "strictNullChecks": false,
    "noUnusedLocals": true,
    "experimentalDecorators": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "removeComments": false,

    "paths": {
      "~/*": [
        "./*"
      ],
      "@/*": [
        "./*"
      ]
    },
    "lib": [
      "ESNext",
      "ESNext.AsyncIterable",
      "DOM"
    ],
    "types": []
  },
  "exclude": [
    "node_modules",
    "dist"
  ]
}

TS配置项的知识足够单独开一片长文来讲了,本文没有办法涵盖如此多的内容。因此如果对其中的配置项有疑问可以参阅官方文档

配置rollup

packages/my-chatter-bot目录下创建rollup.config.js文件,输入如下内容

import typescript from 'rollup-plugin-typescript2';
import dts from 'rollup-plugin-dts'
export default [{
  input: "lib/index.ts",
  output: {
    file: "dist/index.js",
    format: "cjs"
  },
  plugins: [
    typescript()
  ]
},
{
  //汇总.d.ts 类型声明文件
  input: './lib/index.ts',
  output: {
    file: 'dist/index.d.ts',
  },
  plugins: [
    dts()
  ],
}
]

简单解释一下。这里有两套打包配置,第一套配置的含义是:以packages/my-chatter-bot/lib/index.ts文件作为打包入口,将源码打包成符合cjs模块规范的代码,打包的结果会放在packages/my-chatter-bot/dist/index.js文件内。 另一套打包配置的含义是,以packages/my-chatter-bot/lib/index.ts文件作为打包入口,收集源码中全部的ts类型定义,将它们放到packages/my-chatter-bot/dist/index.d.ts文件内。

创建构建指令

我们把默认的test指令删掉。添加一条构建指令,如图。 image.png 这条指令的含义是,使用rollup工具以及rollup.config.js配置文件进行构建。

构建

我们暂且可以直接在packages/my-chatter-bot目录内输入npm run build指令开始构建。指令执行结束后可以看到打包后的文件已经出现了,如下图。 image.png

最后别忘了,我们得为使用我们包的用户指定一个入口,修改packages/my-chatter-bot/package.json中的main字段,将其改为dist/index.js。 这样做的目的是,确保当使用者在代码中写import { ask } from "my-chatter-bot"的时候,node能够依据main字段的值找到node_modules/my-chatter-bot/dist/index.js这个文件。 image.png

多个package联调

接下来我们就要讨论同时开发多个package的情况了。对于monorepo项目来说,通常在packages目录内都是有多个package的,并且他们之间还存在引用关系。

下面让我来拓展下机器人这个例子,为了让我的机器人更加逼真,我打算让机器人每次给出答复之前都思考一秒钟。并且我打算将这个思考的逻辑封装在名为chatter-bot-brains的package中。没错,就是机器人的大脑!下面继续。

创建chatter-bot-brains

chatter-bot-brains的创建过程和my-chatter-bot是相同的,就不赘述了。让机器人思考一秒的代码实现如下: image.png

然后,按照先前的方法,我们在packages/chatter-bot-brains目录下输入npm run build进行打包。

引入chatter-bot-brains

现在我们要在packages/my-chatter-bot/lib/index.ts中引入这个方法。但是我们会发现,编辑器报错了,如图 image.png

这是因为node只会逐级向上地从node_modules里面找这个chatter-bot-brains包,可这个包根本不在任何node_modules目录中而是在packages目录下,因此它当然找不到了。为了解决这个问题,我们可以使用lerna将chatter-bot-brains作为依赖安装到my-chatter-bot中——输入指令 npx lerna add chatter-bot-brains --scope my-chatter-bot

可以看到,packages/my-chatter-bot/package.json的dependencies配置项内已经出现了chatter-bot-brains。并且报错也已经消失了。如果你的报错没有消失,可以尝试关闭文件重新打开或关闭vscode重新打开。

那么,大家有没有好奇这究竟是怎么做到的? 其实原理很简单,当我们执行npx lerna add chatter-bot-brains --scope my-chatter-bot指令的时候,lerna会先去packages目录(工作目录)下检查有没有chatter-bot-brains这个包(比对的是package.json中的name配置项)。如果能找到chatter-bot-brains,它就会在项目根目录的node_modules里面创建一个软连接,链接到packages/chatter-bot-brains。最后在my-chatter-bot的package.json中添加依赖。这样node一级一级向上遍历node_modules时,就能够在项目级的node_modules内查找到chatter-bot-brains,进而就能够通过软链找到packages/chatter-bot-brains。为了证实这个说法,不妨在根目录的node_modules里面执行ls -al指令查看下。如下图: image.png

在真实环境中调试

我们这两个包已经写好了,可我该怎么测试它们在真实环境中的表现呢?如果我另起一个项目来测试不是不可以,但是太麻烦了,因为我得手动执行yarn link,还得不停地在两个窗口间切换。 其实更好的做法是直接在packages目录下创建一个用于测试的项目,我们就把它命名为playground吧!毕竟vue3就是这么命名的。

创建playground

大家可以自由选择创建项目的方式。因为我希望自己的机器人能够在vue3+vite的项目中使用,所以我选择使用vite+官方vue模板来创建项目。即在packages目录下直接执行指令npm init vite@latest playground -- --template vue。创建好的项目目录结构如下图: image.png

为什么要在packages下创建呢?因为这样我们就可以通过lerna add 在playground项目中使用本地尚在开发中的my-chatter-bot和chatter-bot-brains两个package了,十分方便。

要注意,虽然playground在packages目录下,但我们不应该把playground当作公开的包发布到npm上,因此无论你是何种方式创建的playground项目,请确保packages/playground/package.json中包含"private": true这项配置,这是因为lerna不会将包含此项配置的库发布到npm上

另外,还有一点需要提醒各位,无论以何种方式创建了playground项目,在安装全部依赖的时候不要使用yarn installnpm install等指令,而是应该使用npx lerna bootstrap指令来替代它们,这是为了确保所有依赖在安装的时候都能被提升到项目根目录的node_modules中,playground项目当然也不能例外。同样的道理,如果我们要为playground项目安装其他第三方依赖,也不要使用yarn addnpm install而应该使用npx lerna add指令。比如我们接下来就打算在playground中使用my-chatter-bot,因此就应该执行npx lerna add my-chatter-bot --scope playground指令(请确保你已经将my-chatter-bot/dist编译出来了)。我另外解释一下,--scope选项的含义是只为playground这一个package安装my-chatter-bot。如果不加--scope则表示为packages下的所有package安装my-chatter-bot。

为package添加es规范的输出

我本以为安装好my-chatter-bot依赖后,一切都会顺顺利利,但是,噩耗传来了。如果各位读者和我一样使用了vite作为调试工具,并且一步步做到这里的话,一定会发现,当我们引入my-chatter-bot时候虽然编辑器没有报错,可是浏览器的控制台却爆了个错误,如下图: image.png

这因为我们构建出来的模块是cjs规范,再加上我是以解构的方式引入的,因此vite会对这个包进行预构建。 但是很显然,预构建的结果是有问题的。解决方案是,我们需要在构建my-chatter-bot和chatter-bot-brains这两个包的时候为vite额外构建出一个es格式的包。这样vite就不需要再做预构建了,问题也就绕过去了。

具体做法也很简单,只需要在my-chatter-bot/rollup.config.jschatter-bot-brains/rollup.config.js两个文件内添加一组打包配置即可。内容如下:

import typescript from 'rollup-plugin-typescript2';
import dts from 'rollup-plugin-dts'
export default [{
...
{
  input: "lib/index.ts",
  output: {
    file: "dist/index.mjs",
    format: "es" //这里的es指的就是将源码编译成esmodule规范的文件
  },
  plugins: [
    typescript(),
  ]
}
...
]

修改完rollup.config.js之后,还需要修改两个package.json文件。我们在main字段下面继续添加一个名为modules的字段,修改后的内容如下

{
  ...
  "main": "dist/index.js",
  "module": "dist/index.mjs",
  ...
}

我想解释下module这个配置项究竟是个啥。 我在前面已经说过,当我们使用import xxx from “包名”语法引入依赖的时候,并未在包名这个位置写明入口文件的路径,但node却能够找到入口文件。这是因为node会将package.json中的main字段的值视为入口文件的路径。可main和这里的module又有什么关系呢?

包的作者为了更好的兼容性,通常会选择将自己的代码按照CommonJS规范来打包,我们最开始在rollup配置文件中写的format: "cjs"就是这个作用。 多年前这样做确实没啥问题,因为webpack这类工具出现的时候还没有esmoudle规范呢,可随着esmoudle规范的提出,rollup这类打包工具出现了,rollup是希望充分利用esmoudle模块的特性的。我们各位开源作者当然可以提供esmoudle规范的模块文件,可依赖入口只有一个——也就是main。这时候怎么办呢?rollup提出了一个解决办法,它跟我们这些package的开发者达成了一个约定,我们这些开发者提供esmoudle规范的模块文件,然后在package.json中用module字段指明此文件的路径,这样rollup工具在进行打包的时候就会先去检查有没有package.moudle,如果有的话直接使用这个es模块,没有的话才会按照main字段值去找cjs模块的文件。

好了,话题回到我的机器人项目。问题解决之后,我为了让自己的ai聊天程序看起来更专业一些,还给它写了个简陋的ui。如下图(右边是我) image.png 看,我们现在就已经能够在真实的vue3项目中使用我们package的了。

开发时调试与自动构建

现在,我又产生一个想法,我想给机器人加个安慰人的功能——当我们说“我很伤心”的时候机器人会安慰我们。

那,如果我要加这样一个功能的话,该怎么做呢?确实,我只需要在packages/my-chatter-bot/lib/index.ts内加一段逻辑就好了。现在逻辑被我修改成了下图这样。注意红框是新增加的内容 image.png

但是现在问题来了,如果我希望在playground项目可以立即用上这个新功能。我就必须先输入npm run build手动打包my-chatter-bot。这是因为playground项目引入的并不是我的源码,而是我构建出来的dist/index.mjs。那有没有什么办法可以在我保存文件的时候自动执行build指令呢?

当然有的。我们只需要修改下my-chatter-bot与chatter-bot-brains两个package的package.json文件,新增一个dev指令即可。如下图: image.png

平时开发前我可以先执行dev指令,这样每当我保存修改的时候,rollup就会自动构建出新的dist/index.mjs文件,进而vite的hmr也会被触发,最终我们的页面也就使用上最新代码了。这一切都是-w选项的能力。

lerna run

到这里,可能不熟悉lerna的读者会想:“我每次准备开发的时候,仍然得先在三个package下分别执行npm run dev指令才行,十分麻烦啊!”。其实不用这样,我们可以借助lerna run指令来完成这个件事。lerna run的能力是分别在packages目录下的所有package目录内执行指定的npm指令,例如lerna run dev的意思就是分别在所有package中执行npm run dev

另外,由于我们的dev指令需要持续不断地监听文件变化,这就意味着它是需要单独霸占一个进程的。如果不理解为什么的话,各位可以想想你平时是不是每启动一个项目都需要开一个新的控制台?一个控制台就是一个进程。那么为了能让lerna每次执行npm run dev的时候都能使用一个单独的进程,我们还需要为指令添加 --parallel选项,这个选项可以让每个指令都运行在一个单独的子进程上。

于是我们最终需要在控制台执行的指令是npx lerna run dev --parallellerna run的更多内容详见

编写文档

为了让使用者清楚包的使用方法,我们可以在各个package目录下创建README.md文件,如下图: image.png

如果是组件库这种大型项目,一部分文档其实是由代码自动生成的,如果各位对这个感兴趣不妨看看element-ui的源码,我觉得这个项目是很好读的。 这功能说白了也就是一些文件操作。但是却可以帮助我们更深刻的理解工程化这件事。

发布

总算到了发布环节了,这是最后一点内容了,各位坚持住。

登陆npm账号

首次发布前大家请先确定自己已经登陆了npm账号。如果你不清楚自己是不是已经登陆了,那可以在控制台输入npm whoami来查看。如果npm报错提示“This command requires you to be logged in.” 就说明你还没有登陆过。 image.png 此时就需要使用npm login进行登录。这里多提一嘴,建议大家在登陆时使用npm login而非npm adduser,虽然目前8.x版本中login只是adduser的一个别名,可将来adduser指令的功能会变为注册账号(降为register的一个别名)。而login才是登陆指令。

git提交

首次发布之前务必先将代码手动提交到远程仓库一次,否则会报错“No commits in this repository. Please commit something before using version.”

即使非首次发布,每次发布前我们也需要将改动提交到本地或远程仓库,否则报错“Current HEAD is already released, skipping change detection.”

开源证书

npm需要我们提供LICENSE.md文件,开源许可证不在文本讨论范围内。大家可在此网站按需选择。只要确保将证书文件放在packages/chatter-bot-brainspackages/my-chatter-bot目录下即可。

lerna publish

发布非常简单,我们只需要执行lerna publish指令即可。但需要注意的是,由于我们是首次发布,因此还需要额外指定一下版本号,如lerna publish 1.0.0。将来再次发布的时候只需要输入lerna publish就足够了。

指令输入之后,lerna会列举出提交过新版本的package,并询问如何增加他们的版本号,各位酌情选择即可,如下图: image.png

大家在这个过程中会发现playground也需要修改版本,这是肯定的,因为它也是做过修改的package之一。 但是大家放心,playground是不会被发布到npm上的,因为我们已经将其设置为private了,如下图。 image.png

发布完成之后,我们来到自己npm,可以看到包已经在上面了,芜湖! image.png

npm scope

如果各位希望给自己包名中加入scope的话,可以看看这一节内容。

scope的目的是防止命名冲突。例如npm上已经有一个包叫my-chatter-bot了,可我刚刚写的这个包也叫这个名字,而npm是不允许重名的,这可怎么办呢? 这时候就可以使用scope来解决。scope相当于命名空间,类比一下,我们都知道不同命名空间的两个同名变量是不存在命名冲突问题的对吧。

包名中@/之间的部分就是scope。以我们非常熟悉的@vueuse/core为例,名称中的vueuse就是scope。我知道有些读者也希望自己的包名能携带scope,这样不仅可以避免命名冲突,更主要的是包被打上自己“logo” 这件事很令人欣喜!需要注意的是,你的scope必须和你npm的用户名保持一致,否则会报错“Scope not found”

那么该怎么做呢?以my-chatter-bot为例,要想使用scope,我需要先将packages/my-chatter-bot/package.json中的name属性值改为@double-shine/my-chatter-bot,然后继续添加一项配置,如下

"publishConfig": {
    "access": "public"
  }

解释一下添加publishConfig.access的理由。如果包名带scope,那么包默认的可见范围会由public变为private。之前我也说过了,lerna不会发布那些private的包。因此如果想让这个包被发布出去,就需要使用publishConfig.access配置项来告知lerna。

不过,还有一种方法是你可以不配置publishConfig.access,而是在执行lerna publish的时候加上--registry选项,这相当于将所有带scope的包都发布出去,但我觉得这不够灵活,所以没有使用这种方法。

最后发布一下试试看,发现包仍然被发布上来了 image.png

项目clone下来后怎么使用?

项目首次clone下来之后请按照初始化一节的内容进行操作。之后每次调试前请按照启动调试一节的方法来启动项目。

初始化

初始化这几步操作只需要在项目首次被clone下来的时候做一次就够了,不用重复做。

  1. 首先在项目根目录下执行yarn指令。这一步的目的是根据项目根目录下的package.json来安装壳子的依赖,这其中就包括安装lerna。要注意这个指令不会安装packages目录下各个package依赖!指令执行完毕后项目根目录下会出现node_modules目录。
  2. 继续在项目根目录下执行npx lerna bootstrap指令。这一步的作用我在文章中已经说过了,其主要作用是为各个package安装依赖,并且这些依赖会被统一安装到项目根目录的node_moduels中。这一指令还会为chatter-bot-brains创建软链接。指令执行完毕后各个package的根目录下会出现node_modules目录,但目录中只包含.bin目录,不包含依赖代码。

启动调试

每次进行开发工作之前都得启动调试模式,就像大家每次用vue搬砖前都得执行yarn run dev一样。

  1. 项目根目录下执行npx lerna run dev --parallel即可开启调试模式。指令执行完毕后就可以打开http://127.0.0.1:5173/和机器人聊天啦!

拓展

到此为止,一个最最基本且可用的架子就搭好了,各位可以继续添砖加瓦去完善它。目前这个架子还缺少一些基本功能如代码规范化、提交规范化、单元测试等。 我之所以没有把这些内容写到这篇文章里面有两个原因,一个是这些并不是框架的刚需,没有它们我们依然可以进行开发(只是缺少这些功能会让使用者觉得我们的package不太可靠),而且将他们剔除掉也更有助于新手学习核心知识。二是,这篇文章的字数真的太多了,我不确定有多少人能看到这里,如果您觉得文章确实对您有帮助,希望能给我点个赞收个藏,这对我真的很重要。如果大家想看单元测试和规范化相关内容或者希望学习其他monorepo工具以及pnpm相关知识的话可以给我留个言。