一、前言
最近准备开发前端的一个模块化的库,业务级别,要求可以任意插拔随意组合。大型工程,需要多人维护。
以前的项目都是业务级,单个包项目。简单,便于管理。但是,当一个大的项目库代码量剧增之后,管理起来就是一件比较麻烦的事情,为了方便代码的共享,就需要将代码库拆分成独立的包。
调研了一下lerna
库,适合我们的场景,babel即用这个工具进行管理。
二、lerna基础
初始化:
$ npm i -g lerna
$ mkdir lerna-repo && cd $_
$ lerna init
生成文件路径如下:
lerna-repo/
packages/
package.json
lerna.json
默认情况下,packages/下即保存多包地址。
管理模式
使用lerna管理项目时,可以选择两种模式。
默认的为固定模式(Fixed mode),当使用lerna init命令初始化项目时,就默认为固定模式,也可以使用 lerna init --independent 命令初始化项目,这个时候就为独立模式(Independent mode)。
固定模式中,packages下的所有包共用一个版本号(version),会自动将所有的包绑定到一个版本号上(该版本号也就是lerna.json中的version字段),所以任意一个包发生了更新,这个共用的版本号就会发生改变。
独立模式允许每一个包有一个独立的版本号,在使用lerna publish命令时,可以为每个包单独制定具体的操作,同时可以只更新某一个包的版本号。此种模式时,lerna.json中的version字段指定为independent
即可。
常用命令
- 启动
lerna bootstrap
切记使用这个指令代替 npm install。默认使用npm安装,如果需要使用yarn或者cnpm可参考4.
这个指令会执行以下操作:
- 在每个 package 下面执行 npm install 。
- 根据各个 package 下 package.json 里面的 dependencies 和 devDependencies 配置,使用
symlink
在各个 package 的 node_modules 下面建立引用关系。 - 在每个 package 下执行
npm run prepublish
。 - 在每个 package 下执行
npm run prepare
。
- 添加依赖
lerna add <pkg> [globs..]
/*
Add a dependency to matched packages
位置:
pkg Package name to add as a dependency [字符串] [必需]
globs Optional package directory globs to match [数组] [默认值: []]
*/
例如目录结构为:
lerna-repo/
packages/
super-page-component/
package.json
index.ts
node_modules/
...
other-xxx-module/
package.json
lerna.json
给 super-page-component
添加 依赖 vue-class-component
,scope后要跟package.json里面的名字,而不是文件夹名。如果全部安装,去掉scope即可。
lerna add vue-class-component --scope=@mfelibs/superpage-component
- 发布
lerna publish
用于发布更新,默认发到npm上,如需修改可参考4。运行该命令会执行如下的步骤:
- 运行lerna updated来决定哪一个包需要被publish
- 如果有必要,将会更新lerna.json中的version
- 将所有更新过的的包中的package.json的version字段更新
- 将所有更新过的包中的依赖更新
- 为新版本创建一个git commit或tag
- 将包publish到npm上
同时,该命令也有许多的参数,例如--skip-git 将不会创建git commit或tag,--skip-npm将不会把包publish到npm上。
- 指定源
如果你的项目并不是用npm管理,像我们一样,可以在需要的操作上都指定源。
例如使用yarn管理包lerna bootstrap --npm-client=yarn
例如使用cnpm发布包lerna publish --npm-client=cnpm
(注意,3.x版本无效)
三、遇到的问题
1. symlink 的问题
看我们前面提到的lerna bootstrap
执行的操作第二步,使用 symlink 在各个 package 的 node_modules 下面建立引用关系。
如果我们的package中有webpack,那么其中的loader很有可能会出问题。
假设 package 下面有一个包 pkg1 ,依赖 package 下面的另一个包 pkg2 。
运行 lerna bootstrap 之后, pkg1/node_modules 下就会出现 pkg2 的 symlink 。
我们遇到的问题是在pkg2中有一个TS文件,export出去。pkg1中去引入,但是发现总是没有命中真实的loader。
如果使用 webpack 系列工具来编译运行 pkg1 ,由于 webpack loader 判断路径默认是按照真实路径来的,所以 pkg2 对应到的路径是 [project root]/package/pkg2
,而不是 [project root]/package/pkg1/node_modules/pkg2
。
这样一来,如果需要 pkg2 中的源码过 pkg1 的 loader (比如 pkg2 中的 TS 通过 pkg1 的 ts-loader),就需要特殊配置。这和不涉及 symlink 的真实场景存在较大差异。
同时,很多配置(比如 postcssrc 、 babelrc 、 eslintrc 等)是以 resolve 到的文件去解析的。
比如要用 babel 编译 pkg2 下面的 [project root]/package/pkg2/src/Report.ts 源码,会按照如下目录顺序查找 babelrc 配置:
[project root]/package/pkg2/src/
[project root]/package/pkg2/
[project root]/package/
[project root]/
...
而此时很可能希望能在 [project root]/package/pkg1/ 目录下寻找配置。
所以此时其实很希望 webpack loader 基于 symlink 的路径去解析判断 include / exclude 等配置,而不是按照真实文件的路径。
所以需要配置webpack 的 resolve.symlinks
来解决这个问题,具体参见官方文档。
2. 指定cnpm源无效
据gitlab issue publish时不接受参数,只能使用npm仓库。
但是旧版本 2.x 的支持,本人测试,截止到3.3.2
,3.x的lerna指定cnpm源publish均无效。