當 webpack 趕上 symlink

在開發若干個有相互依賴關係的庫的時候,一般都會採用 symlink 的方式互相引用,比較典型的一種場景就是使用 lerna 開發多個 package 。css

lerna 簡介

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

  1. 在每一個 package 下面執行 npm installweb

  2. 根據各個 package 下 package.json 裏面的 dependencies 和 devDependencies 配置,使用 symlink 在各個 package 的 node_modules 下面創建引用關係。npm

  3. 在每一個 package 下執行 npm run prepublishjson

  4. 在每一個 package 下執行 npm run preparebootstrap

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 代碼過 pkg1babel-loader),就須要在 webpack 相應 loader 配置中加上這個特殊的路徑匹配,這和不涉及 symlink真實場景存在較大差別。

同時,不少配置(好比 postcssrcbabelrceslintrc 等)是以 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-runtimebabel-loader 了,否則 babel 可能會在 babel-runtime 的源碼裏面插入一些 ES6 的代碼。

若是 pkg1pkg2 同時依賴了第三方模塊 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];
}

此時 pkg1pkg2 會用各自的 obj 對象,若是 pkg1 中想用 pkg2 註冊進去的 value ,就會拿不到。

能夠考慮在 lerna.json 中配置 commands.bootstrap.ignore["pkg2"] ,在 lerna bootstrap 的時候不安裝 pkg2 的依賴,使得最終只會有一個 externalPkg3

[project root]/package/pkg1/node_modules/externalPkg3

這種方式確定不會是萬能的,具體怎麼作還要看真正的場景,可能還得各類配置互相配合才能解決問題。

相關文章
相關標籤/搜索