在开发若干个有相互依赖关系的库的时候,通常都会采用 symlink 的方式互相引用,比较典型的一种场景就是使用 lerna 开发多个 package 。
lerna 简介
lerna 是用于管理拥有多个 package 的 JavaScript 项目,其典型目录结构为
lerna-repo/
packages/
package1/
package.json
package2/
package.json
package.json
lerna.json
packages 目录下面就是各个 package 了。
lerna 有两个比较常用的命令:
lerna clean
lerna bootstrap
lerna clean
用于清理 packages ,会删掉各个 package 下面的 node_modules 目录。
lerna bootstrap
用于处理各个 package 的依赖,处理步骤为:
在每个 package 下面执行
npm install
。根据各个 package 下 package.json 里面的 dependencies 和 devDependencies 配置,使用 symlink 在各个 package 的 node_modules 下面建立引用关系。
在每个 package 下执行
npm run prepublish
。在每个 package 下执行
npm run prepare
。
symlink 的问题
假设 package 下面有一个包 pkg1
,依赖 package 下面的另一个包 pkg2
。
运行 lerna bootstrap
之后, pkg1/node_modules
下就会出现 pkg2
的 symlink 。
如果使用 webpack 系列工具来编译运行 pkg1
,由于 webpack loader 判断路径默认是按照真实路径来的,所以 pkg2
对应到的路径是 [project root]/package/pkg2
,而不是 [project root]/package/pkg1/node_modules/pkg2
。
这样一来,如果需要 pkg2
中的源码过 pkg1
的 loader (比如 pkg2
中的 ES6 代码过 pkg1
的 babel-loader
),就需要在 webpack 相应 loader 配置中加上这个特殊的路径匹配,这和不涉及 symlink
的真实场景
存在较大差异。
同时,很多配置(比如 postcssrc
、 babelrc
、 eslintrc
等)是以 resolve 到的文件去解析的,比如要用 babel 编译 pkg2
下面的 [project root]/package/pkg2/src/Dialog.es6
源码,会按照如下目录顺序查找 babelrc
配置:
[project root]/package/pkg2/src/
[project root]/package/pkg2/
[project root]/package/
[project root]/
...
而此时很可能希望能在 [project root]/package/pkg1/
目录下寻找 babelrc 配置。
所以此时其实很希望 webpack loader 基于 symlink 的路径去解析判断 include / exclude
等配置,而不是按照真实文件的路径。
resolve.symlinks
webpack 提供了 resolve.symlinks
来解决这个问题,具体参见官方文档。
新的问题
虽然使用 symlink 解决了基准路径
的问题,但是还存在另外的问题。
如果 pkg2
依赖了 babel-runtime
,那么在 pkg1
的配置中就要注意不要让 babel-runtime
过 babel-loader
了,不然 babel 可能会在 babel-runtime
的源码里面插入一些 ES6 的代码。
如果 pkg1
和 pkg2
同时依赖了第三方模块 externalPkg3
,那么在 lerna bootstrap
之后,会存在两个 externalPkg3
:
[project root]/package/pkg1/node_modules/externalPkg3
[project root]/package/pkg1/node_modules/pkg2/node_modules/externalPkg3 -> [project root]/package/pkg2/node_modules/externalPkg3
而 externalPkg3
里面有个 module 提供了全局的 object :
const obj = {};
export function register(name, value) {
obj[name] = value;
}
export function getValue(name) {
return obj[name];
}
此时 pkg1
和 pkg2
会用各自的 obj
对象,如果 pkg1
中想用 pkg2
注册进去的 value
,就会拿不到。
可以考虑在 lerna.json
中配置 commands.bootstrap.ignore
为 ["pkg2"]
,在 lerna bootstrap
的时候不安装 pkg2
的依赖,使得最终只会有一个 externalPkg3
:
[project root]/package/pkg1/node_modules/externalPkg3
这种方式肯定不会是万能的,具体怎么做还要看真正的场景,可能还得各种配置互相配合才能解决问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。