在Monorepo中开发一个新的npm package过程总结

这周在工作中需要开发一个npm的package,是辅助其他已有的pacakge。整个仓库采用了monorepo的形式,记录一下整个过程中踩的坑和学到的新技术

Lerna的使用

venom仓库采用的是monorepo的方式

monorepo是一种与multirepo不同的代码组织方式,首先,我们解释一下什么是 monorepo 和 multirepo。这两者都是管理组织代码的方式,顾名思义 monorepo 就是把所有的相关项目都放在一个仓库中(比如 React, Angular, Babel, Google…),multirepo 则是按模块分为多个仓库。

Monorepo与MultiRepo

Monorepo——大型前端项目的代码管理方式

总的来说,monorepo是为了将多个项目放在同一个仓库里面去管理,而lerna是实现monorepo的一种框架或者说途径。

这是它的github地址:https://github.com/lerna/lerna

首先我们可以通过lerna init命令在一个文件夹下初始化lerna的配置以及文件目录

1
2
3
4
lerna-repo/
packages/
package.json
lerna.json

lerna.json中的是lerna的配置项(在git的readme中有),package.json中是整个仓库的配置项,而packages下面是一个个的文件夹,每个文件夹都是一个mulitrepo实践下的仓库,也就是我们平时最常见的仓库形式,只不过没有自己的.git

两种模式:Fixed和Independent

Fixed模式下,整个lerna仓库中所有的仓库的版本都是根目录下package.json中的version字段,也就是说都是同一个,也就是说所有的package要一起升版本,即使你只改动了其中一个。

Independent模式下,根目录下的package.json的version字段变为independent,而每个仓库自己的版本在自己目录下的package.json中

公共依赖

大部分的devDependencies可以被提取到lerna仓库的根目录依赖中,通过lerna link convert命令

  • 这样可以减小重复依赖的空间占用

  • 保证每个pacakge使用的是相同版本(如果一开始各个package使用的是不同版本, 提取后好像是使用的最大版本)

  • 减少依赖的安装时间

常用命令

lerna init:初始化lerna配置

Lerna bootstrap:安装所有的依赖

Lerna publish:发布新的版本

Lerna exec:执行命令(–scope可以指定在哪个pacakge中执行)

Lerna create:创建新的 package

Lerna link:将当前Lerna存储库中彼此依赖的所有Lerna软件包符号链接在一起(这个目前没有用过,本地调试我使用的是npm link)

其他的可以去参考github仓库

Npm link的使用(本地调试package)

在你要被其他包使用的包的根目录下执行npm link,就会在全局之中建立一个映射,即从global/a -> a

在要使用a的包中调用npm link a(这个a就是a这个包的package.json中name字段指定的),就会建立起b/node_modules/a -> global/a -> a的映射。

这个时候去修改a,会直接在b中更新。

npm link

NPM Hooks(在安装以后执行脚本)

通常情况下,应用程序只能处理来自内部的消息,如果希望对外部发来的消息也能拦截处理,那就需要一种叫钩子(Hook)的技术。想象一下,npm test这个过程你是控制不了的,但如果就非常想在test之前自动处理点什么事儿,怎么办?没次都手动在test之前执行什么,烦不烦、烦不烦、烦不烦?就是不烦,也会忘啊!

这时候就用到我们的Hook了。下面这些指令都是Hook,它们都可以在package.json的scripts属性里定义,并且会在生命周期的某个指定时刻被执行,这就是上面提到的“对外部发来的消息也能拦截处理”,这极大的方便了开发人员(或许你想做点坏事儿?)

  • prepublish: 在publish该包之前执行。(在包目录下执行npm install时也会执行)

  • postpublish: 在该包publish之后执行

  • preinstall: 在该包被install之前执行

  • postinstall: 在该包被install之后执行

  • preuninstall: 在该包被uninstall之前执行

  • postuninstall: 在该包被uninstall之后执行

  • preversion: 在修改该包的version之前执行

  • postversion: 在修改该包的version之后执行

  • pretest, posttest: 在该包内执行test时执行,其中pretest先于posttest

  • prestop, poststop: 在该包内执行stop时执行,其中prestop先于poststop

  • prestart,poststart: 在该包内执行start时执行,其中prestart先于poststart

  • prerestart, postrestart: 在该包内执行restart脚本时执行,其中prerestart先于postrestart。注意: 如果没有在scripts里显示指定restart脚本,则会自动调用stop,然后再start

上面这些Hooks都是npm预定义好的,也就是说,当你执行npm install时,如果你在scripts里定义了preinstall和postinstall,那它们分别会在npm install之前/后自动执行,不劳你操心!碉堡了,有木有?

还有,任何自定义脚本(通过npm run-script <脚本名>来执行)也可以前缀pre和post为其制作钩子。比如:premyscript,myscript,postmyscript

也就是说,pretest脚本也会在npm test之前运行

Verdccio建立本地仓库(验证hook)

为了测试postinstall的效果,又不想直接将未完成的包直接上传,就可以在本地创建一个自己私有的仓库,这里我采用的是Verdccio

使用起来很简单,可以直接看上面的链接

目前遇到了三个坑:

  1. 首先一定要新建一个用户才行,不然你没法publish(也可能有别的方法)
  2. 如果你publish包以后,想要从中npm install,而你的包依赖的包没有,会报错,这个时候可以去修改配置项,mac是在~/.config/verdaccio/config.yaml中
1
2
3
4
5
uplinks:
npmjs:
url: https://npm.org
yarn:
url: https://npm.org/

配置上游链接

  1. 如果想要把已有的一个仓库删了,也很简单,因为全都会存在本地的~/.local/share/verdaccio/storage中。

node获取仓库地址

有个需求是要通过node获取git的仓库地址,一开始是想读取.git/config

后来发现可以用child_process执行命令行:

1
2
3
4
const gitRemoteUrl = childProcess.execSync(
'git config --get remote.origin.url',
);
return gitRemoteUrl ? gitRemoteUrl.toString() : noGitRepositoryUrl;

如何指定打包的文件都有哪些

由于我的postinstall中执行的脚本是独立的,没有在pacakge.json的main指向的index.js及其依赖中使用到,所以打包publish以后,我的脚本并没有在打包后的目录中,这个时候就需要在package.json的files中指定将我的脚本放进去。

Npm dependences version的坑

img