在开发若干个有相互依赖关系的库的时候,一般都会采用 symlink 的方式互相引用,比较典型的一种场景就是使用 lerna 开发多个 package 。css
lerna 是用于管理拥有多个 package 的 JavaScript 项目,其典型目录结构为node
lerna-repo/ packages/ package1/ package.json package2/ package.json package.json lerna.json
packages 目录下面就是各个 package 了。webpack
lerna 有两个比较经常使用的命令:git
lerna clean lerna bootstrap
lerna clean
用于清理 packages ,会删掉各个 package 下面的 node_modules 目录。es6
lerna bootstrap
用于处理各个 package 的依赖,处理步骤为:github
在每一个 package 下面执行 npm install
。web
根据各个 package 下 package.json 里面的 dependencies 和 devDependencies 配置,使用 symlink 在各个 package 的 node_modules 下面创建引用关系。npm
在每一个 package 下执行 npm run prepublish
。json
在每一个 package 下执行 npm run prepare
。bootstrap
假设 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
等配置,而不是按照真实文件的路径。
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
这种方式确定不会是万能的,具体怎么作还要看真正的场景,可能还得各类配置互相配合才能解决问题。