在Monorepo中开发一个新的npm package过程总结
这周在工作中需要开发一个npm的package,是辅助其他已有的pacakge。整个仓库采用了monorepo的形式,记录一下整个过程中踩的坑和学到的新技术
Lerna的使用
venom仓库采用的是monorepo的方式
monorepo是一种与multirepo不同的代码组织方式,首先,我们解释一下什么是 monorepo 和 multirepo。这两者都是管理组织代码的方式,顾名思义 monorepo 就是把所有的相关项目都放在一个仓库中(比如 React, Angular, Babel, Google…),multirepo 则是按模块分为多个仓库。
总的来说,monorepo是为了将多个项目放在同一个仓库里面去管理,而lerna是实现monorepo的一种框架或者说途径。
这是它的github地址:https://github.com/lerna/lerna
首先我们可以通过lerna init命令在一个文件夹下初始化lerna的配置以及文件目录
1 | lerna-repo/ |
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 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
使用起来很简单,可以直接看上面的链接
目前遇到了三个坑:
- 首先一定要新建一个用户才行,不然你没法publish(也可能有别的方法)
- 如果你publish包以后,想要从中npm install,而你的包依赖的包没有,会报错,这个时候可以去修改配置项,mac是在~/.config/verdaccio/config.yaml中
1 | uplinks: |
配置上游链接
- 如果想要把已有的一个仓库删了,也很简单,因为全都会存在本地的~/.local/share/verdaccio/storage中。
node获取仓库地址
有个需求是要通过node获取git的仓库地址,一开始是想读取.git/config
后来发现可以用child_process执行命令行:
1 | const gitRemoteUrl = childProcess.execSync( |
如何指定打包的文件都有哪些
由于我的postinstall中执行的脚本是独立的,没有在pacakge.json的main指向的index.js及其依赖中使用到,所以打包publish以后,我的脚本并没有在打包后的目录中,这个时候就需要在package.json的files中指定将我的脚本放进去。