在開發若干個有相互依賴關係的庫的時候,一般都會採用 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
這種方式確定不會是萬能的,具體怎麼作還要看真正的場景,可能還得各類配置互相配合才能解決問題。