標籤:

當 webpack 遇上 symlink

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

lerna 簡介

lerna 是用於管理擁有多個 package 的 JavaScript 項目,其典型目錄結構為

lerna-repo/n packages/n package1/n package.jsonn package2/n package.jsonn package.jsonn lerna.jsonn

packages 目錄下面就是各個 package 了。

lerna 有兩個比較常用的命令:

lerna cleannlerna bootstrapn

lerna clean 用於清理 packages ,會刪掉各個 package 下面的 node_modules 目錄。

lerna bootstrap 用於處理各個 package 的依賴,處理步驟為:

  1. 在每個 package 下面執行 npm install 。
  2. 根據各個 package 下 package.json 裡面的 dependencies 和 devDependencies 配置,使用 symlink 在各個 package 的 node_modules 下面建立引用關係。
  3. 在每個 package 下執行 npm run prepublish 。
  4. 在每個 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/n[project root]/package/pkg2/n[project root]/package/n[project root]/n...n

而此時很可能希望能在 [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/externalPkg3n[project root]/package/pkg1/node_modules/pkg2/node_modules/externalPkg3 -> [project root]/package/pkg2/node_modules/externalPkg3n

而 externalPkg3 裡面有個 module 提供了全局的 object :

const obj = {};nnexport function register(name, value) {n obj[name] = value;n}nnexport function getValue(name) {n return obj[name];n}n

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

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

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

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

推薦閱讀:

webpack源碼學習系列之一:如何實現一個簡單的webpack
create-react-boilerplate: 面向 React 技術棧的工程項目腳手架
重溫 Webpack, Babel 和 React
讀懂webpack生成的26行代碼

TAG:webpack |